Na tej stronie zaprezentowano ogólne informacje na temat sposobu przechowywania danych w grach XCom. Informacje te są przeznaczone dla osób, które dotąd nie zetknęły się z tą problematyką.
Na dysku lub innym nośniku komputer przechowuje różne dane w strukturach zwanych plikami. Pliki znajdują się wewnątrz innych struktur zwanych folderami. Pliki można przyrównać do kartek, zaś foldery do szuflad, w których kartki te są przechowywane.
Każdy plik jest sekwencją bajtów, podobnie jak tekst na kartce papieru jest sekwencją liter. Analogia ta jest bardzo trafna, gdyż pojedynczy bajt często bywa przez komputer interpretowany właśnie jako litera.
Każdy bajt jest w rzeczywistością sekwencją 8 bitów. Bit może mieć tylko dwa stany: ustawiony (1) lub zgaszony (0). 8 kolejnych, ponumerowanych bitów może zakodować dokładnie 256 różnych stanów (dwa bity kodują 4 stany: 00, 01, 10, 11, trzy bity kodują 8 stanów: 000, 001, 010, 011, 100, 101, 110, 111, itd.). Inaczej mówiąc, każdy bajt może występować w 256 różnych stanach. Albo jeszcze inaczej, komputery używają alfabetu złożonego z 256 liter.
Do zapisu tych 256 wartości bajtu często stosowany jest system szesnastkowy (heksadecymalny). Nie będziemy tu zagłębiać się w szczegóły matematyczne tego systemu, wystarczy wiedzieć, że składa się on nie z 10, ale z 16 cyfr, oznaczanych 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.
Aby uniknąć nieporozumień, zapis w systemie szesnastkowym poprzedza się prefiksem 0x albo zaopatruje w sufiks h. I tak, zapis 0xA lub Ah oznacza liczbę 10 (A jest cyfrą następującą po 9). Zapis Fh oznacza liczbę 15: F jest najwyższą cyfrą systemu szesnastkowego, podobnie jak 9 jest najwyższą cyfrą systemu dziesiętnego.
Zapis 0x10 (lub 10h) oznacza liczbę 16. W dziesiętnym systemie liczenia zapis 10 oznacza jedną dziesiątkę i zero jedności. W systemie szesnastkowym 10h oznacza jedną szesnastkę i zero jedności. Nietrudno już chyba zgadnąć, że 20h to 32, 0x40 to 64, 55h to 85 (= 5*16 + 5), 0xAB to 171 (= 10*16 + 11) itd.
Największą liczbą dwucyfrową w układzie szesnastkowym jest FFh. Zapis ten oznacza 15*16 + 15, czyli 255. Zwróćmy uwagę, że jest to też najwyższa liczba możliwa do przedstawienia w ramach 1 bajtu. Bajt przyjmuje bowiem 256 różnych wartości: najmniejszą z nich jest 0, największą 255.
Układ szesnastkowy pozwala zapisać zawartość każdego bajtu przy pomocy dokładnie dwóch cyfr (piszemy przy tym wiodące zera). Najmniejszą wartością będzie 00h, największą FFh. Każda cyfra szesnastkowa oznacza zawartość połówki bajtu, tj. 4 kolejnych bitów. Np. 1h odpowiada układowi bitów 0001, 2h odpowiada układowi 0010, 4h odpowiada układowi 0100, Ah (=10) odpowiada układowi 1010, Fh (=15) odpowiada układowi 1111.
Uwaga: zapis 00h oznacza po prostu zero; użycie takiej formy zapisu podyktowane jest różnymi względami. Podobnie 01h to po prostu 1, 09h to 9 itd.
Pierwszy bit bajtu określany jest jako najstarszy, ostatni jako najmłodszy. Bajt, w którym ustawiony jest tylko bit najmłodszy, przechowuje wartość 1, gdyż układ 0001 to 1h (czyli 1). Jeżeli ustawiony jest tylko bit najstarszy (układ 1000), zapisana wartość wynosi 8h (czyli 8).
Aby poznać rzeczywistą zawartość dowolnego pliku, potrzebny jest program zwany edytorem heksadecymalnym (jest wiele tego typu programów, część z nich posiada status freeware, np. tutaj lub tutaj; edytor Edit jest także wbudowany w Windows). Edytor taki pokazuje dokładnie wartości każdego bajtu w omówionym systemie.
Plik jest zawsze ciągiem bajtów. Dopiero system operacyjny decyduje, czy bajty te należy interpretować jako litery (wtedy plik zawiera tekst, jak prawdziwa kartka papieru), jako zapis grafiki, dźwięku, czy też może jeszcze innych, nieliterowych danych.
Stany gier, znane także jako save’y, składają się najczęściej z plików zawierających takie właśnie nieliterowe dane. Przy ładowaniu takich plików komputer jest w stanie dokładnie odtworzyć stan gry w momencie jej zapisania. Dokładnie tak dzieje się w grach z serii XCom. Znajomość sposobu przechowywania informacji w plikach stanów gry pozwala odczytać różne informacje bez potrzeby uruchamiania gry. Z pomocą odpowiedniego edytora zachowane dane można też zmienić (i na przykład powiększyć ilość dostępnej graczowi gotówki, albo pozbawić wszystkich obcych życia w grze taktycznej). Dla wielu zaawansowanych miłośników gier odczytywanie plików danych jest jednak sztuką samą w sobie. Nie chodzi wtedy o „poprawianie” gry, ale raczej o ciekawość, jak to wszystko działa i co sprawia, że komputer „wie”, jak wyglądała gra przed jej zapisaniem.
Wszystkie bajty tworzące dany plik możemy ponumerować. W informatyce przyjmujemy często, że pierwszy element zbioru (na przykład pierwszy bajt pliku) ma numer zero, a nie jeden. Aby uniknąć dwuznaczności, używamy terminu offset. Oznacza on pozycję bajtu wewnątrz pliku liczoną w ten sposób, że pierwszy bajt otrzymuje numer zero. Umawiamy się, że numer bajtu będziemy zawsze liczyć w taki właśnie sposób.
Bardzo zbliżonym pojęciem jest adres. O ile pojęcia offset używamy mówiąc o pozycji określonego bajtu wewnątrz pliku, o tyle adres jest pojęciem szerszym i może oznaczać np. pozycję bajtu w pamięci komputera. Offset jest w tym ujęciu specjalnym typem adresu.
Dane zawarte w pliku występują w postaci bajtów, ale bajty te mogą z kolei być zorganizowane w struktury wyższego rzędu: pola i rekordy. Organizacja ta nie jest zawarta w samym pliku: to raczej program pobierający dane z pliku (może to być na przykład gra pobierająca dane z pliku zapisu stanu) musi znać ów wzorzec. Odczyt pliku danych przypomina zapełnianie formularza szeregiem liter docierających jedna po drugiej. Każda kolejna litera zajmuje miejsce w odpowiednim pokratkowanym polu formularza, puste kratki też muszą być „zapełnione” znakami spacji.
W jednym pliku możemy zmieścić wiele formularzy o identycznym wzorcu. Wystarczy, aby program odczytujący wiedział, ile liter zawiera jeden cały formularz. Długość pliku będzie wówczas pełną wielokrotnością tej ilości.
Taki zawarty w pliku „formularz” nazwiemy tu rekordem. Podobnie jak formularze, rekordy składają się z pól, a każde pole ma określoną długość.
Aby nieco lepiej objaśnić funkcjonowanie plików danych, wyobraźmy sobie kartkę z szeregiem cyfr: 196611161988121619900124. Cyfry te będą stanowić całkowitą zagadkę, chyba że będziemy dysponować kluczem do ich zinterpretowania. Jest ich łącznie 24, możemy więc zgadywać, czy zapisano tu tylko jeden rekord złożony z 24 cyfr, czy 2 rekordy po 12 cyfr, a może 3 rekordy po 8 cyfr każdy itd. Oczywiście dla nas będą to tylko domniemania, ale ten, dla kogo pozostawiono tę informację, z pewnością wie, jak długi jest pojedynczy rekord. Przypuśćmy, że podzielił się z nami tą wiedzą, i że każdy rekord złożony jest z 8 cyfr.
W dalszym ciągu nie wiemy jednak, co oznaczają poszczególne cyfry, i czy każda z nich ma jakiś sens, czy też należy je jakoś połączyć. Odbiorca informacji powie nam jednak, że każdy rekord zawiera 3 pola. Pierwsze pole złożone jest z 4 cyfr, drugie i trzecie pole zawiera po dwie cyfry.
Wiemy już więc, że w pierwszym polu pierwszego rekordu znajduje się 1966, w drugim polu 11, w trzecim 16. Czy są to liczby, czy też jakieś kody – to wie też tylko odbiorca informacji. I tylko on może nam powiedzieć, że pierwsze pole zawiera rok, drugie miesiąc, trzecie dzień miesiąca. Cały rekord zawiera więc zapis pewnej daty. W sumie w pliku zawierającym 24 cyfry zapisano więc 3 daty.
Organizacja danych może być nawet bardziej skomplikowana niż w opisanym prostym modelu, w którym występują tylko rekordy i pola. Ilość struktur o określonej hierarchii może być bowiem większa niż dwa. Poza tym zamiast cyfr przechowywane są bajty lub grupy bajtów o ściśle określonej długości. W tym ostatnim przypadku każdy bajt lub para bajtów może mieć ściśle określone znaczenie.
Terminu offset będziemy używać dla określenia pozycji pierwszego bajtu określonej struktury wewnątrz struktury nadrzędnej. Na przykład jeśli plik liczy 1392 bajty i zawiera tylko 1 rekord złożony ze 116 pól o identycznej wielkości (każde pole składa się z 12 bajtów, gdyż 12 * 116 = 1392), wówczas offsetami kolejnych pól będą 0, 12, 24, 36 itd. aż do 1380. Czym innym niż offsety będą numery pól. Zauważmy, że offset pola o określonym numerze można znaleźć bardzo łatwo. Jeśli interesuje nas pole numer 15, pierwszy bajt (czyli bajt numer zero) tego pola jest jednocześnie bajtem numer 180 (czyli bajtem sto osiemdziesiątym pierwszym, licząc w sposób „normalny”) całego pliku. Offsetem pola wzglądem początku pliku będzie więc 180 (aby obliczyć offset, wystarczy przemnożyć długość pola przez numer pola: 12 * 15 = 180). Możemy też powiedzieć, że bajt numer 180 ma offset 0 względem początku pola, do którego należy.
Offset jest więc zawsze pojęciem względnym. Dla jasności przeanalizujmy inny przykład odnoszący się do tego samego pliku. Mamy ustalić offset bajtu numer 1304 w pliku, w rekordzie i w polu (pamiętajmy, że jest to tysiąc trzysta piąty bajt pliku), a także ustalić, do którego rekordu i do którego pola bajt ten należy. Ponieważ plik zawiera tylko 1 rekord, offset względem początku rekordu będzie taki sam jak względem początku pliku – 1304. Oczywiście bajt należy do pierwszego rekordu (zgodnie z umową, nr 0), bo innego w pliku nie ma.
Aby ustalić offset względem początku pola, podzielimy offset względem rekordu przez długość pola, czyli przez dwanaście. Z dzielenia 1304 : 12 otrzymujemy 108 i resztę 8. Oznacza to, że szukany bajt należy do pola numer 108, a jego offset w tym polu wynosi 8.
Rozpatrzmy jeszcze zadanie odwrotne. W analizowanym pliku mamy znaleźć bajt nr 2 w polu nr 7. Wystarczy w tym celu wykonać działanie 7 * 12 + 2, aby otrzymać numer interesującego nas bajtu (czyli jego offset w pliku): 86.
Widać, jak bardzo upraszcza rachunki liczenie od zera. Sprawdźmy jeszcze, że bajt numer zero ma rzeczywiście offset równy zero – przecież 0 : 12 daje 0 i resztę 0. Podobnie bajt nr 12 pliku jest bajtem nr 0 w polu nr 1, gdyż 12 : 12 = 1, reszta 0.
Opisana wyżej struktura występuje w pliku up.dat opisującym dane dotyczące ufopedii. Poszczególne bajty pola danych mają tu swoje znaczenie. Całe pole opisuje jedną kartkę ufopedii. Przyjmujemy, że w pliku znajduje się 1 rekord, bo przecież w grze istnieje tylko 1 ufopedia.
Plik base.dat zawiera natomiast informacje o bazach XCom-u. Baz tych może być maksymalnie 8, dlatego też plik zawiera 8 rekordów. Długość pliku (w wersji PC) wynosi 2336 bajtów, a zatem długość rekordu wynosi 292 bajty. Rekord składa się z pewnej ilości pól, które nie są jednak równej długości. Pamiętajmy jednak, że wszystko tu jest kwestią interpretacji. Jeśli interesuje nas tylko to, co znajduje się w magazynach bazy, możemy śmiało przyjąć, że każde pole ma długość dwóch bajtów. Powiemy więc, że 1 rekord składa się tu ze 146 pól.
Z odpowiedniej tabeli odczytamy, że ilość Stingray Launcher umieszczona jest w pliku base.dat w polu nr 48. Jeśli chcemy odnaleźć ilość wyrzutni rakiet typu Stingray w trzeciej bazie (nr 2!), musimy uświadomić sobie, że interesująca nas dana będzie zawarta w dwóch bajtach pola nr 48 rekordu nr 2. Aby znaleźć offset pierwszego z tych bajtów względem początku pliku base.dat wykonamy następujące obliczenie: 2*292 + 48*2 = 680. Offsetem drugiego z bajtów będzie naturalnie 681. Obliczenie możemy sprawdzić: pierwsza baza zapisana jest w bajtach 0–291, druga w bajtach 292–583, od bajtu 584 rozpoczyna się opis trzeciej bazy, otrzymane wartości na pewno znajdą się we właściwym rekordzie. Proste odejmowanie 680–584 daje offset bajtu względem początku rekordu: 96. Ponieważ pola w rekordzie uznaliśmy za złożone z dwóch bajtów, wskazany bajt jest pierwszym bajtem pola numer 48 i jeśli tabela nie zawiera błędnych danych, musi rzeczywiście zawierać ilość wyrzutni Stingray.
Komputery PC traktują zwykle bajt o niższym numerze jako mniej ważny (jest to tak zwany system Big Endian = duży na końcu). Wbrew pozorom nie jest to wcale kolejność, do której jesteśmy przyzwyczajeni, bo przecież najpierw zapisujemy cyfrę dziesiątek, a dopiero potem jedności. W innych systemach komputerowych, między innymi na komputerach Amiga, zapis bywa odwrotny niż na PC (Little Endian = mały na końcu).
Przypuśćmy, że w dwubajtowym polu danych chcemy zapisać liczbę 1000. Zapisujemy ją najpierw w układzie szesnastkowym: 3E8 (bo 3*16*16 + 14*16 + 8 = 768 + 224 + 8 = 1000). Na komputerze PC liczbę tę zapamiętamy w dwóch bajtach jako E8 03 (lewy bajt ma niższy numer, więc zawiera jednostki niższego rzędu). Tę samą liczbę na komputerze Amiga zapiszemy jako 03 E8. Oto dlaczego pliki danych gry UFO-1 nie będą możliwe do prawidłowego odczytania w innym systemie komputerowym (nawet jeśli poza kolejnością bajtów struktura pliku będzie taka sama, co wcale nie zawsze odpowiada prawdzie).
Istnieją różne sposoby zapisu wartości TRUE (prawda) i FALSE (fałsz) w postaci bajtów. W grach UFO-1 i UFO-2 fałsz zapisywany jest w postaci 00h. Wartość TRUE jest natomiast oznaczana różnie, zależnie od platformy sprzętowej, co ma związek z różnicą big endian i little endian – zapalany jest bit najmniej istotny. W wersji PC wartość TRUE zapisywana jest jako 01h, w wersji amigowej jako 80h.