Dzień dobry!
W dniu dzisiejszym pozwolę sobie powrócić do tematu “The Eidolon”, a powodem do powrotu do tego tematu były ostatnie posty szanownego kolegi Piguły, w których walczył on z kolejnymi taśmami przybyłymi z przeszłości i napotkał on na nich obraz tejże gry, tym razem wersji przeznaczonej dla systemu KSO Turbo 2000, a więc wersji turbo bazującej na transmisji danych z wykorzystaniem interfejsu podłączanego do drugiego portu joysticka.
Tę wersję opracował niejaki “*AJEK”, bazując na wersji zrobionej przez ludzi z “Unerring Masters”, dla która to zaistniała wcześniej na tym forum jako nagranie znajdujące się za zestawie nr. 4 firmy Empex z Łodzi. Podczas prób odtworzenia tamtej wersji okazało się że co prawda udało się poprawnie zdekodować nagrania, ale niestety okazało się również że tamtej wersji brakuje plików z poziomu ósmego gry, zatem mimo usilnych prób przywrócenia tej pozycji wszystkie wysiłki rozbiły się o brak końcowych fragmentów nagrania.
Gdy teraz okazało się że pojawiła się inna wersja tejże pozycji, tym razem przygotowana przez *AJKA pojawiła się nadzieja że tym razem uda się odtworzyć całość! Tym bardziej że wersja *AJKOWA bazowała na wersji od “Unerring Masters”. Usiadłem więc do roboty i podjąłem kilka prób konwersji nagrań dostarczonych przez Pigułę… niestety część bloków na taśmie okazała się uszkodzona w sposób uniemożliwiający poprawne odtworzenie wszystkich uszkodzonych bloków. Siedząc i analizując rekordy, zastępując część z nich (tych w których sygnał zanikał do takich poziomów które uniemożliwiały dekodowanie) “połatałem” i poskładałem to w całość.
Przyszedł zatem czas na końcowe testy i nie przypuszczając jeszcze co mnie czeka, zabrałem się ochoczo do testów… po dłuższej chwili zabaw we wczytywanie kolejnych poziomów dotarło do mnie że *AJKOWA wersja jest również niekompletna i zabawa kończy się na poziomie szóstym! Normalnie się zagotowałem… tyle walki na nic? Mogę dokleić co prawda brakujące bloki z wersji “UM”, ale i tak będzie brakowało końcowych plików! Myślałem że przysłowiowy szlag mnie trafi… tyle walki i roboty na nic? Prawie pogodziłem się porażką i pomyślałem że opiszę sprawę na forum i być może za jakiś czas ktoś znajdzie kasetę w pełną wersją tejże gry zawierającej wszystkie pliki i jakimś cudem udostępni jej zawartość.
Zacząłem się zastanawiać jakie jednak są szanse na taki obrót sytuacji, stwierdziłem że dość niewielkie. Spokoju jednak mi to nie dawało bo ta wersja gry sygnowana przez “Unerring Masters”, była wersją która nie wymagała żadnej dodatkowej pamięci, a do jej wczytania wystarczało jedynie 64kB RAM. Zacząłem się zastanawiać jak wygląda wersja dyskowa tejże gry i czy nie dałoby się wyłuskać z niej brakujących plików i wygenerować je w formacie których oczekuje loader. Za długo nie myśląc, gdy tylko znalazłem chwilę wolnego czasu zacząłem analizować zawartość obrazu dyskietki z grą. Zastanawiałem się czy gra używa jakichś wyrafinowanych struktur jeżeli chodzi przechowywanie danych poszczególnych poziomów gry.
Jakież było moje zdziwienie gdy przeglądając hex-edytorem zawartość pliku .ATR z obrazem gry natrafiłem na prawie normalne wpisy w miejscu gdzie powinien się znajdować katalog dysku w formacie DOS 2.0:
xxd -g1 -seek 0xb410 -l 0x280 "Eidolon, The v1.1 (1985)(Epyx)(US).atr"
0000b410: 60 01 00 01 00 20 20 20 20 54 68 65 20 20 20 20 `.... The
0000b420: 60 02 00 02 00 20 20 45 69 64 6f 6c 6f 6e 20 20 `.... Eidolon
0000b430: 60 03 00 03 00 76 65 72 73 69 6f 6e 20 31 2e 31 `....version 1.1
0000b440: 60 04 00 04 00 20 43 6f 70 79 72 69 67 68 74 20 `.... Copyright
0000b450: 60 05 00 05 00 20 20 20 31 39 38 35 20 20 20 20 `.... 1985
0000b460: 60 06 00 06 00 20 4c 75 63 61 73 66 69 6c 6d 20 `.... Lucasfilm
0000b470: 60 07 00 07 00 20 4c 74 64 2e 20 35 30 39 31 39 `.... Ltd. 50919
0000b480: 00 43 61 76 65 61 74 20 50 65 69 72 61 6e 21 21 .Caveat Peiran!!
0000b490: 42 0b 00 d7 00 50 41 4e 45 4c 20 20 20 53 43 52 B....PANEL SCR
0000b4a0: 42 09 00 e2 00 54 49 54 4c 45 20 20 20 53 43 52 B....TITLE SCR
0000b4b0: 42 03 00 eb 00 4c 45 56 45 4c 31 20 20 4d 41 50 B....LEVEL1 MAP
0000b4c0: 42 03 00 ee 00 4c 45 56 45 4c 32 20 20 4d 41 50 B....LEVEL2 MAP
0000b4d0: 42 03 00 f1 00 4c 45 56 45 4c 33 20 20 4d 41 50 B....LEVEL3 MAP
0000b4e0: 42 03 00 f4 00 4c 45 56 45 4c 34 20 20 4d 41 50 B....LEVEL4 MAP
0000b4f0: 42 03 00 f7 00 4c 45 56 45 4c 35 20 20 4d 41 50 B....LEVEL5 MAP
0000b500: 42 03 00 fa 00 4c 45 56 45 4c 36 20 20 4d 41 50 B....LEVEL6 MAP
0000b510: 42 03 00 fd 00 4c 45 56 45 4c 37 20 20 4d 41 50 B....LEVEL7 MAP
0000b520: 42 03 00 00 01 4c 45 56 45 4c 38 20 20 4d 41 50 B....LEVEL8 MAP
0000b530: 42 11 00 03 01 44 52 41 47 4f 4e 31 20 43 45 4c B....DRAGON1 CEL
0000b540: 42 1a 00 14 01 44 52 41 47 4f 4e 32 20 43 45 4c B....DRAGON2 CEL
0000b550: 42 14 00 2e 01 44 52 41 47 4f 4e 33 20 43 45 4c B....DRAGON3 CEL
0000b560: 42 1c 00 42 01 44 52 41 47 4f 4e 34 20 43 45 4c B..B.DRAGON4 CEL
0000b570: 42 1b 00 5e 01 44 52 41 47 4f 4e 35 20 43 45 4c B..^.DRAGON5 CEL
0000b580: 42 19 00 82 01 44 52 41 47 4f 4e 36 20 43 45 4c B....DRAGON6 CEL
0000b590: 42 14 00 9b 01 44 52 41 47 4f 4e 37 20 43 45 4c B....DRAGON7 CEL
0000b5a0: 42 1e 00 af 01 44 52 41 47 4f 4e 38 20 43 45 4c B....DRAGON8 CEL
0000b5b0: 42 0f 00 cd 01 44 52 41 47 4f 4e 38 41 43 45 4c B....DRAGON8ACEL
0000b5c0: 42 10 00 dc 01 44 52 41 47 4f 4e 38 42 43 45 4c B....DRAGON8BCEL
0000b5d0: 42 10 00 ec 01 44 52 41 47 4f 4e 38 43 43 45 4c B....DRAGON8CCEL
0000b5e0: 42 1c 00 fc 01 42 49 46 46 20 20 20 20 43 45 4c B....BIFF CEL
0000b5f0: 42 1c 00 18 02 42 4e 45 43 4b 20 20 20 43 45 4c B....BNECK CEL
0000b600: 42 1d 00 34 02 47 52 45 50 20 20 20 20 43 45 4c B..4.GREP CEL
0000b610: 42 18 00 51 02 4d 41 4c 4c 4f 43 20 20 43 45 4c B..Q.MALLOC CEL
0000b620: 42 1b 00 69 02 50 4f 4c 59 50 53 20 20 43 45 4c B..i.POLYPS CEL
0000b630: 42 10 00 84 02 50 55 46 46 20 20 20 20 43 45 4c B....PUFF CEL
0000b640: 42 08 00 94 02 52 4f 54 4f 46 4c 59 20 43 45 4c B....ROTOFLY CEL
0000b650: 42 17 00 9c 02 54 52 4f 4c 4c 20 20 20 43 45 4c B....TROLL CEL
0000b660: 42 0f 00 b3 02 42 42 49 52 44 20 20 20 43 45 4c B....BBIRD CEL
0000b670: 80 0e 00 c2 02 4d 49 53 20 20 20 20 20 43 45 4c .....MIS CEL
0000b680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
… gdy ujrzałem tę strukturę katalogu przyznam że pojawił się w mojej głowie pewien promyk nadziei. Sprawdziłem na szybko czy aby na pewno wpisy znajdujące się w katalogu mają odzwierciedlenie w rzeczywistości i czy na pewno wskazują na poprawne pliki umieszczone na dysku. Okazało się że faktycznie da się te pliki bezproblemowo odczytać za pomocą DOS-a czy też dowolnego narzędzia do ekstrakcji plików umieszczonych w obrazie ATR, aby jednak taka operacja była możliwa i aby “DOS” czy inne narzędzie widziały całą strukturę wpisów należy podmienić bajt znajdujący się pod offsetem 0xb480 na wartość np. 0x60, tak aby DOS czy inne narzędzie nie uznawały że to ostatni wpis w katalogu dysku. Po tej operacji możemy listować już zawartość dysku a także skopiować wybrane pliki.
Teraz przyszedł czas na zastanowienie się jak wygląda wczytywanie przez grę poszczególnych plików, tzn. jak wygląda struktura danego poziomu i gdzie w kodzie gry określone jest jakich plików potrzebuje dany poziom do poprawnego działania. Oczywiście patrząc na strukturę katalogów można domyśleć się iż do każdego poziomu przypisany jest jeden ze smoków (pliki od “dragon1.cel” do “dragon8.cel”), przy czym widać że ostatni smok przeciwnik składa się wielu segmentów (dragon8.cel, dragon8a.cel, dragon8b.cel, dragon8b.cel). Można się domyśleć że pliki: “biff.cel”, “bneck.cel”, “grep.cel” … “rotofly.cel”, opisują wygląd poszczególnych przeciwników spotykanych podczas eksploracji jaskiń na kolejnych poziomach gry, jasne również staje się co reprezentują pliki z końcówką “.scr” oraz pliki “level1.map”. Przyszło mi do głowy że każdy z plików opisujących poziom (*.map_ powinien mieć w informację również o przeciwnikach i ich wyglądzie zawartą w sobie, zatem moja uwaga skierowała się na plik “LEVEL8.MAP”, i patrząc na wpis w katalogu dotyczący pliku “LEVEL8.MAP” widzimy:
0000b520: 42 03 00 00 01 4c 45 56 45 4c 38 20 20 4d 41 50 B....LEVEL8 MAP
Analizując ten wpis widzimy że plik zaczyna się od sektora 256 i ma długość 3 sektorów, zatem zaglądamy we wskazane sektory:
xxd -g1 -seek 0x8010 -l 0x180 "Eidolon, The v1.1 (1985)(Epyx)(US).atr"
00008010: 00 80 00 00 00 e3 00 e2 80 e0 80 80 80 e0 80 e2 ................
00008020: 00 e3 00 00 00 80 00 40 00 40 00 40 00 40 00 80 .......@.@.@.@..
00008030: 00 80 00 00 00 e3 00 e2 80 e0 80 80 80 e0 80 e2 ................
00008040: 00 e3 00 00 00 80 00 80 00 00 00 40 00 00 00 80 ...........@....
00008050: 00 80 00 00 00 80 80 80 80 f3 00 80 00 f3 80 80 ................
00008060: 80 80 00 00 00 00 00 00 00 00 00 80 00 00 00 00 ................
00008070: 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00 ................
00008080: 00 00 00 00 00 00 00 00 00 00 00 80 00 45 02 7d .............E.}
00008090: 00 00 00 00 00 00 44 52 41 47 4f 4e 38 20 43 45 ......DRAGON8 CE
000080a0: 4c 44 52 41 47 4f 4e 38 41 43 45 4c 44 52 41 47 LDRAGON8ACELDRAG
000080b0: 4f 4e 38 42 43 45 4c 44 52 41 47 4f 4e 38 43 43 ON8BCELDRAGON8CC
000080c0: 45 4c 18 c8 c8 c8 c8 08 0a 00 00 08 00 0c 00 00 EL..............
000080d0: a0 ff 54 c6 c6 c6 c6 88 84 03 00 00 b8 08 04 04 ..T.............
000080e0: 04 04 04 06 08 0a 40 40 40 40 00 00 00 00 00 00 ......@@@@......
000080f0: 00 00 00 00 00 ff 00 00 ff 80 e0 80 e2 00 e3 00 ................
00008100: 00 00 80 00 80 00 40 00 40 00 40 00 40 44 00 69 ......@.@.@.@D.i
00008110: 38 08 2b 0f 7f 0d 1b 7f 0d fa 00 02 4d 52 57 fa 8.+.........MRW.
00008120: c9 17 59 92 bc fa dd f8 f4 f0 f2 fa f4 ba 38 8c ..Y...........8.
00008130: 09 60 06 ff fa ea fa 07 00 01 02 02 03 04 00 04 .`..............
00008140: e3 05 0d 00 05 05 06 07 08 00 09 0a 0b 0b 0c 0c ................
00008150: 0d 5f 4a 06 9a 9a aa ba ca da ea 46 e0 01 4e e0 ._J........F..N.
00008160: 13 4e 02 03 0e 10 4c 02 04 0f 11 4c 0a 14 16 19 .N....L....L....
00008170: 18 05 02 08 09 0a 0b 0d 44 02 07 15 17 52 03 07 ........D....R..
00008180: 24 fe 1f 41 c1 e2 fd a3 e4 f1 ea c5 e9 49 04 7d $..A.........I.}
… no i wszystko stało się jasne! Plik .MAP opisujący poziom zawiera w środku informacje o plikach które są konieczne do wczytania przed uruchomieniem danego poziomu. Szybka analiza rekordów danych nagranych na taśmie pozwoliła mi ustalić że na taśmie brakuje ostatniego pliku “DRAGON8C.CEL”.
Teraz pozostało tylko skopiować z obrazu dyskietki plik “DRAGON8C.CEL” i spróbować zakodować go w taki sposób aby loader wczytujące poszczególne bloki gry poprawnie wczytał ów plik, a przy odrobinie szczęścia mogło się okazać że jeżeli ta operacja skończy się sukcesem to będziemy dysponowali kompletnym odtworzonym obrazem gry. Szanse na powodzenie niewielkie, jednak warto było spróbować.
Jak się do tego zabrać? Ponieważ pracujemy na plikach .HEX opisujących strukturę nagrania w sposób czytelny dla człowieka, a nagrane na taśmę bloki są zgodne ze standardem AST to wystarczyło napisać prosty skrypt kodujący dany plik jako ciąg liczb hex, dodać to tego segment “PWMD” zawierające odpowiednie długości impulsów, na końcu tego całego ciągu danych dodać sumę kontrolną XOR i jeżeli wszystko będzie się zgadzało to loader gry powinien wczytać brakujący plik bez błędu.
Klepiąc parę skryptów w Pythonie (do konwersji danych, liczenia sumy kontrolnej, etc.) udało mi się tę operację przeprowadzić w miarę bezproblemowo. Brakujące segmenty danych dodałem do pliku .hex zawierającego AJKOWĄ wersję gry… i podczas przejścia na ósmy poziom gry okazało się że loader poprawnie wczytał brakujący plik! :) Poziom ósmy wystartował bez problemu! Jakaż była moja radość gdy okazało się że te wszystkie operacje wykonane wcześniej przyniosły oczekiwany skutek.
Postanowiłem również poprawić zatem wcześniejszą wersję przeznaczoną dla turbo AST/UM. Na początku myślałem że loader ładujące poszczególne segmenty danych znajduje się w segmencie danych który pokazuje czołówkę informującą że tę wersję gry opracował “Unerring Masters”, podmieniłem więc tylko początkowe segmenty, tak aby zostały tylko jak mi się wydawało rekordy zawierające loadery dla systemu AST/UM, jednak moja radość nie trwała długo, ponieważ okazało się że loader danych został umieszczony w głównym segmencie danych zawierającego kod gry (blok danych o długości 24705 bajtów), zatem należało przenieść również ten długi blok z wersji dla AST/UM.
W tym jednak momencie zacząłem się zastanawiać czy aby na pewno skonwertowany wtedy przeze mnie plik był na pewno dobrze przetworzony, miałem wątpliwości ponieważ nagranie było dość wiekowe i miejscami trudno było poprawnie przetworzyć niektóre bloki danych, jednak teraz pojawiła się możliwość porównania obu plików, tzn. wersji AJKOWEJ i wersji UM, postanowiłem dokonać takiego porównania, w różnice powinny były tylko występować w obrębie loadera, gdyż wersja dla “KSO Turbo 2000”, dokonywała odczytu używając odwołań do PORTA ($D300) a wersja AST/UM będzie oczekiwać danych z portu SIO, a dokładniej rzecz biorąc z rejestru $D20F (bit #4 który to odwzorowuje stan wejścia SIO_DATA_IN).
Problem w tym że pierwsze dwa główne bloki gry (czołówka “Lucasfilm” oraz główny kod gry) były zakodowane inaczej niż pozostałe bloki gry odczytywane przez loader. Bloki ładowane w trakcie gry zapisano w standardzie AST, natomiast dwa pierwsze bloki posiadały inny początkowy ton synchronizujący, a długości impulsów były zgodne z Turbo 2000, z tym że kolejność bitów w bajcie była kodowana odwrotnie (LSB first) niż w przypadku KSO Turbo 2000 (MSB first). Moja wcześniejsza próba konwersji nie była doskonała, ponieważ mając świadomość iż użycie a8cas-util.pl z wymuszeniem formatu Turbo 2000 i sumą kontrolą XOR, pozostawiła w strukturze bloku PWMD bajty z odwróconymi bitami, a jako że nie przeszkadzało we wczytywaniu danych, to ten blok zostawiłem w takim stanie. Chcąc jednak porównać teraz zawartość obu plików należało doprowadzić ten segment danych do porządku. Kolejny skrypt w python i szybko udało się przetworzyć wcześniej skonwertowany segment danych, niestety okazało się że przy poprzedniej konwersji oprócz odwrócenia bitów w bajtach całość danych została przesunięta o jeden bit w lewo (konwersja i dekodowanie przez a8cas-util.pl było przeprowadzone nie na tym zboczu sygnału które trzeba) … blah… a więc kolejny skrypt i dane zostały przesunięte o jeden bit w prawo. Można było więc porównać zawartość obu segmentów danych (AJEK vs AST/UM). Kolejny skrypt to porównania danych, XOR aby zobaczyć ew. różnice w bitach i okazało się że mam dużo przekłamań na najstarszych bitach niektórych bajtów… ehhh.
Okazało się że przesunięcie nie przeszkadzało procedurom ładującym dane pod emulatorem, gdyż procedury ładujące dane pod emulatorem miały inne zależności czasowe i one dekodowały sygnał generowany ze strumienia PWMD nieco później, przez co dekodowanie następowało nieco później i to się nawet poprawnie wczytywało.
Co należało więc zrobić… ponownie dokonać konwersji tego bloku, niestety u mnie źródłowy plik .FLAC/.WAV się nie zachował, na szczęście Piguła pozostawiał ten plik w udostępnionych przez siebie zasobach. Pobrałem plik źródłowy jeszcze raz, wysłuskałem blok który mnie interesował i spróbowałem innego podejścia… początkowo chciałem napisać własny dekoder do tego typu danych jednak po paru eksperymentach z “a8cas-util.pl” udalo się poprawnie zdekodować ten blok danych używając następujących parametrów:
./a8cas-util.pl conv -t lowsil2000 --cksum xor eidolon.blk2.um.wav eidolon.blk2.um.hex
Okazało się że długości impulsów używane w przypadku “Wrocławskiego Turbo 2000” są w miarę zgodne z blokami danych zapisanych w formacie UM, jedynie sposób liczenia sumy kontrolnej się zmienia na XOR. Oczywiście po takiej konwersji kolejność bitów w bajcie jest odwrócona, ale tę sprawę można załatwić dodatkowym skryptem.
Tutaj jeszcze jedna uwaga, Piguła udostępnił zgraną taśmę z próbkowaniem 48KHz (to bardzo dobrze), jednak z jakiegoś powodu a8cas-util, nie radził sobie z tym nagraniem próbkowanym z tą częstotliwością, w tym wypadku konwersja z 48kHz do 44.1kHz pomogła a8cas-util uporać się z dekodowaniem bez najmniejszego problemu.
Pewnie niektórzy z was zastanawiają się po co ja to wszystko opisuje, robię to po to aby opisując swoje doświadczenia zostawić coś w rodzaju poradnika i sposobów postępowania, gdyby ktoś kiedyś zechciał odzyskiwać jakieś nagrania odnaleziona na taśmach.
Wracając do moich zewnętrznych prymitywnych "skrypcików" w python, to ja zdaje sobie sprawę że te wszystkie operacje pewnie dałoby się dopisać do a8cas-util.pl, ale ja kompletnie nie znam się na PERL-u w którym to “a8cas-util.pl” jest napisany. Prościej i szybciej jest mi posługiwać się narzędziami czy językami które znam. A pisanie jednorazowych prymitywnych skryptów przy użyciu chociażby Pythona jest po prostu dla mnie szybsze i efektywniejsze, niźli dłubanie w PERL czy też pisanie tony kodu w C.
Także wracając do głównego wątku, po konwersji tego bloku i porównaniu z AJKOWĄ wersją różnice w plikach występowały tylko i wyłącznie w kodzie loadera, który to czytał z innego portu, w tym momencie mogłem być pewien że blok główny blok danych jest poprawnie zdekodowany. Koniec końców można było przygotować finalne wersje plików .HEX oraz .CAS. Przygotowane pliki sprawdziłem pod zmodyfikowaną przez FUJI-ego wersją atari800 (wsparcie dla systemów turbo). Wszystkie poziomy się wczytują poprawnie zarówno w wersji AST/UM jak i tej przygotowanej przez AJKA. Wspomniane pliki dodaję jako załącznik tego posta i kończę ten jak zwykle przydługi swój wywód.
Miałem napisać jeszcze o dwóch rzeczach… gdy loader gry wykryje błąd to na ekranie pokaże się typowa "atarowska tęcza", po cofnięciu taśmy i wciśnięciu START odczyt zostanie ponowiony. Jeżeli zakończymy grę (ostatni ósmy poziom) to gra będzie chciał wczytać ponownie pierwszy poziom, w tym wypadku należy przewinąć taśmę do tego miejsca, albo przesunąć się w obrazie taśmy w emulatorze do 23 bloku (dane poziomu pierwszego zaczynają się właśnie od tego bloku danych).
Drugą sprawą jest to że zastanawiało mnie dlaczego dwa pierwsze bloki nagrano w formacie UM a resztę niejako w formacie zgodnym AST? (różnica w początkowych tonach sync) … początkowo wydało mi się że zabieg ten był celowy i miał za zadnie chronić przed wczytaniem bloku z grą zamiast danych poziomu (np. gdy użytkownik przewinie taśmę zbyt daleko w tył) … ale nie wiem czy to był rzeczywisty powód zastosowania rekordów w różnych formatach. Być może powodem było coś innego.
W sumie to nie sądziłem że zabawa z tym “The Eidolon” zajmie mi tyle czasu, a po drugie nie sądziłem że mnie to wciągnie na tyle że dociągnę ten temat do końca, sam nie wiem czemu się tak zawziąłem, ale każda kolejna przeszkoda która się pojawiała powodowała że bardziej chciałem sprawę doprowadzić do szczęśliwego finału, tym razem się udało, ale nie wiem czy będę miał jeszcze kiedykolwiek tyle cierpliwości ;)