Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

Edytor plików Infinity Engine – część 1 — format zapisu plików

Silnik (gier komputerowych) Infinity Engine pozwala na tworzenie gier cRPG. Napisany został przez BioWare i wykorzystywany przede wszystkim przez studio Black Isle.

Gry jakie w nim powstały z pewnością są wszystkim dobrze znane, bowiem uchodzą za legendy komputerowych gier RPG:

  • Baldur’s Gate
  • Icewind Dale
  • Planescape Torment
(nie wymieniam dodatków, rozszerzeń czy kolejnych części tego samego tytułu).

Jeśli gra jest dobra, z pewnością przegramy w nią co najmniej kilkadziesiąt godzin, nieraz przechodząc ją po kilka razy. Z czasem jednak – co nieuniknione – zacznie doskwierać nam nuda. Wtedy z pomocą przychodzą, a jakże, mody (modyfikacje)!

Tworzenie modu

Tylko czy zastanawialiśmy się, w jaki sposób mody zostały stworzone? Spróbujmy stworzyć modyfikację pliku przedmiotu (stwórzmy po prostu nowy przedmiot) dla gry Baldur’s Gate (część pierwsza).

Z pewnością punktem wyjścia modu dla nieudokumentowanego silnika gry (ponieważ oficjalna dokumentacja Infinity Engine nie jest dostępna) są pliki oryginalne, stworzone przez twórców.

Dobrze, znajdźmy zatem jakiś plik przedmiotu, żeby móc go skopiować, edytować i ostatecznie dodać na nowo do gry (i dać naszej postaci za pomocą kodu)!

Hm...

A właściwie gdzie jest plik przedmiotu? Najbliżej będzie plik data/ITEMS.BIF.
Czyżby tablica plików? Nic bardziej mylnego! A w każdym razie: na etapie naszej obecnej wiedzy.

Analiza pliku ITEMS.BIF

Pierwsze (jak i kolejne) linijki pliku otworzonego w Sublime Text wyglądają tak:

4249 4646 5631 2020 0305 0000 0000 0000
9c96 0700 0000 0000 4954 4d20 5631 2020
6437 0000 6637 0000 0000 00a4 1424 0004
2000 0000 0000 0000 0000 2020 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0100 0000 00a4 1424 0004 0000 0000 00a4
1424 0004 0000 0000 6537 0000 6737 0000

i jest to 32365 linijek łącznie.

Ale przecież w jakiś sposób ktoś stworzył edytory/przeglądarki plików (np. NearInfinity). Czyżby informacja była w jakiś sposób zakodowana?

Otóż tak! Szybki research w sieci pozwolił domyślić się, że jest to plik zapisany w systemie liczbowym szesnastkowym (na co dzień używamy systemu liczbowego dziesiętnego).

Niestety nie mam absolutnie żadnego doświadczenia z pracą z systemami innymi niż dziesiętny, dlatego potrzebowałem dalszych studiów.

Okazuje się, że jeden bajt w systemie szesnastkowym to 2 znaki (czyli np. pierwsze 4 znaki 4249 to dwa bajty: 42 i 49). Zapis bajtów parami jest stosowany jedynie dla czytelności. Dzięki temu, że w każdej linijce znajduje się dokładnie taka sama liczba bajtów, będziemy mogli weryfikować część z naszych eksperymentów z plikami.

Pierwszy kod

Pora napisać pierwszy kod. Wybrałem język Ruby (ponieważ jest to mój ulubiony język do pisania programów).

Na pewno przyda się podzielić cały plik na pojedyncze bajty tak, aby potem móc na nich wygodnie pracować. Chcielibyśmy zachować zapis w formacie szesnastkowym, ponieważ jest on dla nas na tym etapie wystarczająco czytelny.

Niestety, nie można po prostu otworzyć takiego pliku (binarnego) i pociąć całej treści na pary znaków (o czym się boleśnie przekonałem). Skończymy wtedy bowiem z informacją zapisaną w postaci stringów: \x2050 – i bynajmniej nie jest to szczególnie wygodna forma do pracy.

Można jednak otworzyć plik jako plik binarny, a następnie pobierać pojedyncze bajty. Wymagane także będzie od razu przekonwertowanie ich do postaci szesnastkowej.

file = ‘data/ITEMS.BIF’ bytes = [] File.open( file, 'rb' ) do |f| f.each_byte do |b| b = b.to_i.to_s(16) # make sure every block has two chars b = '0' + b if b.length == 1 bytes << b end end

Cóż, nasze bajty w pliku też nie są szczególnie uporządkowane, dlatego wrzucimy je „tak jak lecą” do tablicy. Uzupełnimy je także o zera w taki sposób, żeby zawsze składały się z dwóch znaków – dla czytelności.

Nasze pobrane bajty będą tekstem.
W celu konwersji danych na system szesnastkowy wykorzystamy funkcję Rubiego do konwertowania typu (liczby) na string: to_s. Jako argument przyjmuje ona system, w jakim liczba powinna być zapisana (czyli automatycznie dokonuje przeliczenia systemu). Niestety, nie możemy wywołać to_s na stringu, więc najpierw musimy nasz bajt przekonwertować na liczbę: to_i (system dziesiętny). Możemy teraz przeliczyć liczbę na system szesnastkowy.

W tej chwili w tablicy „bytes” posiadamy dokładnie to, co widzieliśmy na ekranie edytora tekstu, a każdy bajt zapisany jest w osobnej komórce w tabeli.

Co dalej?

Wykorzystujemy pracę innych ludzi

Cóż, same bajty niewielką nam dają informację o zawartości pliku, choć gdybyśmy je wszystkie przekonwertowali na litery ASCII (każda litera to powiem liczba w formacie ASCII, a nasze bajty to przecież liczby!), to co nieco można odczytać, np. „BOW01” (identyfikator jednego z łuków w grze). Jesteśmy więc na dobrym tropie.

Pora na kolejny research. Trafiamy na kilka stron, na których znajdziemy piekną interpretację pliku. Na początku wybrałem stronę starą, niekompletną, ale ostatecznie znalazłem:

http://gibberlings3.net/iesdp/file_formats/ie_formats/

na której jest największy (chyba) zbiór informacji dot. plików.

Znajdźmy format .biff:

http://gibberlings3.net/iesdp/file_formats/ie_formats/bif_v1.htm

Jak widać cały plik podzielony jest na trzy główne sekcje (Header, File Entries oraz Tileset Entried). Cokolwiek to znaczy.

Każda sekcja podzielona jest na kolejne podsekcje, z których każda posiada offset i size. O ile size (rozmiar) jestem w stanie zrozumieć, to mogę się tylko domyślać, o co chodzi z offsetem czy typem rozmiaru (dword, word, char array). Oj, jednak znajomość C++ mogłaby mi się teraz przydać, a ja od zawsze siedzę na językach wyższego poziomu (abstrakcji).

No dobrze, te typy rozmiaru sobie odpuśmy, wrócimy do nich kiedy indziej.

Nagłówek (Header) naszego pliku posiada:
- Signature
- Version
- Count of file entries
- Count of tileset entries
- Offset to file entries

Jak się (słusznie) domyśliłem, offsety zapisane są również w postaci szesnastkowej (0x na początku). Pierwsze, co zrobiłem, to przekonwertowałem przykładowy offset na liczbę w systemie dziesiętnym:

(offset Version) „0004”.to_i(16) => 4

Nieszczególna niespodzianka. Zakładam, że otrzymana liczba 4 to bajt w pliku, od którego zaczyna się dany element (version) nagłówka (header).

W edytorze odliczam sobie ten offset i kopiuję 4 następne bajty (ponieważ size = 4):
5631 2020
i przy wykorzystaniu metody pack zamieniam te bajty na tekst ASCII: ["56312020"].pack('H*') => "V1 "

To już coś. Otrzymaliśmy dokładnie to, co podane jest w naszej rozpisce danych.

Co dalej?

Zrozumieliśmy, technikę leżącą u podstaw zapisu plików w Infinity Engine.
Nauczyliśmy się wgrywać te pliki do pamięci programu i zapisywać je w wygodnej formie (formie bezpośrednio związanej z oryginałem i nie fałszującej danych – co jest bardzo ważne, jak się przekonamy).
Znaleźliśmy bazę wiedzy, dzięki której będziemy w stanie odszyfrować i uporządkować dane w każdym z formatów plików.
Rozgryźliśmy offset i size (choć na razie bez typu size’u).

Kolejnym krokiem (oczywiście w kolejnym wpisie) będzie zamiana danych w pliku na dane uporządkowane według wzorca z naszej bazy wiedzy.

Aktualny kod projektu (troszkę do przodu) znajduje się na :
https://github.com/soanvig/ie-ie 

programowanie gry

Komentarze

0 nowych
nintyfan   12 #1 27.01.2017 11:56

Mi się wydaje, że NeverWinter Nights także był oparty o infinity engine(choć mocno podrasowanym).

biomen   8 #2 27.01.2017 12:03

@nintyfan
Mylisz się, Neverwinter to zupełnie inna technologia. Render w trzech wymiarach, moduł sieciowy do kooperacji i wsparcie dla modyfikacji.

O wiele bliżej pierwszemu Wiedźminowi do Neverwinter niż takiemu Tormentowi. ;)

nintyfan   12 #3 27.01.2017 13:33

@biomen: Miałem trochę racji. Za wikipedią: "Aurora Engine – silnik gry stworzony w 2002 roku przez firmę BioWare. Jest następcą Infinity Engine, stworzonego również przez BioWare."
Aurora jest silnikiem NeverWinter Nights.

stasinek   12 #4 27.01.2017 18:11

@nintyfan: "troche miałeś racji" jak 2=3?

Autor edytował komentarz w dniu: 27.01.2017 20:06
tarantul   4 #5 28.01.2017 16:47

@stasinek: czemu się dziwisz? To określenie bardzo popularne w informatyce praktycznej, tak samo jak "trochę działa". :)