![]() |
Crosspost: Compressed Data in Datei auslesen
Liste der Anhänge anzeigen (Anzahl: 1)
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:
Diese Daten aus dem Header und Subheader lese ich mit folgendem Code aus:
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)
Delphi-Quellcode:
Das funktioniert soweit, mit diesem Code würde ich z.B. den dword-Wert an Offset 0x0020 auslesen.
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; 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:
Delphi-Quellcode:
Soweit so gut, diese Dinge funktionieren.
FileSeek(hFile, integer($30+$2), 0);
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:
Also, ab 0x044 (nach dem 2. Header) folgen x-viele compressed Data Blocks.
===============================================================================
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) 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:
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:
Also als allererste 1 word in dem die Grösse der compressed Data steht.
0x0000 | 1 word | size n of compressed data block (excluding header)
Mit diesem Code könnte ich die Data auslesen, wenn da nicht irgendwo ein Fehler steckt:
Delphi-Quellcode:
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.
var compresseddatasize : word;
begin FileSeek(hFile, integer($44), 0); FileRead(hFile, compresseddatasize, sizeof(word)); end; 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:
Also weiss ich, dass ab ($44+$08) die Compressed Data beginnt.
0x0008 | n bytes | compressed data (decompress using zlib)
Wenn ich wieder genau denselben Code anwende wie ganz am Anfang, um den normalen Header auszulesen:
Delphi-Quellcode:
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.
var compressedData : string;
begin fileseek(hfile, integer($44+$08), 0); fileread(hfile, compressedDATA, compresseddatasize); //compresseddatasize habe ich ja vorhin aus dem Header gelesen. end; Den string compressedData habe ich dann durch einen in eurer Code-Library gefundenen Quellcode gejagt:
Delphi-Quellcode:
Es tritt eine AccessViolation auf.
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; 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:
Ich kann damit auch mit Googlen absolut nicht anfangen.
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. 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. |
Re: Crosspost: Compressed Data in Datei auslesen
Wow. Auch wenn mir auf Grund der Uhrzeit, und damit dem weitestgehend bereits schlafenden Hirn lediglich die Verwendung von String als Container für binäre Daten mehr als Spanisch vorkommt, so will ich zumindest schon einmal los werden, dass allein schon die Art und Weise der Fragestellung und Offenlegung der bisherigen Ansätze Beispielcharakter hat!
Der Beitrag könnte glatt als Tutorial für "Wie stelle ich eine Frage" als Pflichtlektüre in die Anmeldung der DP aufgenommen werden. Ich bin schier entzückt :love: Morgen mit offenen Augen aber erstmal alles lesen und verstehen ;) |
Re: Crosspost: Compressed Data in Datei auslesen
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,
Vielleicht gibt die Unit ![]()
Delphi-Quellcode:
procedure ZDecompress(const inBuffer: Pointer; inSize: Integer;
out outBuffer: Pointer; out outSize: Integer; outEstimate: Integer); var zstream: TZStreamRec; delta: Integer; begin FillChar(zstream, SizeOf(TZStreamRec), 0); delta := (inSize + 255) and not 255; if outEstimate = 0 then outSize := delta else outSize := outEstimate; GetMem(outBuffer, outSize); try zstream.next_in := inBuffer; zstream.avail_in := inSize; zstream.next_out := outBuffer; zstream.avail_out := outSize; ZDecompressCheck(InflateInit(zstream)); try while ZDecompressCheck(inflate(zstream, Z_NO_FLUSH)) <> Z_STREAM_END do begin Inc(outSize, delta); ReallocMem(outBuffer, outSize); zstream.next_out := PChar(Integer(outBuffer) + zstream.total_out); zstream.avail_out := delta; end; finally ZDecompressCheck(inflateEnd(zstream)); end; ReallocMem(outBuffer, zstream.total_out); outSize := zstream.total_out; except FreeMem(outBuffer); raise; end; end; |
Re: Crosspost: Compressed Data in Datei auslesen
Ich habe ein Tool für Gothic 3 entwickelt (damals in Eile zusammengehackt):
![]() Der interessante Quellcodeauszug ist:
Delphi-Quellcode:
ps: Man kann für DstStrm natürlich andere Streams verwenden (TMemoryStream, wenn man genug Speicher hat).
SrcStrm := TFileStream.Create({SrcFileName}, fmOpenRead or fmShareDenyWrite);
try SrcStrm.Seek({SrcOffset}, soFromBeginning); DstStrm := TFileStream.Create({DstFileName}, fmCreate or fmShareDenyWrite); try if {SrcSize} <= 0 then Exit; if {SrcIsCompressed} then begin ZipStrm := TZDecompressionStream.Create(SrcStrm); try SrcStrm.Seek({SrcOffset}, soFromBeginning); DstStrm.CopyFrom(ZipStrm, {SrcSize}) finally ZipStrm.Free(); end; end else DstStrm.CopyFrom(SrcStrm, {SrcSize}); finally DstStrm.Free(); end; finally SrcStrm.Free(); end; Die 'variablen Strukturen' sind nicht ungewöhnlich. Das hängt damit zusammen, dass die Daten sequenziell gelesen/geschrieben werden. Beispiel für eine einfache Persistenz: (string)[size, [data[size]], (integer)[data], ... |
Re: Crosspost: Compressed Data in Datei auslesen
Okay vielen Dank für eure Antworten! Danke auch an Medium für das Kompliment. Eigentlich sollte jedem klar sein: Je besser die Frage, desto besser die Antwort :)
So, jetzt ist ein Fall eingetreten der mir Leid tut, mit dem ich auch nicht gerechnet hätte: Im DF wurde zwischenzeitlich doch geantwortet und der User Martok aus dem DF hat ein Beispielprojekt zusammengebaut. ![]() Mit diesem Beispielprojekt bin ich jetzt auf dem Stand, dass ich den ersten compressed Data Block entpackt bekomme. Der relevante Quelltext scheint übrigens grösstenteils auf dem von nicodex geposteten zu basieren. Das nenn ich mal Forenübergreifenden Teamwork :mrgreen: Also auf jedenfall ist das schon mal super. Aber so wirklich glücklich bin ich noch nicht, da ich den Quelltext noch kaum verstande habe und mir jetzt der Ansatz fehlt wie ich weitermachen kann. Wie könnte ich denn jetzt z.B. die Spielernamen auslesen? Mit der Zugriffsart wie oben funktioniert es ja nicht mehr. So, also ich muss mich erstmal ein bisschen mit der neuen Situation auseinandersetzen. Ich würde es dann so machen, dass ich mich in dem Thread im DF herzlichst bedanke und darauf verweise, dass der Thread jetzt hier weitergeht, damit nicht wieder parallel diskutiert wird. Lg. |
Re: Crosspost: Compressed Data in Datei auslesen
Die Anhänge im DF können nur durch Mitglieder heruntergeladen werden.
Es wäre hilfreich, wenn das Archiv auch hier angehängt wird... |
Re: Crosspost: Compressed Data in Datei auslesen
Liste der Anhänge anzeigen (Anzahl: 1)
(Ich bin Martok, ich heiß hier nur anders, weil einer meinte auch 'martok' heißen zu müssen...)
Zitat:
Übrignes seh ich keine Ähnlichkeit(Nicos Code hätte nicht funktioniert), am ehesten noch mit toms' Code. Oder meinst du die Verwendung von Streams? Naja, eigentlich auch egal. Zitat:
Auch hier wieder der Hinweis: nicht für den Programmierstil hauen, das ist nur mal eben hingehackt. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:40 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz