Hallo!
Das hier ist ein Crosspost, da ich den Thread am Sonntag schon im Delphi-Forum erstellt habe. Leider bekam ich dort bis dato keine Antwort die mir weiterhelfen konnte, deswegen versuche ich es hier nochmal. Ich versuche das Problem so gut wie möglich zu erklären:
Es geht um die Replaydateien eines Computerspiels (Warcraft III). Die Replaydateien kann man mit dem Hauptprogramm abspielen und sich so Spiele von anderen Leuten ansehen. Ich möchte ein Programm schreiben, dass ein paar interessante Daten aus den Replaydateien ausliest. Z.B. die Spieler, die Karte auf der gespielt wurde etc.
Es gibt zu diesen Replayfiles von Warcraft eine herrvoragende Dokumentation.
In dieser steht, dass der Header der Dateien so aufgebaut ist:
Code:
offset | size/type | Description
-------+-----------+-----------------------------------------------------------
0x0000 | 28 chars | zero terminated string "Warcraft III recorded game\0x1A\0"
0x001c | 1 dword | fileoffset of first compressed data block (header size)
| | 0x40 for WarCraft III with patch <= v1.06
| | 0x44 for WarCraft III patch >= 1.07 and TFT replays
0x0020 | 1 dword | overall size of compressed file
0x0024 | 1 dword | replay header version:
| | 0x00 for WarCraft III with patch <= 1.06
| | 0x01 for WarCraft III patch >= 1.07 and TFT replays
0x0028 | 1 dword | overall size of decompressed data (excluding header)
0x002c | 1 dword | number of compressed data blocks in file
0x0030 | n bytes | SubHeader (see section 2.1 and 2.2)
Diese Daten aus dem Header und Subheader lese ich mit folgendem Code aus:
Delphi-Quellcode:
var DATA : dword;
hFile : Thandle;
begin
hFile := FileOpen('c:\test\replay.w3g', $0000);
FileSeek(hFile, integer($20), 0);
FileRead(hFile, DATA, sizeof(dword));
memo1.lines.add(inttostr(Data));
end;
Das funktioniert soweit, mit diesem Code würde ich z.B. den dword-Wert an Offset 0x0020 auslesen.
Der Subheader beginnt ja dann ab 0x0030 und endet bei 0x044.
Wenn ich z.B.: den Wert an Offset 0x002 des Subheaders auslesen möchte, dann komm ich an die Adresse mittels:
FileSeek(hFile, integer($30+$2), 0);
Soweit so gut, diese Dinge funktionieren.
Jetzt komme ich in der Dokumentation aber an einen Punkt an dem ich absolut nichts mehr verstehe. Zur Erläuterung eine kurze Zusammenfassung, was die Dokumenation aussagt (Ihr könnt sie euch auch gerne selbst anschauen wenn ihr wollt, sie ist unten angehängt).
Also, die Dokumentation sagt folgendes über Rohbau der Datei:
1.) Header geht bist 0x030
2.) Subheader geht bis 0x044
3.) Data Blocks
Über die Data Blocks an 3.) steht in der Doku wortwörtlich folgendes:
Code:
===============================================================================
3.0 [Data block header]
===============================================================================
Each compressed data block consists of a header followed by compressed data.
The first data block starts at the address denoted in the replay file header.
All following addresses are relative to the start of the data block header.
The decompressed data blocks append to a single continueous data stream
(disregarding the block headers). The content of this stream (see section 4) is
completely independent of the original block boundaries.
offset | size/type | Description
-------+-----------+-----------------------------------------------------------
0x0000 | 1 word | size n of compressed data block (excluding header)
0x0002 | 1 word | size of decompressed data block (currently 8k)
0x0004 | 1 dword | unknown (probably checksum)
0x0008 | n bytes | compressed data (decompress using
zlib)
Also, ab 0x044 (nach dem 2. Header) folgen x-viele compressed Data Blocks.
Jeder Data Block hat einen Header, der aufgebaut ist wie oben angegeben.
So, damit ihr versteht was einen erwarten soll, wenn man die compressed Data dekomprimiert hat (oder wie auch immer man da ran kommt), ist hier der nächste Teil aus der Doku, der beschreibt wie man mit dem dekomprimierten Data Block arbeiten soll:
Code:
===============================================================================
4.0 [Decompressed data]
===============================================================================
Decompressed data is a collection of data items that appear back to back in
the stream. The offsets for these items vary depending on the size of every
single item.
This section describes the records that always appear at the beginning of
a replay data stream. They hold information about settings and players right
before the start of the game. Data about the game in progress is described
in section 5.
The order of the start up items is as follows:
# | Size | Name
---+----------+--------------------------
1 | 4 byte | Unknown (0x00000110 - another record id?)
2 | variable | PlayerRecord (see 4.1)
3 | variable | GameName (null terminated string) (see 4.2)
4 | 1 byte | Nullbyte
5 | variable | Encoded String (null terminated) (see 4.3)
| | - GameSettings (see 4.4)
| | - Map&CreatorName (see 4.5)
6 | 4 byte | PlayerCount (see 4.6)
7 | 4 byte | GameType (see 4.7)
8 | 4 byte | LanguageID (see 4.8)
9 | variable | PlayerList (see 4.9)
10 | variable | GameStartRecord (see 4.11)
The following sections describe these items in detail.
After the static items (as described above) there follow variable information
organized in blocks that are described in section 5.
So, was mich jetzt zuerst mal verblüfft ist, dass hier nicht mehr wie bei den Headern die Offsets angegeben werden, ich also selbst wenn ich den Data Block dekomprimieren könnte, nicht wüsste wie ich auf diese Daten dann zugreifen kann.
Der User Sinspin auf dem Delphi-Forum hat mir dazu diesen Tipp gegeben:
Zitat von
Sinspin (Delphi-Forum.de):
Stream oder String stellt hier keinen Unterschied dar.
Du nimmst einfach die Daten die du entschlüsseln willst und speicherst sie in einen String, dekomprimierst diesem mit
zlib und arbeitest mit dem Ergebnis weiter. Dazu könntest du es ja auch wieder in eine datei speichern, so das du das gleiche vorgehen für die Zugriffe hast, das dir schon bekannt ist.
Anhand dieses Hinweis habe ich mir folgenden Ansatz zurechtgelegt:
1.) Grösse der zu lesenden Bytes aus dem Header auslesen
Da der Subheader an 0x044 endet, müsste ja der Header des ersten Datablocks an dieser Adresse beginnen. Zu diesem Header stand in der Dokumentation ja:
Code:
0x0000 | 1 word | size n of compressed data block (excluding header)
Also als allererste 1 word in dem die Grösse der compressed Data steht.
Mit diesem Code könnte ich die Data auslesen, wenn da nicht irgendwo ein Fehler steckt:
Delphi-Quellcode:
var compresseddatasize : word;
begin
FileSeek(hFile, integer($44), 0);
FileRead(hFile, compresseddatasize, sizeof(word));
end;
Ich nehme diesen Wert $44 an, da ich vorhin ja auch mit $30+$02 auf den Wert an offset $02 des Subheaders zugreifen konnte. Zur Erinnerung: Der Header endet bei $30, der Subheader endet bei $44.
2.) Compressed Data aus der Datei auslesen
Ich müsste jetzt ja theoretisch wissen, wie gross die compressed Data in dem Data Block ist. Und ich weiss auch wo die compressed Data beginnt. Zur Erinnerung: Im Compressed-Data-Header stand:
Code:
0x0008 | n bytes | compressed data (decompress using
zlib)
Also weiss ich, dass ab ($44+$08) die Compressed Data beginnt.
Wenn ich wieder genau denselben Code anwende wie ganz am Anfang, um den normalen Header auszulesen:
Delphi-Quellcode:
var compressedData : string;
begin
fileseek(hfile, integer($44+$08), 0);
fileread(hfile, compressedDATA, compresseddatasize); //compresseddatasize habe ich ja vorhin aus dem Header gelesen.
end;
Aufgrund von Sinspins Hinweis, und der Tatsache, dass ich
zLib-dekomprimierung nur in Verbindung mit Strings gefunden habe, bin ich davon ausgegangen, dass der Datentyp für die compressedData String sein sollte.
Den string compressedData habe ich dann durch einen in eurer Code-Library gefundenen Quellcode gejagt:
Delphi-Quellcode:
function DeCompressString(input:string):string;
var
InpBuf, OutBuf: Pointer;
OutBytes: Integer;
begin
InpBuf := nil;
OutBuf := nil;
try
GetMem(InpBuf, Length(input));
Move(input[1], InpBuf^, Length(input));
DeCompressBuf(InpBuf, Length(input),0,OutBuf, OutBytes);
SetLength(result,OutBytes);
Move(OutBuf^, result[1], OutBytes);
finally
if InpBuf <> nil then FreeMem(InpBuf);
if OutBuf <> nil then FreeMem(OutBuf);
end;
end;
Es tritt eine AccessViolation auf.
Das bringt folgende Rückschlüsse:
Möglichkeit 1) Mein kompletter Ansatz ist falsch.
Möglichkeit 2) Meine ausgelesene Grösse des compressed Data Blocks ist falsch, weil
a) Die Adresse an der ich auslese falsch ist
b) Die Grössenangabe zuerst umgerechnet werden muss o.ä.
Möglichkeit 3) Die compressed Data wird falsch ausgelesen, weil
a) Die Adresse an der ich auslese falsch ist
b) die Grössenangabe die ausgelesen wurde falsch ist
c) Der Datentyp in den ich einlese (String) falsch ist
Möglichkeit 4) Die Funktion zum dekomprimieren mit
zLib ist nicht auf diesen Fall anwendbar (geht davon aus, dass in 1-3 keine Fehler sind).
Ad. Dekomprimierung mit
zLib:
In der Dokumenation ist zum Dekomprimieren dieser Hinweis gegeben:
Code:
To decompress one block with
zlib:
1. call 'inflate_init'
2. call 'inflate' with Z_SYNC_FLUSH for the block
The last block is padded with 0 bytes up to the 8K border. These bytes can
be disregarded.
Ich kann damit auch mit Googlen absolut nicht anfangen.
Okay, ich hoffe ich hab mich halbwegs deutlich ausgedrückt. Im Anhang noch die Dokumentation zu dem Dateiformat, allerdings sollte ich alles was mit meinem Problem zu tun hat hier gepostet haben.
Vielen Dank für Hilfe.