
Исполняемые файлы под Windows состоят из двух частей:
- Программа для DOS, которая выводит на экран «Данна программа не может быть запущена в режиме DOS»
- Заголовок исполняемого файла, который понимает 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/
Добавить комментарий