Staré hry Čtyřlístku na tabletu
Obsah článku
V březnu jsem psal, jak jsem pro syna rozběhal staré hry se Čtyřlístkem ve Wine v Dockeru. Hrát se to dalo, ale nebylo to ono. Od té doby Claude Opus rozebral původní spustitelné soubory her a napsal pro ně nový engine v TypeScriptu. Teď běží všech osmnáct dílů v prohlížeči i jako aplikace na tabletu s Androidem. Syn si zatím zahrál dvě hry a moc ho bavily.
Jak jsem se sem dostal
Hry jsou z let 2001 až 2005 a potřebují Windows s DirectX 7. Postupně jsem zkoušel tohle:
- Windows 11 – hry se nainstalovaly, ale při spuštění padaly i v režimech kompatibility.
- Windows XP v UTM – po hodině a půl instalace chyběla podpora DirectX.
v86v prohlížeči – Wine v emulovaném Linuxu padalo dřív, než se hra vůbec spustila.- Wine v Dockeru – fungovalo, obraz a zvuk se do prohlížeče přenášely přes noVNC a WebSocket. O tom byl březnový článek.
- Vlastní engine v prohlížeči – dnešní stav, o kterém je tenhle článek.
První pokusy, jak hry spustit
Řešení s Wine fungovalo, ale mělo slabiny, které se odladit nedaly:
- Obraz přes VNC se trhal, při zátěži se zobrazovalo jen něco přes 20 snímků za sekundu.
- Zvuk šel vlastní cestou přes WebSocket a měl zpoždění.
- Videa v menu shazovala hru, takže jsem je musel vypnout úpravou exe.
- Na Macu s Apple Silicon běžel image pro i386 v emulaci a spuštění trvalo kolem půl hodiny.
Samotné hry jsou jednoduché. Engine CPAL2 od Centauri Production je čistě 2D – místnosti z obrázků, postavy chodí po polygonech, k tomu rozhovory a inventář. Emulovat kvůli tomu celé Windows mi přišlo zbytečné, a tak jsem se rozhodl engine napsat znovu. Přesně jsem věděl, co a jak udělat, jen jsem na to neměl tolik času a jsem trochu líný. Takže jsem Opusovi říkal, co a jak dělat, a on provedl reverse engineering a napsal nový engine.
Formáty dat
Kromě videí je všechno v archivech .res (Resource File 2.01). Na začátku je hlavička, za ní nekomprimovaná data jednotlivých souborů a na konci adresář se záznamem o 92 bajtech pro každý soubor: cesta v cp1250, offset a velikost. Každý vnitřní soubor začíná textovou hlavičkou s typem a verzí, třeba CP roominfo 2.2, CP talk 2.4 nebo CP paction 2.5. Místnost tvoří soubory .rom, .spr, .pth, .cbx, .ans, .gfx a .ani s pozadím a sprity, polygony pro chůzi, klikacími oblastmi a animacemi.
V exe má každý formát právě jednu načítací funkci a ta umí i starší verze. Jeden parser v Pythonu proto zvládne všech osmnáct her. Exportér data převede pro prohlížeč: grafiku do WebP atlasů, popisy místností do JSON, zvuky do Ogg a videa z Intel Indeo 5 do H.264. Starší díly mají hlasy ve WAV s kompresí MS ADPCM, kterou Chromium nepřehraje, takže se převádějí do formátu Opus.
Dekompilace
Logika v datech není. Co se stane po kliknutí na dveře, které předměty jdou zkombinovat nebo kdy začne rozhovor, je zakompilované v cdromek.exe každého dílu. Claude každé exe naimportoval do Ghidry a pouštěl ji bez GUI, jen skripty z příkazové řádky. Skript DecompileAll vypíše celé exe do jednoho souboru v C, u každé funkce i s řetězci, které používá, a v tom se pak dá hledat obyčejným grepem.
Logika místností je jen malá část kódu. U Siláka je to asi 30 funkcí a 35 KB, zhruba 4 % kódu. Zbytek tvoří engine, minihry a přehrávání videa. Každá místnost má proceduru, která zpracovává zprávy Windows: kliknutí na oblast je zpráva 0x407, dojití postavy na místo 0x404, řádek rozhovoru 0x409. Zpráva projde globálním handlerem, pak procedurou místnosti a nakonec výchozí procedurou, která řeší obecné odpovědi, když s věcí nejde nic udělat.
Kliknutí na kbelík v laboratoři ve hře Zítra se bude tapetovat vypadá po dekompilaci takhle:
case 0x407:
if (param_3 == (RECT *)0x4) {
switch(param_4) {
case (int *)0x7:
uVar3 = FUN_00469730(0x4bd,1,1,-1,0,-1,0);
DAT_004a2a08 = (int *)(int)uVar3;
DAT_004a2a0c = (int)((ulonglong)uVar3 >> 0x20);
return 1;
FUN_00469730 je SAY, tedy postava něco řekne, a 0x4bd je text číslo 1213. Funkce vrací 64bitový handle, který si místnost uloží do proměnné. V TypeScriptu z toho zbude:
case CBOX_BUCKET:
game.setVar(V_SAY_BUCKET, game.say(1213, true));
return true;
Tak jednoduché to ale nebylo všude:
- Ghidra nenašla všechny funkce. Na procedury místností se odkazuje jen adresou v instrukci
push, takže je automatická analýza přehlédla. Skriptgen_starts.pyprojde výpis zobjdumpa chybějící začátky funkcí doplníCreateMissing.java. - Switch nad 64bitovou hodnotou. Místnosti mají krok svého stavového automatu v 64bitové proměnné a dekompilátor na takové tabulce skoků skončí. Tabulky opravuje
FixJumpTables.javapodle seznamu pro každou hru. - Silák a Loskutík jsou přeložené s LTCG. Překladač předává argumenty v registrech a Ghidra je v C vynechá. Čísla textů, předmětů nebo cesty ke zvukům se u nich musely číst přímo z disassembleru.
- Disky 15 až 20 mají kumulativní exe. Každé obsahuje i místnosti všech předchozích dílů, jen bez dat, takže se čísla místností mezi verzemi posouvají.
Mezi jednotlivými exe se funkce párovaly podle řetězců a assertů, ve kterých autoři nechali jméno zdrojového souboru a číslo řádku. Chůze postavy je jr_person.cpp:51 v Sami doma i v Silákovi. Mezi těmito dvěma hrami se takhle spárovalo 176 funkcí.
Ke každé hře vznikl dokument s logikou všech místností, herními proměnnými a návodem. Každé tvrzení v něm má adresu v exe a co se dohledat nedalo, je označené jako odhad.
Engine
Samotný engine má asi 9 tisíc řádků TypeScriptu. Každá hra k tomu má v průměru 2 500 řádků vlastních skriptů: jeden soubor na místnost, seznam všech herních proměnných z exe s počátečními hodnotami a globální reakce na kombinace předmětů. Logika místností je v originále zakompilovaná, takže se musí přepsat ručně místnost po místnosti, a proto mají skripty všech her dohromady asi pětkrát víc řádků než engine. Zprávám z původní hry odpovídají hooky: 0x407 je onUse, 0x404 je onWalkEnd a 0x409 je onTalkLine. Funkce enginu, které místnosti volají, mají stejný význam jako v originále, třeba say, walk, talk, showSprite nebo enableCbox. Skript místnosti se tak dá psát skoro řádek po řádku podle dekompilovaného C.
Referenční hrou byl Silák Bobík, na něm engine vznikl. Každá další hra je popsaná jako rozdíl proti němu a engine se rozšiřoval jen o to, co nová hra opravdu používá. Například nabídka odpovědí v rozhovoru se poprvé objevila až v Pinďovi na brigádě.
Pár dalších detailů:
- Chůze hledá cestu algoritmem A* na mřížce po 4 px nad polygony z
.ptha výslednou trasu vyhlazuje. - Pohyb pusy při mluvení se řídí souborem
mouth.defz původních dat. - Uložená pozice je JSON obsahující jen to, co se liší od nové hry: proměnné, inventář, stav navštívených místností, časovače i rozehrané animace.
Kde se chování z exe vyčíst nedalo, engine použije nejjednodušší odhad, který je v kódu označený komentářem stand-in. Přechody mezi scénami jsou třeba obyčejné prolínačky. Chyby původních her zůstaly, v laboratoři u Siláka pořád dostanete další plnou misku při každém kliknutí na mixér.
Osmnáct her
Centauri Production vydávalo adventury se Čtyřlístkem na discích časopisu CD-Romek v letech 2001 až 2005. Mám všech osmnáct českých dílů, od Zítra se bude tapetovat po Siláka Bobíka. Chybí jen Čtyřlístek na horách, který vyšel pouze v Rumunsku.
Všechny jdou dohrát od začátku do konce, včetně videí ve hře. Pozice se ukládají automaticky i ručně a v nabídce her je u každé vidět, kolik procent už je hotovo.
Myš už není potřeba
Minule jsem psal, že se syn bude muset naučit ovládat myš. Místo toho jsem do enginu přidal ovládání dotykem:
- klepnutí předmět použije,
- dlouhé podržení ho prohlédne,
- přetažení předmětu z inventáře ho použije na věc ve scéně,
- tlačítko s otazníkem na dvě sekundy zvýrazní všechna aktivní místa i s popiskem.
Nápověda se hodí hlavně u malých předmětů, které se ve scéně snadno přehlédnou.
Kromě webové verze existuje i aplikace pro Android, která obsahuje všechny hry a běží na šířku přes celou obrazovku.
Testy
Osmnáct her pokaždé ručně procházet nejde. Každá hra má proto automatický test v prohlížeči, který ji podle návodu odehraje od začátku do konce šestnáctinásobnou rychlostí. Po každém kroku kontroluje místnost, inventář a herní proměnné. Uprostřed hry uloží pozici, kus odehraje a pozici zase nahraje; stav i snímek obrazovky musí odpovídat uložení. Všech osmnáct testů běží souběžně asi dvě minuty.
Jestli animace sedí a zvuk se neopožďuje, testy nepoznají. Hru proto testujeme spolu se synem a objevujeme věci k doladění.