[RElog] – Oel (Pompowacze) port z C64 (część 2)

Analiza statyczna kodu

Hej, w dzisiejszym wpisie chciałbym podzielić się z Wami moimi przeżyciami związanymi z pisaniem portu do gry Oel (więcej info o samym projekcie [tutaj]. Zgodnie z nowo przyjętą tradycją omawiane fragmenty kodu dotyczą zmian do commita [81fa7cc].

W poprzednim wpisie zająłem się ogólnymi przygotowaniami, od tamtego momentu odkryłem całkiem sporo. W tym wpisie chciałbym pokazać jakie kroki wykonałem aby móc względnie komfortowo poruszać się po kodzie tej gry.

Pierwszym krokiem jaki postanowiłem zrobić to oczywiście zrozumieć do czego służą poszczególne słowa kluczowe, zmienne, funkcje, itp. Jak łatwo się domyślić dokładne zrozumienie nie jest do końca potrzebne przy zwykłej statycznej analizie (no chyba że przepisuje się kod 1:1 ze wszystkimi bugami ;) ), ale nawet do ogólnego zrozumienia trzeba było oryginalny kod nieco pozmieniać.

Uwaga! W poniższych listingach kodu pokazuję jedynie wybrane fragmenty, linki do kompletnych plików w okolicach odpowiednich listingów ;) Oczywiście gorąco zachęcam do przejrzenia ich w całości, bo nie ukrywam że każda linia to sporo pracy ;)

Mając pod ręką [linki z dokumentacją] zabrałem się do pracy (samą bazę linków rozbudowywałem w miarę potrzeby), na pierwszy ogień postanowiłem zabrać się za rozbicie spacjami [słów kluczowych] (komend), tak aby vim dał radę je pokolorować.

Jak widać na powyższym przykładzie nie załatwia nam to np. spójników logicznych, ani operatorów. Zauważamy także prostą strukturę linii kodu:

Oprócz tego każda linia może składać się z wielu instrukcji, instrukcje można ze sobą „skleić” używając operatora „:”

Powyższą linię można zapisać w nieco przyjaźniejszy sposób następująco:

Jak widać powyższy zapis jest znacznie bardziej czytelny i różne jego wersje są stosowane w [„przetłumaczonym” listingu]. Samo sklejanie instrukcji jest bardzo ważnym elementem tego języka, a to chociażby z powodu ograniczeń instrukcji if która po spełnieniu warunku wykonuje tylko jedną linię po słowie kluczowym then (do tego tematu wrócimy jeszcze później)

Kolejnym krokiem użytecznym w analizie było „zapolowanie” na wszystkie funkcje, w tym celu użyłem komendy:

W wyniku otrzymałem listę odwołań do konkretnych adresów, które można traktować jako funkcje (jak się okazało pod koniec analizy lista nie była kompletna). Całość zrzuciłem do pliku [functions.txt] i tam stworzyłem „mapę funkcji”, w której zacząłem zmieniać mało zrozumiałe nazwy na bardziej intuicyjne:

Gdy już stworzyłem wpis zmiany nazwy w tym pliku to wystarczyło jedynie zamienić wszystkie gosub na ich czytelne odpowiedniki w pliku źródłowym (np znaną już komendą vima „:%s/gosub 1460/init_values()/g„), nie ukrywam że stworzenie tego pliku znacznie poprawiło szybkość analizy kodu.

Podobnie zacząłem postępować też z innymi wyrażeniami, a także zmiennymi.

Interesującym odkryciem było, to że gra operuje na pamięci rzeczywistej, co oznacza że rozmowa ze sprzętem też może odbywać się na tym samym poziomie. Ten fakt jest szczególnie przydatny przy instrukcji poke, która może zapisać wartość pod dany adres, np.

Oznacza: „Zamień kolor ramki na biały”, aby do tego dość należało skorzystać z dwóch rzeczy: 1) mapy pamięci, 2) tabeli kolorów (linki do nich macie w moim repo). Ostatecznie ich czytelniejsza wersja wygląda następująco:

Kończąc temat funkcji, tak BASIC i Commodore udostępniają także komendy dostępne przy użyciu print, znalezienie wszystkich przysporzyło mi sporo problemów ostatecznie udało mi się je odnaleźć i przypisać im odpowiednie funkcje:

Oprócz nich były także inne wariacje, których już tutaj nie zapisałem, ale ich mnemoniki są na tyle proste że nie tworzyłem im oddzielnych funkcji (np zmiana koloru tekstu: „{blk}Print black text”).

Jak wspomniałem już wcześniej, zacząłem także tworzyć mapę zmiennych, która jest dostępna w pliku [variables], jeżeli rzucicie na nią okiem to nie jest ona zupełna, a wynika to z faktu że zmiennych jest masa oraz części funkcjonalności nie byłem w stanie odgadnąć (lub też jeszcze nie zdążyłem), tutaj nie obyło się bez solidnego wgryzienia w kod.

Pomocne okazały się stringi, które należało przetłumaczyć z niemieckiego na np angielski, w tym celu grepnąłem kod źródłowy po zawartości cudzysłowów a następnie zacząłem umieszczać w plikach: [strings_translated] oraz [strings_notranslation].

Dojście do obecnej wersji pliku [listing.bas] pochłonęło naprawdę sporo godzin (myślę, że około 15-20), duża część z tego czasu to research oraz analiza kodu, bardzo pomocne okazywały się być skrypty robiące za mnie sporo roboty.

Nie ukrywam, że sam projekt jest bardziej wymagający niż na początku sądziłem, przy porcie 1:1 wiele rzeczy wymaga dokładnego zrozumienia (nie pomaga język niemiecki w którym pisana jest gra), jednakże jest to bardzo relaksujące. Jeżeli ktoś chce mi pomóc w tym porcie to gorąco zachęcam do poszukania „TODO” w kodzie i ich wykonania ;)/

Ty tyle w tym wpisie, do przeczytania w kolejnym, w którym mam nadzieję przedstawię Wam prototyp portu gry ;)

Code ON!