Ok, postanowiłem poświęcić kilka chwil na analizę tego pliku. Jako że temat może zainteresować kogoś, będę starał się opisać co kolejno robię i dlaczego. Nie gwarantuję, że skończy się całość udanym ekstraktorem (a z pewnością nie tej nocy), ale opis procesu i tak może być dla kogoś pomocny w przyszłości.
Zacząłem od lektury forum gry w poszukiwaniu zrzutów ekranu. Dobrze jest wiedzieć jakiej grafiki się w pliku szuka. Napisałem prosty program traktujący cały plik SPR jako RAW 32 x ileśtam i wyświetliłem wynik. Wyglądało to mniej więcej tak:

Oglądałem te źle wyglądające sprajty, aż natrafiłem na coś, co było widać na jednym ze zrzutów ekranu:

W prawym górnym rogu widać kamienną podłogę z kafli. Przesunięty odpowiednio viewer pokazał mi coś takiego:

Zatem sprajty na zrzucie to odpowiednik:

Pierwotnie chciałem porównać wartości kolorów z pliku do wartości kolorów na zrzucie, pojawiły się jednak 3 problemy:
- grafika na zrzucie jest rozciągnięta (sprajty 32x32 z pliku mają 64x64 na zrzucie) przy użyciu (prawdopodobnie) liniowej interpolacji, co zniekształca kolory i porównanie nie jest możliwe
- grafika w grze jest cieniowana przy użyciu źródeł światła (gracz jest jednym z nich), co zniekształca kolory i porównanie nie jest możliwe
- zrzut ekranu był w JPG, co zniekształca kolory i porównanie nie jest możliwe
Jakiekolwiek próby porównania barw odpadają zatem. Daje się jednak zauważyć, że trzy widoczne w pliku SPR sprajty, w przeciwieństwie do tego co widać na zrzucie, mają zupełnie różne odcienie. Może to zatem oznaczać, że plik jest w jakiś prymitywny sposób zakodowany. Na tą chwilę zakładam, że wartości w pliku są XORowane albo dodawana jest jakaś stała. Wartość dla całego sprajta musiałaby być stała, bo sprajt mimo wszystko ma jednorodną barwę.
Zakładając, że mam rację, co w takim razie trzeba zrobić? Znaleźć różnicę między tymi trzema sprajtami w pliku. Ze zrzutu ekranowego wynika, że powinny się w sumie różnić wyłącznie rysą, więc górny brzeg powinny mieć mniej więcej identyczne (oczywiście poza miejscami, w których rysa zahacza o brzeg).
Przesunięcie pierwszego sprajta w pliku to 640556 czyli 0x9C62C. Drugiego to 643633 czyli 0x9D231 a trzeciego to 646710 czyli 0x9DE36. Ktoś, kto liczy uważnie zauważy, że różnica offsetu w pliku między tymi sprajtami 32x32 to nie 3*1024 (czyli 3 bajty na kolor, sprajt 32x32=1024) a 3*1024+5. Oznacza to, że przed lub za każdym sprajtem znajduje się 5 bajtów z jakimiś informacjami. Zakładam, że przed, opiszę za chwilę dlaczego.
Zobaczmy w takim razie początki tych sprajtów. Poniżej 5 bajtów przed sprajtem i pierwszych 10 pikseli (30 bajtów)
** Pierwszy sprajt **
- Pięć bajtów przed nim
68 42 01 E3 E3
- Pierwszych 30 bajtów grafiki
AF AE A2 | AF AE A2 | 5A 59 4E | 5A 59 4E | 5A 59 4E | 5A 59 4E | 5A 59 4E | 5A 59 4E | BA B9 AE | AF AE A2
** Drugi sprajt **
- Pięć bajtów przed nim
13 43 01 14 14
- Pierwszych 30 bajtów grafiki
58 59 55 | 58 59 55 | AD AE B9 | AD AE B9 | AD AE B9 | AD AE B9 | AD AE B9 | AD AE B9 | 4D 4E 59 | 58 59 55
** Trzeci sprajt **
- Pięć bajtów przed nim
0B 44 01 C1 C1
- Pierwszych 30 bajtów grafiki
8D 8C 80 | 8D 8C 80 | 78 7B 6C | 78 7B 6C | 78 7B 6C | 78 7B 6C | 78 7B 6C | 78 7B 6C | 98 9B 8C | 8D 8C 80
Jeśli miałem rację, to powinien się powtarzać wzorzec kolorów. I tak właśnie jest, bo mamy wzorzec: AABBBBBBCA. Zanim jednak ktoś powie: aha! A nie mówiłem?! Plik jest skompresowany. Nie jest (a przynajmniej nie to, co widać do tej pory). Jest tylko badziewnie zakodowany, żeby utrudnić jego czytanie osobom postronnym. Najprawdopodobniej po to.
Zanim przejdę dalej, wrócę do mojej wcześniejszej teorii mówiącej o tym, że to wariacja na temat formatu Tibii. W SPR Tibii występują dwa rodzaje pikseli: barwne i ciąg przezroczyste. Opisane są one jako coś na kształt strumieni: najpierw pojawia się informacja o długości strumienia, później dane strumienia (oczywiście strumień przezroczystości jest pusty). Opisane to zostało tutaj:
http://otfans.net/showthread.php?t=141982Każdy kafel to:
3B - przeznaczenie nieznane
2B - rozmiar sprajta
(
2B - ilość przezroczystych pikseli
2B - ilość kolorowych pikseli (P)
3B * P - dane kolorowych pikseli
)
Jest to bardzo prymitywna wersja bardzo prymitywnego algorytmu kompresji, RLE. Oznacza to, że nieprzezroczyste sprajty (teren) mają 3*32*32 bajtów, podczas kiedy przezroczyste (broń, przeciwnicy i inne obiekty) oszczędzają nieco miejsca. Widać to na pierwszym zrzucie ekranu z programu, mniej więcej w połowie jego wysokości ilustracji (trzy niższe niż reszta sprajty).
Zobaczmy w takim razie jakie są różnice między sprajtami. Zanim jednak to zrobię, posłużę się małym oszustwem. Wszystkie z trzech analizowanych sprajtów mają jednolitą powierzchnię w prawej górnej ćwiartce. Pozwala to na pobranie z obrazu przybliżonej barwy tego miejsca mimo kompresji JPG (trafiamy bowiem na względnie jednolity blok 8x8).

I tak otrzymujemy:
2E 2F 29 - barwa ze zrzutu ekranu
AF AE A2 - barwa z pierwszego sprajta
88 89 85 - barwa z drugiego sprajta
8D 8C 80 - barwa z trzeciego sprajta
Tak swoją drogą to ze stosunku R do G do B w sprajtach można wnioskować, że nie mamy do czynienia z przesunięciem wartości o stałą a z XORem właśnie. Bo 0xAF - 0xAE = 1, ale już 0x88 - 0x89 = -1. Podobnie 0xAE - 0xA2 = 12 ale 0x89 - 0x85 = 4.
Spróbujmy w takim razie znaleźć wartość kodu zakładając, że chcemy uzyskać kolor ze zrzutu ekranu.
AF ^ 2E = 81
AE ^ 2F = 81
A2 ^ 29 = 8B
88 ^ 2E = A6
89 ^ 2F = A6
85 ^ 29 = AC
8D ^ 2E = A3
8C ^ 2F = A3
80 ^ 29 = A9
Wygląda na to, że ta sama wartość jest używana do XORowania całego sprajta (
odległość Hamminga między wynikami 81-8B, A6-AC i A3-A9 to zawsze 2, co tłumaczy przekłamania związane z kompresją JPG i przyciemnianiem sprajtów w grze). Kod niestety nie jest zbliżony do żadnej wartości z 5 bajtów poprzedzających sprajt (zbliżony znowu w sensie odległości Hamminga), więc nie sądzę by któraś z wartości przed sprajtem była kodem. Nic też nie wskazuje na to, że kod zwiększa się o stałą wartość co sprajt.
Do analizy pliku wrócę niebawem. Jeśli ktoś ma jakieś uwagi, pomysły, albo chce pociągnąć to dalej, zapraszam.
//edyta
Już wiem jak to jest kodowane.

Podpowiem, że ((20 ^ E3) ^ AF) == ((20 ^ 14) ^ 58) == ((20 ^ C1) ^ 8D).