Запускаем игру на C# в MS-DOS

Автор оригинала: Michal Strehovský
  • Перевод
Меня всегда раздражало, что я не могу запустить 64-битную игру на C# под MS-DOS. Сегодня я это исправил.



Исполняемые файлы под Windows состоят из двух частей:

  1. Программа для DOS, которая выводит на экран «Данна программа не может быть запущена в режиме DOS»
  2. Заголовок исполняемого файла, который понимает Windows

В некотором смысле все .exe-файлы являются программами для DOS, но не делают ничего полезного. И вот однажды я нашел проект на Github, который заслуживает гораздо больше звезд, чем у него есть:

github.com/Baron-von-Riedesel/Dos64-stub

Dos64-stub — это маленькая программа, которая заменяет бесполезное сообщение «не может быть запущено под DOS» на загрузку Windows-секции исполняемого файла и «телепортацию» процесса в 21 век. Под «телепортацией» я подразумеваю настройку страничной памяти и перевод процессора в 64-битный режим («long»).

Для начала я взял свою игру «Змейка» под Windows, которую недавно ужал до 8 кб без зависимостей:



Конечно, в DOS недоступны привычные методы Windows API, поэтому пришлось переписать слой общения игры со внешним миром. Вот так выглядит теперь Environment.TickCount:

public static unsafe long TickCount64
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    get
    {
        // Читаем область данных BIOS - счетчик прерываний 1AH.
        // Да, мы просто обращаемся к памяти напрямую по некому "случайному" смещению.
        // Область данных BIOS - это документированная структура,
        // которую BIOS заполняет при старте компьютера.
        // Приложения под DOS могут ее читать.
        // По смещению 0x46C лежит прошедшее время в интервалах по 55мс.
        uint timerTicks = * (uint *) 0x46C;
        return (long) timerTicks * 55;
    }
}

Когда у нас есть количество тиков, можно сделать Thread.Sleep:

public static unsafe void Sleep(int delayMs)
{
	// Помещаем на стек последовательность байтов: 0xF4 0xC3
        // Эти байты соответствуют ассемблерным инструкциям:
	//
	// hlt
	// ret
	
	ushort hlt = 0xc3f4;
	long expected = Environment.TickCount64 + delayMs;
	while(Environment.TickCount64 < expected)
	{
		// Вызываем код, который мы положили на стек, чтобы ненадолго
		// поставить процессор на паузу
		// (Безопасники сейчас рыдают в углу)

		ClassConstructorRunner.Call<int>(new IntPtr(&hlt;));
	}
}

А потом и Console.WriteLine:

public static unsafe void Write(char c)
{
    byte* biosDataArea = (byte*)0x400;
	
    // Находим начало VRAM, считывая данные из BIOS
    byte* vram = (byte*)0xB8000;
    if(*(biosDataArea + 0x63) == 0xB4)
        vram = (byte*)0xB0000;
		
    // Находим смещение активной видеостраницы
    vram += * (ushort*)(biosDataArea + 0x4E);

    // Транслируем символы юникода в символы хардварной кодировки IBM
    byte b = c switch
    {
        '│' => (byte)0xB3,
        '┌' => (byte)0xDA,
        '┐' => (byte)0xBF,
        '─' => (byte)0xC4,
        '└' => (byte)0xC0,
        '┘' => (byte)0xD9,
        _ => (byte)c,
    };

    // TODO: считать число колонок из BIOS
    vram[(s_cursorY * 80 * 2) + (s_cursorX * 2)] = b;
    vram[(s_cursorY * 80 * 2) + (s_cursorX * 2) + 1] = (byte)s_consoleAttribute;
	
    // TODO: скроллить или переносить на другую сторону?
    s_cursorX++;
}

Ну а дальше все просто: компилятор C# -> компилятор CoreRT AOT -> линковщик. Мы указываем линковщику использовать Dos64-stub вместо того, чтобы генерировать бесполезный заголовок по умолчанию.



А вот и весь исходный код в виде пулл-реквеста.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 14

    +2
    С появлением AOT наконец то первые шаги C# в эмбеддед (про интерпретатор в курсе — не в счет).

    Не понял по исходникам про наличие управления памятью и String. Похоже, заглушки.
      +2
      В первой статье автора о том, как он упаковал змейку в 8кб, все рассказано — рантайм полностью вырезан, пользоваться reference-типами нельзя (т.е. только структуры, никаких аллокаций). Ограничения жесткие и далеко не любой код можно так зарефакторить. Однако змейка работает :)
        +1
        Для embedded еще нужен hardRT, многозадачность, и полноценное управление динамической памятью, думаете есть шансы что кто-то сможет оторвать от модели языка C# сборщик мусора?
          +1

          Уже есть Zero Garbage Collector, который просто ничего не делает с мусором :)

            0

            Ну собственно в Unity вполне успешно оторвали для performance critical кода

              0
              Особых отличий не вижу
              That said, when working on a piece of performance critical code, we can give up on most of the standard library, (bye Linq, StringFormatter, List, Dictionary), disallow allocations (=no classes, only structs), reflection, the garbage collector and virtual calls, and add a few new containers that you are allowed to use (NativeArray and friends). Then, the remaining pieces of the C# language are looking really good.
          +3
          Меня всегда раздражало, что я не могу запустить 64-битную игру на C# под MS-DOS.

          Наверное человека это раздражало ещё с 90-х? ;)

            +3
            Ну логично. В 90-х он тоже не мог запустить 64-битную игру на C# под MS-DOS — потому что ещё не было C#. Это наверняка ужасно раздражает.
              0

              В 90-е не было не только C#, но и 64-битной архитектуры в мейнстриме (разве только на суперкомпьютерах и каких-нибудь графических станциях).

            +4
            эх, прям как 0xB8000 увидел — так на 25 лет назад вернулся: EMS, XMS, VESA, прерывания… чудные были времена, игры со смыслом :)
              0

              Не только игры, но и тексты программ имели видимый смысл...

                0
                игры со смыслом

                сразу представил библейского змея-диавола, совратившего первых людей, предающегося греху обжорства?

                0
                Хм, спасибо за информацию про AOT.
                  –1
                  image

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое