AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Win32/Win64 API (native code) Delphi Crosspost: Compressed Data in Datei auslesen
Thema durchsuchen
Ansicht
Themen-Optionen

Crosspost: Compressed Data in Datei auslesen

Offene Frage von "Karlson"
Ein Thema von Karlson · begonnen am 23. Apr 2008 · letzter Beitrag vom 25. Apr 2008
Antwort Antwort
Karlson

Registriert seit: 12. Apr 2004
92 Beiträge
 
#1

Crosspost: Compressed Data in Datei auslesen

  Alt 23. Apr 2008, 00:08
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.
Angehängte Dateien
Dateityp: txt w3g_format_934.txt (57,2 KB, 4x aufgerufen)
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.679 Beiträge
 
Delphi 2007 Enterprise
 
#2

Re: Crosspost: Compressed Data in Datei auslesen

  Alt 23. Apr 2008, 02:05
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



Morgen mit offenen Augen aber erstmal alles lesen und verstehen
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Benutzerbild von toms
toms
(CodeLib-Manager)

Registriert seit: 10. Jun 2002
4.648 Beiträge
 
Delphi XE Professional
 
#3

Re: Crosspost: Compressed Data in Datei auslesen

  Alt 23. Apr 2008, 05:17
Hallo,

Vielleicht gibt die UnitIdCompressionIntercept.pas ein wenig Aufschluss darüber, wie inflateInit etc. zu verwenden ist oder die ZlibEx im Anhang, insb:

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;
Angehängte Dateien
Dateityp: pas zlibex_192.pas (28,7 KB, 4x aufgerufen)
Thomas
  Mit Zitat antworten Zitat
Benutzerbild von nicodex
nicodex

Registriert seit: 2. Jan 2008
Ort: Darmstadt
286 Beiträge
 
Delphi 2007 Professional
 
#4

Re: Crosspost: Compressed Data in Datei auslesen

  Alt 23. Apr 2008, 07:30
Ich habe ein Tool für Gothic 3 entwickelt (damals in Eile zusammengehackt):
http://www.bendlins.de/nico/gothic3/ die g3pak-0.0.0.9_src.7z

Der interessante Quellcodeauszug ist:
Delphi-Quellcode:
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;
ps: Man kann für DstStrm natürlich andere Streams verwenden (TMemoryStream, wenn man genug Speicher hat).

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], ...
  Mit Zitat antworten Zitat
Karlson

Registriert seit: 12. Apr 2004
92 Beiträge
 
#5

Re: Crosspost: Compressed Data in Datei auslesen

  Alt 24. Apr 2008, 22:48
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. http://www.delphi-forum.de/viewtopic...=499136#499136

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

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.
  Mit Zitat antworten Zitat
Benutzerbild von nicodex
nicodex

Registriert seit: 2. Jan 2008
Ort: Darmstadt
286 Beiträge
 
Delphi 2007 Professional
 
#6

Re: Crosspost: Compressed Data in Datei auslesen

  Alt 25. Apr 2008, 06:54
Die Anhänge im DF können nur durch Mitglieder heruntergeladen werden.
Es wäre hilfreich, wenn das Archiv auch hier angehängt wird...
  Mit Zitat antworten Zitat
Dr. Phlox

Registriert seit: 4. Jan 2008
2 Beiträge
 
#7

Re: Crosspost: Compressed Data in Datei auslesen

  Alt 25. Apr 2008, 11:38
(Ich bin Martok, ich heiß hier nur anders, weil einer meinte auch 'martok' heißen zu müssen...)

Zitat von Karlson:
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
Ich muss dich enttäuschen, tut er nicht... Copy&Paste aus /source/rtl/common/zlib.pas
Ü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 von nicodex:
Die Anhänge im DF können nur durch Mitglieder heruntergeladen werden.
Es wäre hilfreich, wenn das Archiv auch hier angehängt wird...
Kann ich machen... bitteschön.
Auch hier wieder der Hinweis: nicht für den Programmierstil hauen, das ist nur mal eben hingehackt.
Angehängte Dateien
Dateityp: zip w3_replay_893.zip (218,4 KB, 4x aufgerufen)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 23:49 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz