Старые программы конца 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) |
Эмулятор старого ПК |
60 МБ |
|
ROM‑файлы 86Box той же версии |
Прошивки BIOS плат и видеокарт |
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 не устанавливается: два архива распаковываются в одну папку, в систему и реестр ничего не пишется. Удалить всё — значит стереть папку.
Создайте папку проекта, например
C:\Users\<вы>\Desktop\win98.Скачайте с последнего релиза 86Box архив
86Box-Windows-64-b….zip(для 6.0 —86Box-Windows-64-b9001.zip) и распакуйте вwin98\86box\.В релизах ROM‑файлов скачайте архив той же версии (для 6.0 — тег
v6.0, «Source code (zip)»). Теги сpreиbetaне берите.Папку
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 через свои драйверы.
Положите образ в
win98\iso\под именемwin98se.iso. Файл с ключом (частоSerial.txt) сохраните.Сохраните скрипт ниже как
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 или средняя кнопка мыши.
Запустите
start_win98.bat. Появится POST: Pentium‑MMX 200 МГц, 65 536 КБ, Creative SB16 PnP, диск и CD. «CMOS checksum error — Defaults loaded» при первом запуске — норма.

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

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

Esc, F10, Y, Enter — настройки сохранятся, машина загрузится с дискеты.
Настройки BIOS лежат в vm\win98\nvr. Чтобы они не терялись, закрывайте 86Box крестиком с подтверждением, а не через диспетчер задач.
Шаг 5. Текстовая часть установки
Установщик сам размечает и форматирует диск, FDISK вручную не нужен. Около 10 минут, из них 5 — форматирование. Всё происходит внутри win98.img, реальный диск не затрагивается.
В Startup Menu выберите 1. Start Windows 98 Setup from CD‑ROM (через 10 секунд выберется сам). Строки Adaptec ASPI Manager — это грузятся драйверы CD.
Welcome to Setup → Enter.
Configure unallocated disk space (recommended) → Enter.
На вопрос про large disk support стрелкой вниз выберите Yes, enable large disk support — будет FAT32. Внимание: по умолчанию выделено «No».
Запрос на перезагрузку с дискетой в A: — дискета уже вставлена, Enter.
Снова Startup Menu → 1. Пойдёт форматирование C: (около 5 минут).
«Setup is now going to perform a routine check on your system» → Enter. ScanDisk за полминуты, затем графическая часть.
Шаг 6. Мастер установки
Везде подходят варианты по умолчанию, всё делается клавишей Enter.
Экран |
Что сделать |
|---|---|
Welcome to Windows 98 Setup |
Continue |
Select Directory |
Оставить |
Setup Options |
Typical, Next |
Windows Components |
Install the most common components, Next |
Identification |
Имя компьютера латиницей до 15 символов (например, |
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 — вы забыли извлечь дискету.
User Information — любое имя, например
User.License Agreement — I accept the Agreement (Alt+A).
Windows Product Key — 25 символов без дефисов, курсор сам перескакивает. Ключ должен подходить к диску: полная версия, Upgrade и OEM не всегда взаимозаменяемы.
Start Wizard → Finish.
Hardware Detection — около 5 минут. Предупреждение про «may cause your computer to stop responding» пишется всегда; у нас ничего не зависло. Ничего не нажимайте.
Date/Time Properties — время уже взято с хоста, выберите часовой пояс и Close.
«Updating System Settings» — ещё около 5 минут и перезагрузка.
Enter Windows Password — оставьте пустым, OK. Больше пароль спрашивать не будут.
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 МБ.
Щёлкните по заголовку окна 86Box и нажмите Win+Alt+R — появится индикатор с таймером.
Запустите видео или программу внутри Windows 98 и не сворачивайте окно до конца записи. Если картинка мелкая, перед записью увеличьте окно: «Вид» → масштаб ×2.
Остановите запись кнопкой ■ на индикаторе. Файл появится в
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» чёрный экран с курсором |
|
Грузиться с дискеты без |
DISK BOOT FAILURE, INSERT SYSTEM DISK |
Дискета не вставлена или BIOS её не пробует |
Проверить |
Не удаётся попасть в BIOS |
Delete нажата поздно или окно не активно |
Щёлкнуть по заголовку 86Box, жёсткий сброс, сразу жать Delete |
Установка начинается заново после копирования |
В приводе осталась дискета |
«Носители» → «Дисковод 1» → «Извлечь», жёсткий сброс |
Изменения в |
Правили при запущенном 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" ```
bob_smith
Это прекрасно, но поделитесь: какой софт Вы запускаете на этой машине?
etotttt Автор
Конечно же 1С:Деньги версии 1.2 и 2.5, а что же еще? Также было интересно посмотреть на демонстрационный ролик прессрелиза https://1c.ru/rus/products/1c/dengi/1cdengi.htm, он там спрятан в кнопке "1С:Деньги 2.5 "