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

от автора

Меня всегда раздражало, что я не могу запустить 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 вместо того, чтобы генерировать бесполезный заголовок по умолчанию.

А вот и весь исходный код в виде пулл-реквеста.

ссылка на оригинал статьи https://habr.com/ru/post/484854/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *