PunchNAF flew 6,100 hours, consumed 10m litres fuel — SpokesmanThe Jerusalem PostOver 50,000 killed in Europe over summer due to worst heat wave on recordRai NewsSci, Soelden, gigante maschile: Braathen domina prima manche, Borsotti è il migliore degli azzurriInquirer15 passengers, driver hurt as jeepney overturns in QuezonESPNIt's time for El Clásico! All you need to know about Barça vs. Madrid, the biggest match in club soccerDaily MaverickSMOOTH OPERATOR: Gayton Mckenzie’s gift of the gab is both his blessing and his curse한겨레“죽도록 일 더 해서 돈 더 벌어라? 정부가 할 소리냐”…이 대통령 발언에 비판 확산ZDF heuteAktuelle Pressemitteilungen des ZDFBBC News BrasilAs pesquisas para presidente na véspera do 2º turno — o que diziam em 2018, 2022 e o que mostram agoraIl Fatto Quotidiano“Per cantare Aeroplano con gli 883 ho preso un milione di lire. Un giorno Cecchetto mi ha detto ‘non siamo le persone giuste per te, meglio chiudere il contratto'”: parla Caterina RappoccioSportstarTamil Nadu head coach Yo Mahesh hopes for an encore at home despite rain threatThe South AfricanCarte Blanche coming up: Warning bells
The Daily Newsstand · Free, Always
Sunday, October 25, 2026

Windows 98 на Windows 11 в 2026 году: пошаговый гайд по 86Box (и как отдать установку ИИ‑агенту)

Translate

Старые программы конца 90-х на Windows 11 то зависают до нажатия клавиши, то молчат, то не запускаются вовсе. Песочница Windows тут не помогает, VirtualBox с Windows 98 дружит плохо. Рабочим вариантом оказался эмулятор 86Box: за полтора‑два часа он даёт настоящую Windows 98 SE в окне, со звуком, мышью и CD‑приводом. Руками из этого времени — минут 20, остальное ожидание.

Ниже — пошаговый гайд, который я проверил на живой установке 21.10.2026 (Windows 11 Pro, 86Box 6.0 build 9001, Windows 98 SE English 4.10.2222A), со всеми граблями, на которые наступили. В конце — бонус: инструкция для ИИ‑агента, по которой Claude Code или похожий агент проходит всю установку сам.

Зачем это вообще нужно

Многие программы 1995–2000 годов на Windows 10/11 не запускаются или работают с ошибками. Чаще всего им нужен 16-битный код, а 64-битная Windows не умеет выполнять его в принципе. В Windows 98 они работают как задумано.

Почему 86Box, а не что‑то привычное:

  • Песочница Windows — это та же Windows 11, только одноразовая. Ограничения у неё те же, что у основной системы.

  • VirtualBox запускает Windows 98 плохо: на современных процессорах бывают сбои, драйверов под неё почти нет. К тому же рядом со встроенной виртуализацией Windows 11 он работает медленнее.

  • 86Box не виртуализирует, а полностью эмулирует ПК конца 90-х: материнскую плату, Pentium, видеокарту S3, Sound Blaster 16. Windows 98 видит знакомое железо и сама ставит драйверы. Эмулятор бесплатный, с открытым кодом и работает из одной папки без установки.

Что понадобится

Что

Зачем

Где взять

Размер

86Box 6.0 для Windows (64-bit)

Эмулятор старого ПК

github.com/86Box/86Box/releases

60 МБ

ROM‑файлы 86Box той же версии

Прошивки BIOS плат и видеокарт

github.com/86Box/roms/releases

75 МБ

Образ Windows 98 SE (ISO)

Установочный диск

Ваш диск или образ (см. ниже)

450–660 МБ

Ключ продукта

Вводится при установке

Наклейка на коробке или конверте диска

25 символов

Свободное место

Программы, образы, виртуальный диск

—

около 4 ГБ

Про лицензию. Windows 98 до сих пор принадлежит Microsoft, официально её не продают и не раздают. Легальный путь — свой старый диск с ключом: коробочные Windows 98 SE недорого находятся на Авито и eBay, а ISO с диска снимает любая программа для записи дисков. Образы из архивов в интернете лицензии не дают. Но можно не заморачиваться и зайти на http://old‑dos.ru/ — там есть все необходимое.

Какой образ подойдёт. Копия оригинального диска Windows 98 SE (4.10.2222) или первой Windows 98 (4.10.1998) размером 450–660 МБ; язык неважен. Файлы на 60–200 МБ — дискетные или урезанные варианты, они не подойдут. Сборки с пометками «ключ вшит» или «с дополнениями» лучше обходить: такой образ кто‑то изменил, и неизвестно как.

Шаг 1. Скачиваем 86Box и ROM‑файлы

86Box не устанавливается: два архива распаковываются в одну папку, в систему и реестр ничего не пишется. Удалить всё — значит стереть папку.

  1. Создайте папку проекта, например C:\Users\<вы>\Desktop\win98.

  2. Скачайте с последнего релиза 86Box архив 86Box-Windows-64-b….zip (для 6.0 — 86Box-Windows-64-b9001.zip) и распакуйте в win98\86box\.

  3. В релизах ROM‑файлов скачайте архив той же версии (для 6.0 — тег v6.0, «Source code (zip)»). Теги с pre и beta не берите.

  4. Папку roms-6.0 из архива переименуйте в roms и положите рядом с 86Box.exe. Внутри должны быть machines, video, sound и другие.

Итоговая структура (vm и iso появятся дальше):

win98\
├─ 86box\
│  ├─ 86Box.exe
│  └─ roms\          ← machines, video, sound, …
├─ vm\win98\         ← настройки и диск виртуальной машины
└─ iso\              ← образы дисков и дискет

Проверка (по желанию). Сравните SHA-256 архива с указанной на странице релиза:

Get-FileHash .\86Box-Windows-64-b9001.zip -Algorithm SHA256

Для 6.0 должно получиться B86722ED547089F095E8875805411F83C76AAFD7D318F67286A5A69AECDD7644. Сам 86Box.exe подписан ведущим разработчиком (Miran Grča): «Свойства» → «Цифровые подписи». Если при первом запуске появится SmartScreen — «Подробнее» → «Выполнить в любом случае», это обычное дело для редко скачиваемых программ.

Шаг 2. Собираем виртуальный компьютер

Собираем типичный домашний ПК 1997 года — всё это железо Windows 98 знает сама.

Компонент

Что выбрать

Почему

Материнская плата

[i430HX] ASUS P/I‑P55T2P4

Надёжная плата под Pentium, BIOS грузится с CD

Процессор

Intel Pentium MMX, 200 МГц

Быстро для Windows 98 и тянется на 100% скорости

Память

64 МБ

Больше 512 МБ Windows 98 не любит

Видеокарта

S3 ViRGE (Diamond Stealth 3D 2000) PCI

Драйвер есть на диске Windows 98

Звуковая карта

Sound Blaster 16 PnP

Определяется сама, драйвер встроен

Мышь

PS/2

—

Жёсткий диск

IDE, 2 ГБ (63/16/4161), Primary Master

Хватает с запасом

CD‑привод

ATAPI, Secondary Master, 24 x

—

Дисковод

3,5" 1,44 МБ

Нужен для загрузочной дискеты

Сеть

Нет

Интернет Windows 98 не нужен и небезопасен

Проще всего не прокликивать это в настройках, а положить готовый конфиг. Создайте папку win98\vm\win98\ и в ней файл 86box.cfg:

[General]
vid_renderer = qt_software

[Machine]
machine = p55t2p4
cpu_family = pentium_p55c
cpu_speed = 200000000
cpu_multi = 3
cpu_use_dynarec = 1
mem_size = 65536

[Video]
gfxcard = virge_pci

[S3 ViRGE PCI]
bios = stealth3d_2000_pci

[Input devices]
mouse_type = ps2

[Sound]
sndcard = sb16_pnp

[Hard disks]
hdd_01_parameters = 63, 16, 4161, 0, ide
hdd_01_fn = win98.img
hdd_01_ide_channel = 0:0

[Floppy and CD-ROM drives]
fdd_01_type = 35_2hd
fdd_02_type = none
fdd_01_fn = ../../iso/win98se_boot_nojo.img
cdrom_01_parameters = 1, atapi
cdrom_01_ide_channel = 1:0
cdrom_01_speed = 24
cdrom_01_image_path = ../../iso/win98se.iso

Пути относительные, так что папку win98 можно переносить целиком.

Пустой жёсткий диск — это файл из нулей ровно на 63 × 16 × 4161 × 512 = 2 147 475 456 байт. Создайте его в PowerShell из папки vm\win98:

fsutil file createnew win98.img 2147475456

Или в самом 86Box: «Инструменты» → «Настройки» → «Жёсткие диски» → «Новый…».

Запуск. Создайте в папке win98 файл start_win98.bat:

@echo off
start "" "%~dp086box\86Box.exe" -P "%~dp0vm\win98"

Ключ -P говорит 86Box, где лежат настройки машины. При первом запуске он допишет в конфиг служебные строки — это нормально. Важно: правьте 86box.cfg только при закрытом 86Box, при выходе он перезаписывает файл.

Шаг 3. Самое неочевидное: загрузочная дискета без JO.SYS

Установочный диск Windows 98 SE в 86Box зависает при загрузке. Внутри диска записана обычная загрузочная дискета Windows 98, а перед ней стоит маленький загрузчик JO.SYS с меню «Boot from Hard Disk / Boot from CD‑ROM». Пункт «Boot from CD‑ROM» в 86Box висит на чёрном экране с курсором — я ждал больше минуты, без результата.

Решение: достаём эту дискету из образа и удаляем из неё JO.SYS. Без него дискета сразу грузит DOS с меню установки, а файлы читает с CD через свои драйверы.

  1. Положите образ в win98\iso\ под именем win98se.iso. Файл с ключом (часто Serial.txt) сохраните.

  2. Сохраните скрипт ниже как make_bootdisk.ps1 в папку win98 и запустите:

powershell -ExecutionPolicy Bypass -File .\make_bootdisk.ps1 -Iso .\iso\win98se.iso -Out .\iso\win98se_boot_nojo.img
param([string]$Iso, [string]$Out)
$fs = [IO.File]::OpenRead((Resolve-Path $Iso)); $b = New-Object byte[] 2048
# Сектор 17 — запись El Torito, в ней ссылка на каталог загрузки
$fs.Position = 17 * 2048; [void]$fs.Read($b, 0, 2048)
if ([Text.Encoding]::ASCII.GetString($b, 7, 23) -ne 'EL TORITO SPECIFICATION') { throw 'Образ не загрузочный' }
$fs.Position = [int64][BitConverter]::ToUInt32($b, 0x47) * 2048; [void]$fs.Read($b, 0, 2048)
if ($b[33] -ne 2) { throw 'Загрузочная запись не является дискетой 1,44 МБ' }
$img = New-Object byte[] 1474560
$fs.Position = [int64][BitConverter]::ToUInt32($b, 40) * 2048; [void]$fs.Read($img, 0, $img.Length); $fs.Close()
# Корневой каталог FAT12: после зарезервированных секторов и таблиц FAT
$bps = [BitConverter]::ToUInt16($img, 11); $res = [BitConverter]::ToUInt16($img, 14)
$root = ($res + $img[16] * [BitConverter]::ToUInt16($img, 22)) * $bps; $entries = [BitConverter]::ToUInt16($img, 17)
for ($i = 0; $i -lt $entries; $i++) { $o = $root + 32 * $i
  if ([Text.Encoding]::ASCII.GetString($img, $o, 11) -eq 'JO      SYS') { $img[$o] = 0xE5; 'JO.SYS удалён' } }
[IO.File]::WriteAllBytes((Join-Path (Get-Location) $Out), $img); "Готово: $Out"

Как это работает: в 17-м секторе ISO лежит запись El Torito со ссылкой на каталог загрузки, оттуда берём смещение образа дискеты 1,44 МБ, копируем его и в корневом каталоге FAT12 помечаем запись JO.SYS удалённой (байт 0xE5). Сам ISO скрипт не меняет. Работает с оригинальными дисками Windows 98 и 98 SE.

Альтернатива без скрипта — готовый образ «Windows 98 SE Boot Disk» (.img, 1,44 МБ), положенный как iso\win98se_boot_nojo.img. Этот вариант мы не проверяли, но такие дискеты устроены так же, только без JO.SYS.

Шаг 4. Первый запуск и BIOS

BIOS настраивается один раз: дискета должна стоять первой в порядке загрузки. Иначе после разметки диска машина попробует загрузиться с пустого винчестера и остановится.

Два правила работы с окном 86Box: клавиатура работает в машине, когда окно активно (раскладка внутри всегда английская), а чтобы мышь заработала внутри, щёлкните по экрану машины. Вернуть курсор в Windows 11 — Ctrl+End или средняя кнопка мыши.

  1. Запустите start_win98.bat. Появится POST: Pentium‑MMX 200 МГц, 65 536 КБ, Creative SB16 PnP, диск и CD. «CMOS checksum error — Defaults loaded» при первом запуске — норма.

  1. Пока идёт проверка памяти, несколько раз нажмите Delete — откроется CMOS Setup Utility. Не успели — «Действие» → «Жёсткий сброс».

  1. Выберите BIOS FEATURES SETUP, спуститесь на Boot Sequence и нажимайте Page Down, пока не появится A,CDROM,C. Теперь при вставленной дискете машина грузится с неё, без дискеты — с жёсткого диска.

  1. Esc, F10, Y, Enter — настройки сохранятся, машина загрузится с дискеты.

Настройки BIOS лежат в vm\win98\nvr. Чтобы они не терялись, закрывайте 86Box крестиком с подтверждением, а не через диспетчер задач.

Шаг 5. Текстовая часть установки

Установщик сам размечает и форматирует диск, FDISK вручную не нужен. Около 10 минут, из них 5 — форматирование. Всё происходит внутри win98.img, реальный диск не затрагивается.

  1. В Startup Menu выберите 1. Start Windows 98 Setup from CD‑ROM (через 10 секунд выберется сам). Строки Adaptec ASPI Manager — это грузятся драйверы CD.

  2. Welcome to Setup → Enter.

  3. Configure unallocated disk space (recommended) → Enter.

  4. На вопрос про large disk support стрелкой вниз выберите Yes, enable large disk support — будет FAT32. Внимание: по умолчанию выделено «No».

  5. Запрос на перезагрузку с дискетой в A: — дискета уже вставлена, Enter.

  6. Снова Startup Menu → 1. Пойдёт форматирование C: (около 5 минут).

  7. «Setup is now going to perform a routine check on your system» → Enter. ScanDisk за полминуты, затем графическая часть.

Шаг 6. Мастер установки

Везде подходят варианты по умолчанию, всё делается клавишей Enter.

Экран

Что сделать

Welcome to Windows 98 Setup

Continue

Select Directory

Оставить C:\WINDOWS, Next

Setup Options

Typical, Next

Windows Components

Install the most common components, Next

Identification

Имя компьютера латиницей до 15 символов (например, RETRO98), Next

Establishing Your Location

Любая страна, Next

Start Copying Files

Next

Страна влияет только на региональные ссылки в Internet Explorer. Если предложат создать Startup Disk — Cancel, дискета у вас уже есть. Дальше примерно 20 минут копирования со слайд‑шоу.

Пока идёт копирование, извлеките дискету: «Носители» → «Дисковод 1 (3.5» 1.44M)…» → «Извлечь». Если её оставить, после перезагрузки меню само запустит установку заново. Ничего не сломается, но придётся извлекать и перезагружаться ещё раз.

И не пытайтесь «испортить» файл дискеты, пока он вставлен: 86Box держит её в памяти и изменений файла не заметит. Мы проверили.

Шаг 7. Первая загрузка

После копирования машина сама перезагрузится в Windows 98 с жёсткого диска. Если вместо заставки «Getting ready to run Windows for the first time» снова появилось чёрное Startup Menu — вы забыли извлечь дискету.

  1. User Information — любое имя, например User.

  2. License Agreement — I accept the Agreement (Alt+A).

  3. Windows Product Key — 25 символов без дефисов, курсор сам перескакивает. Ключ должен подходить к диску: полная версия, Upgrade и OEM не всегда взаимозаменяемы.

  4. Start Wizard → Finish.

  5. Hardware Detection — около 5 минут. Предупреждение про «may cause your computer to stop responding» пишется всегда; у нас ничего не зависло. Ничего не нажимайте.

  6. Date/Time Properties — время уже взято с хоста, выберите часовой пояс и Close.

  7. «Updating System Settings» — ещё около 5 минут и перезагрузка.

  8. Enter Windows Password — оставьте пустым, OK. Больше пароль спрашивать не будут.

  9. Add New Hardware Wizard для Plug and Play Monitor: Next → «Search for the best driver» → снимите галочку «Floppy disk drives» → драйвер найдётся в C:\WINDOWS\INF\MONITOR.INF → Finish.

Появится рабочий стол и окно Welcome to Windows 98!

Установка завершена. Выключайте машину так же, как настоящий компьютер тех лет: Start → Shut Down → Shut down → OK, дождитесь надписи «It's now safe to turn off your computer» и только потом закройте окно 86Box. Если закрыть окно с работающей Windows, при следующем запуске она устроит проверку диска ScanDisk.

После установки: перенос файлов и запись экрана

На этом Windows 98 установлена, и гайд, по сути, закончен. Дальше — не обязательные шаги, а мой опыт. Я переносил в машину старую запись экрана конца 90-х и записывали её воспроизведение в MP4, чтобы смотреть уже на современном компьютере. По дороге набралось несколько неочевидных вещей — возможно, они пригодятся и вам.

Как передать файлы внутрь

Общих папок у 86Box нет, и для безопасности это даже хорошо: старая система не видит ваших файлов. Проще всего передавать их через образ диска: собрать ISO из папки и «вставить» его в виртуальный привод. Мы собирали образ встроенным в Windows IMAPI2, без сторонних программ:

powershell -ExecutionPolicy Bypass -File .\make_iso.ps1 -Folder .\cd_files -Out .\iso\my_files.iso -Label MY_FILES
param([string]$Folder, [string]$Out, [string]$Label = 'FILES')
Add-Type -TypeDefinition @'
using System; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes;
public static class IsoWriter { public static void Save(object stream, string path) {
  IStream s = (IStream)stream; byte[] buf = new byte[65536]; IntPtr pRead = Marshal.AllocHGlobal(4);
  using (FileStream f = File.Create(path)) { while (true) { s.Read(buf, buf.Length, pRead); int n = Marshal.ReadInt32(pRead); if (n <= 0) break; f.Write(buf, 0, n); } }
  Marshal.FreeHGlobal(pRead); } }
'@
$fs = New-Object -ComObject IMAPI2FS.MsftFileSystemImage
$fs.FileSystemsToCreate = 3   # ISO 9660 + Joliet (длинные имена файлов)
$fs.VolumeName = $Label
$fs.Root.AddTree((Resolve-Path $Folder).Path, $false)
[IsoWriter]::Save($fs.CreateResultImage().ImageStream, (Join-Path (Get-Location) $Out))
"Готово: $Out"

Подойдёт и AnyBurn или ImgBurn. Вставить диск можно на ходу («Носители» → «CD‑ROM 1 (ATAPI)…» → «Образ…») или при закрытом 86Box, поменяв в конфиге cdrom_01_image_path = ../../iso/my_files.iso. Внутри Windows 98 диск будет под буквой D:.

Как записать экран в MP4

Я записывал встроенным в Windows 11 Xbox Game Bar: он пишет окно 86Box вместе со звуком приложения. 22 минуты записи у нас заняли 164 МБ.

  1. Щёлкните по заголовку окна 86Box и нажмите Win+Alt+R — появится индикатор с таймером.

  2. Запустите видео или программу внутри Windows 98 и не сворачивайте окно до конца записи. Если картинка мелкая, перед записью увеличьте окно: «Вид» → масштаб ×2.

  3. Остановите запись кнопкой ■ на индикаторе. Файл появится в C:\Users\<вы>\Videos\Captures, лишнее в начале и конце можно обрезать в Clipchamp.

Нюансы Game Bar: пока идёт запись, в Captures лежит файл на пару сотен килобайт — видео дописывается целиком только при остановке, так что ориентируйтесь на таймер. Win+Alt+R — переключатель: «нажать ещё раз на всякий случай» начнёт новую запись (у нас так появились файлы на 4 и 18 секунд). Перечёркнутый микрофон значит только, что не пишется ваш микрофон, звук 86Box пишется.

С чем мы столкнулись по дороге

Экран почернел посреди записи. По умолчанию Windows 98 гасит монитор через 15 минут без движения мыши, и воспроизведение видео ей не мешает: в строке состояния 86Box появляется «Монитор в спящем режиме», а в записи — чёрный экран. Перед долгой записью отключите это: Start → Run… → control powercfg.cpl → Turn off monitor = Never. Заставку на всякий случай проверьте в control desk.cpl,,1 — там должно стоять (None).

Работает ли звук в самой Windows 98, проще всего проверить командой в Start → Run…: sndrec32 /play /close c:\windows\media\tada.wav.

Разрешение экрана меняется в control desk.cpl,,3, но мы оставили 640×480: старые программы и видео тех лет рассчитаны именно на него.

Грабли, на которые мы наступили

Симптом

Причина

Решение

После «Boot from CD‑ROM» чёрный экран с курсором

JO.SYS на диске 98 SE виснет в 86Box

Грузиться с дискеты без JO.SYS (шаг 3)

DISK BOOT FAILURE, INSERT SYSTEM DISK

Дискета не вставлена или BIOS её не пробует

Проверить fdd_01_fn и порядок A,CDROM,C

Не удаётся попасть в BIOS

Delete нажата поздно или окно не активно

Щёлкнуть по заголовку 86Box, жёсткий сброс, сразу жать Delete

Установка начинается заново после копирования

В приводе осталась дискета

«Носители» → «Дисковод 1» → «Извлечь», жёсткий сброс

Изменения в 86box.cfg пропали

Правили при запущенном 86Box

Править только при закрытом

Экран чёрный, «Монитор в спящем режиме»

Windows 98 погасила монитор

Shift в окне; насовсем — Turn off monitor = Never

Играет музыка, хотя ничего не запущено

Открыто окно Welcome to Windows 98

Закрыть и снять галочку

Вторая копия программы падает с illegal operation

Две копии одной старой программы мешают друг другу

Закрыть первую копию или перезагрузить

В Run не печатаются русские буквы

В английской Windows 98 нет русской раскладки

Пути латиницей; раскладка — Control Panel → Keyboard

Мышь «застряла» в окне

Курсор захвачен машиной

Ctrl+End или средняя кнопка

Бонус: пусть установку сделает ИИ‑агент

Всю эту установку можно отдать ИИ‑агенту с доступом к PowerShell, например Claude Code. Для этого у меня есть один самодостаточный файл AGENT_ALL_IN_ONE.md: в нём сценарий экран за экраном, конфиг машины, батник и вспомогательные скрипты (снимок только окна 86Box через PrintWindow, отправка нажатий с проверкой фокуса, клики, корректное закрытие VM, запуск записи Game Bar). Человеку остаётся положить свой ISO, дать ключ и пару раз подтвердить действия.

В сценарий заложены правила безопасности для агента: сам Windows 98 он не скачивает, снимает только окно эмулятора (а не весь экран с вашими переписками), не жмёт клавиши, если окно 86Box не в фокусе, и предупреждает перед тем, как взять управление мышью и клавиатурой.

Сохраните текст ниже в формате md и дайте агенту. Я бы его приложил к статье файлом, но тут так нельзя, поэтому вот:

AGENT_ALL_IN_ONE.md — инструкция для ИИ‑агента (целиком)
# Инструкция для ИИ-агента: Windows 98 SE в 86Box на Windows 11

Этот файл — сценарий для ИИ-агента с доступом к PowerShell на компьютере пользователя (например, Claude Code). По нему агент сам ставит Windows 98 SE в эмулятор 86Box, запускает в ней старую программу и записывает экран. Человеку остаётся скачать образ Windows 98 и пару раз подтвердить действия.

Всё здесь проверено на практике 21.10.2026: Windows 11 Pro 26200, PowerShell 5.1, 86Box 6.0 (build 9001), Windows 98 SE English (4.10.2222A). Тексты экранов и сочетания клавиш взяты из этой установки.

**Файл самодостаточный.** Все вспомогательные скрипты, конфигурация машины и батник лежат в **приложении А** в конце. Первым делом создай папку проекта и вынь их оттуда одной командой (раздел А.0). Других файлов, кроме этого `.md`, не нужно.

---

## 0. Правила для агента (обязательно)

1. **Перед любым перехватом клавиатуры или мыши предупреди пользователя** одной строкой: «Беру управление примерно на N минут, не трогайте мышь и клавиатуру». Когда закончишь, скажи «Готово, компьютер ваш». Нажатия уходят в активное окно: если пользователь работает в другой программе, нажатия попадут туда.
2. **Сам Windows 98 не скачивай.** Это проприетарная ОС, её копии в интернете распространяются без разрешения Microsoft. Попроси пользователя положить ISO в `iso\` (его собственный диск или образ, который он сам нашёл). Ключ продукта тоже должен дать пользователь.
3. **Снимай только окно 86Box** (`snap.ps1`, через PrintWindow). Никогда не снимай весь экран: на нём личные переписки и документы пользователя.
4. **Не отправляй нажатия без проверки фокуса.** `keys.ps1` сам отказывается работать, если окно 86Box не активно (`WARN: 86Box window is not in focus`). Не обходи это ключом `-NoFocus`. Попроси пользователя щёлкнуть по заголовку окна 86Box или получи разрешение и щёлкни сам через `click.ps1 -X 300 -Y 15` (заголовок окна).
5. **После каждого действия делай снимок и смотри на него**, прежде чем жать дальше. Не отправляй длинные цепочки нажатий вслепую.
6. **Долгие ожидания запускай в фоне** (Start-Sleep N; затем snap.ps1). Не блокируй сессию на минуты.
7. **Ничего не удаляй вне папки проекта** без явного разрешения. В том числе файлы в `Видео\Captures`.
8. Разговаривай с пользователем на его языке, коротко сообщай, на каком ты этапе.

---

## 1. Подготовка папки проекта

Структура (корень — любой, например `C:\Users\<user>\Desktop\win98`):

```
<root>\
├─ 86box\            86Box.exe + roms\
├─ vm\win98\         86box.cfg, win98.img, nvr\
├─ iso\              win98se.iso, win98se_boot_nojo.img, образы с файлами
├─ scripts\          скрипты из приложения А
├─ shots\            снимки окна, которые делает агент
└─ start_win98.bat
```

### 1.1. 86Box и ROM-файлы

```powershell
$root = "<root>"; $ProgressPreference = 'SilentlyContinue'
$r = Invoke-RestMethod "https://api.github.com/repos/86Box/86Box/releases/latest" -Headers @{ 'User-Agent'='ps' }
$a = $r.assets | ? { $_.name -like '86Box-Windows-64-*.zip' } | Select-Object -First 1
New-Item -ItemType Directory -Force "$root\_dl" | Out-Null
Invoke-WebRequest $a.browser_download_url -OutFile "$root\_dl\$($a.name)" -UseBasicParsing
# Сверить SHA-256 с полем digest релиза:
(Get-FileHash "$root\_dl\$($a.name)").Hash.ToLower() -eq $a.digest.Replace('sha256:','')
Expand-Archive "$root\_dl\$($a.name)" "$root\86box" -Force
# ROM-файлы той же версии (тег = $r.tag_name, например v6.0):
Invoke-WebRequest "https://github.com/86Box/roms/archive/refs/tags/$($r.tag\_name).zip" -OutFile "$root\_dl\roms.zip" -UseBasicParsing
Expand-Archive "$root\_dl\roms.zip" "$root\_dl\roms_tmp" -Force
Move-Item (Get-ChildItem "$root\_dl\roms_tmp" -Directory)[0].FullName "$root\86box\roms"
Get-AuthenticodeSignature "$root\86box\86Box.exe"   # Valid, CN=Open Source Developer Miran Grča
```

Проверка: в `86box\roms\` есть папки `machines`, `video`, `sound`.

### 1.2. Машина

1. Убедись, что `<root>\vm\win98\86box.cfg` и `<root>\start_win98.bat` созданы из приложения А (раздел А.0).
2. Создай пустой диск ровно на 63×16×4161×512 байт:
   ```powershell
   fsutil file createnew "<root>\vm\win98\win98.img" 2147475456
   ```
3. Перед использованием проверь, что внутренние имена из cfg существуют в установленной версии 86Box (в новых версиях они могут поменяться). Поищи строки `\0p55t2p4\0`, `\0pentium_p55c\0`, `\0sb16_pnp\0`, `\0stealth3d_2000_pci\0` в байтах `86Box.exe`. После первого запуска 86Box перепишет cfg: если устройство пропало или сменилось на другое, значит, имя не подошло.

### 1.3. Образ Windows 98 и загрузочная дискета

1. Попроси пользователя положить ISO в `iso\` (если он в ZIP, распакуй его в `iso\`, читая только нужную запись архива). Переименуй в `win98se.iso`. Ключ часто лежит рядом в `Serial.txt`.
2. Проверь образ, не монтируя его:
   - сектор 16: `CD001`, метка тома (у SE — `WIN98 SE`, дата 1999-04-23);
   - сектор 17: `EL TORITO SPECIFICATION` — образ загрузочный;
   - в корне есть папка `WIN98` с ~77 CAB-файлами и `SETUP.EXE`.
3. Сделай загрузочную дискету без `JO.SYS`:
   ```powershell
   cd <root>; powershell -ExecutionPolicy Bypass -File .\scripts\make_bootdisk.ps1 -Iso .\iso\win98se.iso -Out .\iso\win98se_boot_nojo.img
   ```
   **Почему:** встроенная в диск дискета начинается с загрузчика `JO.SYS` (меню «Boot from Hard Disk / Boot from CD-ROM»). В 86Box пункт «Boot from CD-ROM» навсегда виснет на чёрном экране с курсором. Без `JO.SYS` дискета сразу показывает «Microsoft Windows 98 Startup Menu» и ставит Windows с CD через свои драйверы.

---

## 2. Вспомогательные скрипты (`scripts\`)

Тексты всех скриптов лежат в приложении А. Все скрипты ищут окно процесса `86Box`. Запускай их через `powershell -NoProfile -File` (snap.ps1, click.ps1) или напрямую через `&` (keys.ps1).

| Скрипт | Что делает | Пример |
| --- | --- | --- |
| `snap.ps1` | Снимок окна 86Box через PrintWindow (DPI-aware). Работает, даже если окно перекрыто, но не когда оно свёрнуто | `powershell -NoProfile -File .\scripts\snap.ps1 -Out .\shots\s01.png` |
| `keys.ps1` | Нажатия в машину сканкодами. Сам выводит 86Box на передний план, а если не вышло — отказывается | `& .\scripts\keys.ps1 -Keys 'DOWN','ENTER' -DelayMs 300` |
| `click.ps1` | Клик в координатах относительно окна (физические пиксели) | `powershell -NoProfile -File .\scripts\click.ps1 -X 300 -Y 15` |
| `closevm.ps1` | Штатно закрывает 86Box и подтверждает выход Enter в диалоге «86Box». Сохраняются nvr и cfg | `powershell -NoProfile -File .\scripts\closevm.ps1` |
| `enum.ps1` | Список видимых окон процесса 86Box (найти диалоги) | `powershell -NoProfile -File .\scripts\enum.ps1` |
| `make_bootdisk.ps1` | Дискета из ISO без `JO.SYS` | см. 1.3 |
| `make_iso.ps1` | ISO (ISO 9660 + Joliet) из папки встроенным IMAPI2 | `... -Folder .\cd_files -Out .\iso\files.iso -Label FILES` |
| `winaltr.ps1` | Нажимает Win+Alt+R (старт/стоп записи Xbox Game Bar) | см. раздел 6 |

**Токены `keys.ps1`:**

- отдельные клавиши: `ENTER ESC TAB BKSP F1 F2 F3 F4 F5 F8 F10 LSHIFT LALT UP DOWN LEFT RIGHT DEL NDEL PGUP PGDN`, буквы `A`–`Z`, цифры, `' '` (пробел);
- `TEXT:строка` — набрать текст. Поддерживаются латиница, цифры и `: . \ / , _ -`, пробел. Других символов в таблице нет: допиши их сканкоды в `$map`, прежде чем использовать;
- `ALT+X`, `CTRL+X` (например, `CTRL+ESC` открывает меню Start внутри Windows 98), `ALTTAB`;
- `WAIT:мс` — пауза.

**Проверенные ограничения:**

- `click.ps1` по меню и панели 86Box попадает неточно: однажды вместо «Жёсткий сброс» нажалась пауза, а вместо «Носители» открылся «Вид». Для меню 86Box не используй его. Надёжный путь — закрыть 86Box (`closevm.ps1`), поправить `86box.cfg` и запустить заново.
- Пока открыто меню 86Box, у процесса меняется MainWindowHandle, и `snap.ps1` снимает само меню.

---

## 3. Установка: экран за экраном

Время — ориентир для фоновых ожиданий. Номера снимков — эталонные скриншоты в `guide_screenshots\` (если они переданы вместе с гайдом).

### 3.1. Первый запуск и BIOS (нужно поймать Delete)

Запусти машину и сразу, примерно через 1,5 с, начни жать Delete:

```powershell
Start-Process "<root>\86box\86Box.exe" -ArgumentList '-P','"<root>\vm\win98"'
Start-Sleep -Milliseconds 1500
& .\scripts\keys.ps1 -Keys (@('NDEL','WAIT:150','DEL','WAIT:150') * 40) -DelayMs 30
```

Ожидаемо: синий «ROM PCI/ISA BIOS (PI55T2P4) CMOS SETUP UTILITY» (снимок 03). Если вместо него Startup Menu или POST уже прошёл — закрой 86Box (`closevm.ps1`) и повтори. «CMOS checksum error – Defaults loaded» при первом старте — норма.

В BIOS:

| Действие | Клавиши |
| --- | --- |
| Открыть BIOS FEATURES SETUP | `DOWN`, `ENTER` |
| Перейти на Boot Sequence (6-я строка) | `DOWN` ×5 |
| C,A → A,CDROM,C | `PGDN` (один раз; проверь снимком) |
| Сохранить и выйти | `ESC`, `F10`, `Y`, `ENTER` |

**Зачем:** после разметки диска установщик перезагружается, и с порядком C,A машина остановится на неотформатированном C:.

### 3.2. Текстовая часть установки (≈10 мин)

| Экран | Клавиши | Ждать |
| --- | --- | --- |
| Microsoft Windows 98 Startup Menu (снимок 06) | `ENTER` (пункт 1; через 10 с выберется сам) | ~40 с, мелькают строки Adaptec ASPI |
| Welcome to Setup | `ENTER` | |
| Configure unallocated disk space | `ENTER` | |
| Enable large disk support? (по умолчанию выделено No) | `DOWN`, `ENTER` | |
| Setup will restart… Boot Disk in drive A | `ENTER` | ~30 с |
| Startup Menu снова | `ENTER` | ~45 с |
| Formatting drive C | — | ~5 мин, фоновые проверки снимком раз в 2 мин |
| Setup is now going to perform a routine check | `ENTER` | ~40 с, затем графический мастер |

### 3.3. Графический мастер (≈25 мин, из них ~20 — копирование)

| Экран | Клавиши |
| --- | --- |
| Welcome to Windows 98 Setup | `ENTER` (Continue), ждать ~45 с |
| Select Directory (C:\WINDOWS) | `ENTER` |
| Setup Options (Typical) | `ENTER` |
| Windows Components | `ENTER` |
| Identification (имя выделено) | `TEXT:DENGI98`, `ENTER` (до 15 символов латиницей) |
| Establishing Your Location | `ENTER` (страна неважна) |
| Start Copying Files | `ENTER` |
| Копирование, «File copy progress» | ждать ~20 мин в фоне |

Если спросят про создание Startup Disk — `ESC` или Cancel (у нас вопрос не появился).

**Дискету нужно убрать до перезагрузки после копирования.** 86Box держит образ дискеты в памяти: если испортить или заменить файл на диске, он этого не заметит (проверено). Варианты:

- **попросить пользователя** во время копирования выбрать в 86Box «Носители» → «Дисковод 1 (3.5" 1.44M)…» → «Извлечь»;
- **или** дождаться перезагрузки. Если появился Startup Menu, не нажимай ничего: через 10 с пункт 1 выберется сам и откроется Welcome to Setup, который ждёт Enter и ничего не трогает. Затем `closevm.ps1` → удали строку `fdd_01_fn = …` из `86box.cfg` → запусти машину снова. BIOS перейдёт к CD, меню CD через 10 с само выберет «Boot from Hard Disk», и загрузится установленная Windows (снимок 21, «Getting ready to run Windows for the first time»).

### 3.4. Первая загрузка (≈20 мин)

| Экран | Клавиши |
| --- | --- |
| User Information | `TEXT:User`, `ENTER` |
| License Agreement | `ALT+A`, `ENTER` |
| Windows Product Key | `TEXT:<25 символов без дефисов>` → **снимок, сверить** → `ENTER` |
| Start Wizard | `ENTER` (Finish) |
| Hardware Detection → Setting up Hardware | ждать ~5–8 мин в фоне |
| Date/Time Properties | `ENTER` (Close; время уже взято с хоста) |
| Updating System Settings, перезагрузка | ждать ~5 мин |
| Enter Windows Password | `ENTER` (пустой пароль — больше не спросит) |
| Add New Hardware Wizard (Plug and Play Monitor) | `ENTER`, `ENTER`, `' '` (снять Floppy disk drives), `ENTER`, `ENTER`, `ENTER` (Finish) — снимок после каждого шага |
| Welcome to Windows 98 | `ALT+O` (снять «Show this screen…»), `ESC` |

Ключ — данные пользователя: не выводи его в чат без нужды и не оставляй на снимках, которые пойдут в публикацию.

---

## 4. Настройка после установки (≈3 мин)

```powershell
# Не гасить монитор (иначе через 15 минут запись станет чёрной: «Монитор в спящем режиме»)
& .\scripts\keys.ps1 -Keys 'CTRL+ESC','WAIT:1200','R','WAIT:1800','TEXT:control powercfg.cpl','WAIT:400','ENTER'
# снимок → поле Turn off monitor:
& .\scripts\keys.ps1 -Keys 'ALT+M','WAIT:400','N','WAIT:400','ENTER' -DelayMs 250
# Проверить заставку (должно быть (None)):
& .\scripts\keys.ps1 -Keys 'CTRL+ESC','WAIT:1500','R','WAIT:2000','TEXT:control desk.cpl,,1','WAIT:400','ENTER'
# снимок → ESC
```

Если меню Start не открылось, увеличь паузы: после закрытия окон панели управления нужна ~1,5 с.

**Выключение Windows 98:** на рабочем столе (сначала закрой окна) `ALT+F4` → диалог Shut Down Windows. Позиция по умолчанию бывает разной (Stand by / Shut down) — **сними снимок** и выбери Shut down стрелками → `ENTER` → «It's now safe to turn off your computer» → `closevm.ps1`.

**Звук:** проверка — Control Panel → Sounds → Preview. Окно Welcome to Windows 98 само играет фоновую музыку — не путай её со звуком программы.

---

## 5. Передача файлов и запуск программы

1. Сложи файлы в отдельную папку и собери ISO:
   ```powershell
   powershell -ExecutionPolicy Bypass -File .\scripts\make_iso.ps1 -Folder .\cd_files -Out .\iso\files.iso -Label FILES
   ```
2. Выключи Windows 98 (раздел 4), выполни `closevm.ps1`, в `86box.cfg` замени строку на `cdrom_01_image_path = ../../iso/files.iso`, запусти машину. Образ без загрузки → BIOS загрузит C:.
3. Внутри Windows 98 диск — `D:`. Запуск: `CTRL+ESC`, `R`, `TEXT:d:\program.exe`, `ENTER`.
4. Не запускай второй экземпляр той же старой программы, пока открыт первый. Два плеера Lotus ScreenCam падали с «This program has performed an illegal operation». Если такое случилось — перезагрузи Windows 98.

Пример, на котором всё проверено: демо «1С:Деньги» 1998 года (Lotus ScreenCam 97, 22 мин, 640×480, **без звуковой дорожки** — так записан оригинал).

- `https://1c.ru/ftp/pub/demo/1c/d77.zip` — работает: плеер `SCPLAYER.EXE` и 7 разделов `D20_*.SCM`.
- `dengir.exe` (тот же ролик одним файлом) с 1c.ru отдаёт 404. Копия в веб-архиве: `https://web.archive.org/web/20010418081352if_/http://www.1c.ru:80/ftp/pub/demo/1c/dengir.exe`.
- Перед запуском любых скачанных `.exe` проверь их, не запуская: SHA-256, подпись, PE-заголовок, VersionInfo, метку источника (`Zone.Identifier`).

---

## 6. Запись экрана в MP4 (Xbox Game Bar)

1. Выведи 86Box на передний план (`keys.ps1 -Keys 'WAIT:200'`), затем запусти `powershell -NoProfile -File .\scripts\winaltr.ps1` — запись началась.
2. **Проверка, что запись идёт, — только по индикатору с таймером на экране** (попроси пользователя посмотреть). Файл в `Видео\Captures` во время записи не растёт (~800 КБ, без длительности): Game Bar пишет видео целиком при остановке.
3. Запусти программу внутри машины. Пока идёт запись, не сворачивай окно 86Box и ничего не нажимай в нём.
4. Остановка: попроси пользователя нажать ■ на индикаторе. Если будешь нажимать `winaltr.ps1` сам, помни: это **переключатель**. Если запись уже остановлена, он начнёт новую. Так у нас появились лишние файлы на 4 и 18 секунд.
5. Проверь результат по свойствам файла:
   ```powershell
   $cap = Join-Path ([Environment]::GetFolderPath('MyVideos')) 'Captures'; $sh = New-Object -ComObject Shell.Application; $fo = $sh.Namespace($cap)
   Get-ChildItem $cap -File | sort CreationTime -Desc | select -First 3 | % { "{0} | {1}" -f $_.Name, $fo.GetDetailsOf($fo.ParseName($_.Name), 27) }
   ```
   У нас 22-минутный ролик дал файл 23:55, 164 МБ.

---

## 7. Известные грабли — коротко

| Симптом | Причина | Что делать |
| --- | --- | --- |
| Чёрный экран с курсором после «Boot from CD-ROM» | `JO.SYS` | Дискета без `JO.SYS` (1.3) |
| DISK BOOT FAILURE | Нет дискеты / порядок C,A | Проверить `fdd_01_fn`, Boot Sequence A,CDROM,C |
| После копирования снова Startup Menu | Дискета вставлена | 3.3: извлечь (меню) или убрать `fdd_01_fn` и перезапустить |
| Правки `86box.cfg` пропали | 86Box перезаписывает cfg при выходе | Править только при закрытом 86Box |
| Настройки BIOS пропали | 86Box убит, а не закрыт | Закрывать через `closevm.ps1` |
| «Монитор в спящем режиме» | Power Management Windows 98 | Turn off monitor = Never; разбудить — `LSHIFT` |
| Музыка, которой «не должно быть» | Окно Welcome to Windows 98 | Закрыть, снять галочку |
| `keys.ps1`: WARN not in focus | Пользователь работает в другом окне | Попросить щёлкнуть по 86Box или получить разрешение и щёлкнуть заголовок |
| Символ не напечатался в `TEXT:` | Нет в таблице сканкодов | Добавить в `$map` (например, `'_'=0x0C` с Shift, `'/'=0x35`, `','=0x33`) |
| Старая программа на Windows 11 / в Песочнице Windows виснет до нажатия клавиши, без звука | 16-битный код (flat thunk), которого нет в 64-битной Windows | Запускать в Windows 98 в 86Box |
| Game Bar «не пишет» (файл не растёт) | Так и задумано, файл пишется при остановке | Смотреть на таймер индикатора |

---

## 8. Итог для пользователя

В конце сообщи пользователю:

- где лежит машина и как её запускать (`start_win98.bat`);
- как передавать файлы (ISO + `cdrom_01_image_path` или «Носители» → «CD-ROM 1» → «Образ…»);
- что мышь захватывается щелчком и освобождается Ctrl+End;
- где лежит запись, если её делали;
- что можно удалить: временные загрузки в `_dl\`, снимки в `shots\`.

---

## Приложение А. Файлы проекта

Ниже вложены все файлы, которые нужны по сценарию. Перед каждым стоит пометка `<!-- file: относительный\путь -->`, а под ней — блок кода с содержимым. Все файлы в чистом ASCII.

### А.0. Распаковка одной командой

1. Создай папку проекта `<root>` и положи этот `.md` рядом или внутрь неё.
2. Выполни в PowerShell (подставь пути):

```powershell
$md = '<путь к этому файлу>.md'; $root = '<root>'
$text = [IO.File]::ReadAllText($md, [Text.Encoding]::UTF8)
$rx = [regex]'(?s)<!-- file: (?<path>[^>]+?) -->\r?\n```[a-z]*\r?\n(?<body>.*?)\r?\n```'
foreach ($m in $rx.Matches($text)) {
  $dst = Join-Path $root $m.Groups['path'].Value.Trim()
  New-Item -ItemType Directory -Force (Split-Path $dst) | Out-Null
  [IO.File]::WriteAllText($dst, $m.Groups['body'].Value + "`r`n", [Text.Encoding]::ASCII)
  "written: $dst"
}
```

Должно появиться 10 файлов: `start_win98.bat`, `vm\win98\86box.cfg` и 8 скриптов в `scripts\`. Если меньше — регулярное выражение не нашло блок: проверь, что при копировании этого файла не потерялись переводы строк.

Если команду выполнить нельзя, создай файлы вручную: имя файла указано в пометке над каждым блоком, а содержимое — это блок целиком.

### А.1. `start_win98.bat`

Запуск машины: 86Box с папкой настроек vm\win98.

<!-- file: start_win98.bat -->
```bat
@echo off
start "" "%~dp086box\86Box.exe" -P "%~dp0vm\win98"
```

### А.2. `vm\win98\86box.cfg`

Конфигурация машины (раздел 1.2). Пути к дискете и ISO — относительно папки машины.

<!-- file: vm\win98\86box.cfg -->
```ini
[General]
vid_renderer = qt_software

[Machine]
machine = p55t2p4
cpu_family = pentium_p55c
cpu_speed = 200000000
cpu_multi = 3
cpu_use_dynarec = 1
mem_size = 65536

[Video]
gfxcard = virge_pci

[S3 ViRGE PCI]
bios = stealth3d_2000_pci

[Input devices]
mouse_type = ps2

[Sound]
sndcard = sb16_pnp

[Hard disks]
hdd_01_parameters = 63, 16, 4161, 0, ide
hdd_01_fn = win98.img
hdd_01_ide_channel = 0:0

[Floppy and CD-ROM drives]
fdd_01_type = 35_2hd
fdd_02_type = none
fdd_01_fn = ../../iso/win98se_boot_nojo.img
cdrom_01_parameters = 1, atapi
cdrom_01_ide_channel = 1:0
cdrom_01_speed = 24
cdrom_01_image_path = ../../iso/win98se.iso
```

### А.3. `scripts\snap.ps1`

Снимок окна 86Box в PNG через PrintWindow. Параметры: -Out путь, -Handle (необязательно, снять конкретное окно).

<!-- file: scripts\snap.ps1 -->
```powershell
param([string]$Out)
Add-Type -AssemblyName System.Drawing
Add-Type @"
using System; using System.Runtime.InteropServices;
public static class W { [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
 [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr dc, uint f);
 [DllImport("user32.dll")] public static extern bool SetProcessDPIAware();
 [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; } }
"@
[void][W]::SetProcessDPIAware()
$p = Get-Process 86Box -ErrorAction Stop | ? { $_.MainWindowHandle -ne 0 } | Select-Object -First 1
$r = New-Object W+RECT; [void][W]::GetWindowRect($p.MainWindowHandle, [ref]$r)
$bmp = New-Object Drawing.Bitmap ($r.R-$r.L), ($r.B-$r.T); $g=[Drawing.Graphics]::FromImage($bmp); $dc=$g.GetHdc()
[void][W]::PrintWindow($p.MainWindowHandle, $dc, 2); $g.ReleaseHdc($dc); $bmp.Save($Out, [Drawing.Imaging.ImageFormat]::Png); $g.Dispose(); $bmp.Dispose()
"saved $Out ($($r.R-$r.L)x$($r.B-$r.T)) title='$($p.MainWindowTitle)'"
```

### А.4. `scripts\keys.ps1`

Нажатия в машину сканкодами (токены — раздел 2). Сам выводит 86Box на передний план, а если не вышло — пишет WARN и ничего не нажимает.

<!-- file: scripts\keys.ps1 -->
```powershell
param([string[]]$Keys, [int]$DelayMs = 150, [switch]$NoFocus)
Add-Type @"
using System; using System.Runtime.InteropServices;
public static class K {
 [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte sc, uint f, UIntPtr e);
 [DllImport("user32.dll")] public static extern uint SendInput(uint n, INPUT[] i, int cb);
 [StructLayout(LayoutKind.Sequential)] public struct KEYBDINPUT { public ushort wVk, wScan; public uint dwFlags, time; public IntPtr extra; }
 [StructLayout(LayoutKind.Sequential)] public struct MOUSEINPUT { public int dx, dy; public uint data, flags, time; public IntPtr extra; }
 [StructLayout(LayoutKind.Explicit)] public struct U { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; }
 [StructLayout(LayoutKind.Sequential)] public struct INPUT { public uint type; public U u; }
 public static void Scan(ushort sc, bool ext, bool up) { var i = new INPUT[1]; i[0].type = 1; i[0].u.ki.wScan = sc; i[0].u.ki.dwFlags = 8u | (ext ? 1u : 0u) | (up ? 2u : 0u); SendInput(1, i, Marshal.SizeOf(typeof(INPUT))); }
}
"@
$map = @{ '1'=0x02;'2'=0x03;'3'=0x04;'4'=0x05;'5'=0x06;'6'=0x07;'7'=0x08;'8'=0x09;'9'=0x0A;'0'=0x0B;'-'=0x0C;
 'Q'=0x10;'W'=0x11;'E'=0x12;'R'=0x13;'T'=0x14;'Y'=0x15;'U'=0x16;'I'=0x17;'O'=0x18;'P'=0x19;'A'=0x1E;'S'=0x1F;'D'=0x20;'F'=0x21;'G'=0x22;'H'=0x23;'J'=0x24;'K'=0x25;'L'=0x26;
 'Z'=0x2C;'X'=0x2D;'C'=0x2E;'V'=0x2F;'B'=0x30;'N'=0x31;'M'=0x32;':'=0x27;'.'=0x34;'\'=0x2B;'/'=0x35;','=0x33;'_'=0x0C;' '=0x39;
 'ENTER'=0x1C;'ESC'=0x01;'TAB'=0x0F;'BKSP'=0x0E;'F1'=0x3B;'F2'=0x3C;'F3'=0x3D;'F4'=0x3E;'F5'=0x3F;'F8'=0x42;'F10'=0x44;'LSHIFT'=0x2A;'LALT'=0x38;'Y_'=0x15;'NDEL'=0x53 }
$ext = @{ 'UP'=0x48;'DOWN'=0x50;'LEFT'=0x4B;'RIGHT'=0x4D;'DEL'=0x53;'PGDN'=0x51;'PGUP'=0x49 }
$p = Get-Process 86Box | ? { $_.MainWindowHandle -ne 0 } | Select-Object -First 1; if (-not $NoFocus -and [K]::GetForegroundWindow() -ne $p.MainWindowHandle) { [K]::keybd_event(0x12,0,0,[UIntPtr]::Zero); [K]::keybd_event(0x12,0,2,[UIntPtr]::Zero); [void][K]::SetForegroundWindow($p.MainWindowHandle); Start-Sleep -Milliseconds 300 }; if (-not $NoFocus -and [K]::GetForegroundWindow() -ne $p.MainWindowHandle) { "WARN: 86Box window is not in focus"; return }
foreach ($k in $Keys) {
  if ($k -like 'TEXT:*') { foreach ($ch in $k.Substring(5).ToCharArray()) { $c=[string]$ch; $up=$c.ToUpper(); $shift = ($c -cne $c.ToLower()) -or $c -eq ':' -or $c -eq '_'; if ($c -eq ':') { $up=':' }; if ($c -eq '_') { $up='_' }; if ($shift) { [K]::Scan(0x2A,$false,$false) }; [K]::Scan([uint16]$map[$up],$false,$false); Start-Sleep -Milliseconds 40; [K]::Scan([uint16]$map[$up],$false,$true); if ($shift) { [K]::Scan(0x2A,$false,$true) }; Start-Sleep -Milliseconds 60 } ; continue }
  if ($k -like 'ALT+*') { $c=[uint16]$map[$k.Substring(4)]; [K]::Scan(0x38,$false,$false); Start-Sleep -Milliseconds 50; [K]::Scan($c,$false,$false); Start-Sleep -Milliseconds 50; [K]::Scan($c,$false,$true); [K]::Scan(0x38,$false,$true); Start-Sleep -Milliseconds $DelayMs; continue }
  if ($k -like 'CTRL+*') { $n=$k.Substring(5); if ($ext.ContainsKey($n)) { $c=[uint16]$ext[$n] } else { $c=[uint16]$map[$n] }; [K]::Scan(0x1D,$false,$false); Start-Sleep -Milliseconds 50; [K]::Scan($c,$false,$false); Start-Sleep -Milliseconds 50; [K]::Scan($c,$false,$true); [K]::Scan(0x1D,$false,$true); Start-Sleep -Milliseconds $DelayMs; continue }
  if ($k -eq 'ALTTAB') { [K]::Scan(0x38,$false,$false); Start-Sleep -Milliseconds 50; [K]::Scan(0x0F,$false,$false); Start-Sleep -Milliseconds 50; [K]::Scan(0x0F,$false,$true); Start-Sleep -Milliseconds 300; [K]::Scan(0x38,$false,$true); Start-Sleep -Milliseconds $DelayMs; continue }
  if ($k -like 'WAIT:*') { Start-Sleep -Milliseconds ([int]$k.Substring(5)); continue }
  if ($ext.ContainsKey($k)) { $sc=[uint16]$ext[$k]; $e=$true } else { $sc=[uint16]$map[$k]; $e=$false }
  [K]::Scan($sc,$e,$false); Start-Sleep -Milliseconds 50; [K]::Scan($sc,$e,$true); Start-Sleep -Milliseconds $DelayMs
}
"sent: $($Keys -join ' ')"
```

### А.5. `scripts\click.ps1`

Клик левой кнопкой по координатам относительно окна 86Box (физические пиксели). По меню 86Box попадает неточно.

<!-- file: scripts\click.ps1 -->
```powershell
param([int]$X,[int]$Y)
Add-Type @"
using System; using System.Runtime.InteropServices;
public static class M { [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r);
 [DllImport("user32.dll")] public static extern bool SetCursorPos(int x,int y); [DllImport("user32.dll")] public static extern void mouse_event(uint f,int x,int y,uint d,UIntPtr e);
 [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte sc, uint f, UIntPtr e);
 [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L,T,R,B; } }
"@
[void][M]::SetProcessDPIAware(); $p=Get-Process 86Box | ? { $_.MainWindowHandle -ne 0 } | Select-Object -First 1
[M]::keybd_event(0x12,0,0,[UIntPtr]::Zero); [M]::keybd_event(0x12,0,2,[UIntPtr]::Zero); [void][M]::SetForegroundWindow($p.MainWindowHandle); Start-Sleep -Milliseconds 200
$r=New-Object M+RECT; [void][M]::GetWindowRect($p.MainWindowHandle,[ref]$r); [void][M]::SetCursorPos($r.L+$X,$r.T+$Y); Start-Sleep -Milliseconds 100
[M]::mouse_event(2,0,0,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 60; [M]::mouse_event(4,0,0,0,[UIntPtr]::Zero); "clicked $X,$Y"
```

### А.6. `scripts\enum.ps1`

Список видимых окон процесса 86Box (например, чтобы найти диалог подтверждения выхода).

<!-- file: scripts\enum.ps1 -->
```powershell
Add-Type @"
using System; using System.Text; using System.Runtime.InteropServices; using System.Collections.Generic;
public static class E { public delegate bool CB(IntPtr h, IntPtr l);
 [DllImport("user32.dll")] public static extern bool EnumWindows(CB cb, IntPtr l); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
 [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
 public static List<string> List(uint pid) { var r = new List<string>(); EnumWindows((h,l)=>{ uint p; GetWindowThreadProcessId(h, out p); if (p==pid && IsWindowVisible(h)) { var sb=new StringBuilder(256); GetWindowText(h,sb,256); r.Add(h.ToString()+" '"+sb.ToString()+"'"); } return true; }, IntPtr.Zero); return r; } }
"@
$p=Get-Process 86Box | Select-Object -First 1; [E]::List([uint32]$p.Id)
```

### А.7. `scripts\closevm.ps1`

Штатное закрытие 86Box с подтверждением Enter в диалоге «86Box» (сохраняются nvr и cfg).

<!-- file: scripts\closevm.ps1 -->
```powershell
Add-Type @"
using System; using System.Text; using System.Runtime.InteropServices; using System.Collections.Generic;
public static class C { public delegate bool CB(IntPtr h, IntPtr l);
 [DllImport("user32.dll")] public static extern bool EnumWindows(CB cb, IntPtr l); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
 [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
 [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte sc, uint f, UIntPtr e);
 public static IntPtr FindTitled(uint pid, string title) { IntPtr found = IntPtr.Zero; EnumWindows((h,l)=>{ uint p; GetWindowThreadProcessId(h, out p); if (p==pid && IsWindowVisible(h)) { var sb=new StringBuilder(256); GetWindowText(h,sb,256); if (sb.ToString()==title) found=h; } return true; }, IntPtr.Zero); return found; } }
"@
$p=Get-Process 86Box -ErrorAction SilentlyContinue | Select-Object -First 1; if (-not $p) { "not running"; return }
[void]$p.CloseMainWindow(); Start-Sleep 2
$d=[C]::FindTitled([uint32]$p.Id,'86Box'); if ($d -ne [IntPtr]::Zero) { [C]::keybd_event(0x12,0,0,[UIntPtr]::Zero); [C]::keybd_event(0x12,0,2,[UIntPtr]::Zero); [void][C]::SetForegroundWindow($d); Start-Sleep -Milliseconds 400; if ([C]::GetForegroundWindow() -eq $d) { [C]::keybd_event(0x0D,0x1C,0,[UIntPtr]::Zero); [C]::keybd_event(0x0D,0x1C,2,[UIntPtr]::Zero) } else { "dialog not focused"; return } }
for ($i=0; $i -lt 10; $i++) { Start-Sleep 1; if (-not (Get-Process -Id $p.Id -ErrorAction SilentlyContinue)) { "86Box closed"; return } }; "still running"
```

### А.8. `scripts\winaltr.ps1`

Нажимает Win+Alt+R — старт/стоп записи Xbox Game Bar. Это переключатель.

<!-- file: scripts\winaltr.ps1 -->
```powershell
Add-Type @"
using System; using System.Runtime.InteropServices;
public static class GR { [DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte sc, uint f, UIntPtr e); }
"@
[GR]::keybd_event(0x5B,0x5B,1,[UIntPtr]::Zero); Start-Sleep -Milliseconds 60; [GR]::keybd_event(0x12,0x38,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 60
[GR]::keybd_event(0x52,0x13,0,[UIntPtr]::Zero); Start-Sleep -Milliseconds 80; [GR]::keybd_event(0x52,0x13,2,[UIntPtr]::Zero); Start-Sleep -Milliseconds 40
[GR]::keybd_event(0x12,0x38,2,[UIntPtr]::Zero); Start-Sleep -Milliseconds 40; [GR]::keybd_event(0x5B,0x5B,3,[UIntPtr]::Zero); "Win+Alt+R sent"
```

### А.9. `scripts\make_bootdisk.ps1`

Дискета 1,44 МБ из загрузочного ISO Windows 98 без JO.SYS. ISO не изменяет.

<!-- file: scripts\make_bootdisk.ps1 -->
```powershell
param([string]$Iso, [string]$Out)
$fs = [IO.File]::OpenRead((Resolve-Path $Iso)); $b = New-Object byte[] 2048
$fs.Position = 17 * 2048; [void]$fs.Read($b, 0, 2048)
if ([Text.Encoding]::ASCII.GetString($b, 7, 23) -ne 'EL TORITO SPECIFICATION') { throw 'not bootable' }
$fs.Position = [int64][BitConverter]::ToUInt32($b, 0x47) * 2048; [void]$fs.Read($b, 0, 2048)
if ($b[33] -ne 2) { throw 'not 1.44 floppy emulation' }
$img = New-Object byte[] 1474560
$fs.Position = [int64][BitConverter]::ToUInt32($b, 40) * 2048; [void]$fs.Read($img, 0, $img.Length); $fs.Close()
$bps = [BitConverter]::ToUInt16($img, 11); $res = [BitConverter]::ToUInt16($img, 14)
$root = ($res + $img[16] * [BitConverter]::ToUInt16($img, 22)) * $bps; $entries = [BitConverter]::ToUInt16($img, 17)
for ($i = 0; $i -lt $entries; $i++) { $o = $root + 32 * $i
  if ([Text.Encoding]::ASCII.GetString($img, $o, 11) -eq 'JO      SYS') { $img[$o] = 0xE5; 'JO.SYS removed' } }
[IO.File]::WriteAllBytes((Join-Path (Get-Location) $Out), $img); "Done: $Out"
```

### А.10. `scripts\make_iso.ps1`

ISO (ISO 9660 + Joliet) из папки встроенным IMAPI2. Параметры: -Folder, -Out, -Label.

<!-- file: scripts\make_iso.ps1 -->
```powershell
param([string]$Folder, [string]$Out, [string]$Label = 'FILES')
Add-Type -TypeDefinition @"
using System; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes;
public static class IsoWriter { public static void Save(object stream, string path) {
  IStream s = (IStream)stream; byte[] buf = new byte[65536]; IntPtr pRead = Marshal.AllocHGlobal(4);
  using (FileStream f = File.Create(path)) { while (true) { s.Read(buf, buf.Length, pRead); int n = Marshal.ReadInt32(pRead); if (n <= 0) break; f.Write(buf, 0, n); } }
  Marshal.FreeHGlobal(pRead); } }
"@
$fs = New-Object -ComObject IMAPI2FS.MsftFileSystemImage
$fs.FileSystemsToCreate = 3   # ISO 9660 + Joliet
$fs.VolumeName = $Label
$fs.Root.AddTree((Resolve-Path $Folder).Path, $false)
$outPath = if ([IO.Path]::IsPathRooted($Out)) { $Out } else { Join-Path (Get-Location) $Out }
[IsoWriter]::Save($fs.CreateResultImage().ImageStream, $outPath)
"Done: $outPath"
```

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.