← ST-Computer 11 / 1991

Struktur eines ZOO-Archivs

Grundlagen

Das Auflisten sowie das Entpacken der archivierten Dateien eines Zoo Archivs ist mittels den beigefĂŒgten Shells nicht sehr komfortabel. Da ich aber nunmal mit dem Programm arbeite, suchte ich nach einer etwas angenehmeren Lösung.

Es mußte also eine Routine her mit der man die EintrĂ€ge eines Archivs anderweitig verarbeiten könnte, z.B. Aufbereitung fĂŒr ein Datenbankprogramm, EintrĂ€ge auszudrucken oder eine eigene Shell, die eine leichtere Auswahl der EintrĂ€ge zulĂ€ĂŸt.

Aufbau

Um die EintrĂ€ge eines Archivs anderweitig verarbeiten zu können mĂŒssen wir uns erst den Aufbau eines Archivs genauer betrachten. Uns soll hier nicht die Art und Weise der Packmethode interessieren, darauf wurde schon an anderer Stelle nĂ€her eingegangen. Hier geht es nunmehr um den grundsĂ€tzlichen Aufbau.

Den Anfang eines Archivs bildet ein sog. Header. Dem anschließend folgen die archivierten Programme oder Dateien, unterteilt in Directory-EintrĂ€gen mit den dazu gehörigen, gepackten File-Daten. Ein komplettes Archiv setzt sich also zusammen aus Header, Directory-Eintrag, File-Daten, Directory-Eintrag, File-Daten usw.

Der Header

Soweit der globale Überblick ĂŒber die Struktur eines ZOO-Archivs. An dieser Stelle möchte ich darauf hin weisen das im Text nicht alle EintrĂ€ge besprochen werden, da einige fĂŒr sich selbst sprechen und sich in Tabellen besser machen. Des weiteren findet man EintrĂ€ge deren Zweck bzw. Inhalte ich nicht entschlĂŒsseln konnte. Darauf komme ich aber nochmal zurĂŒck.

Betrachten wir zunĂ€chst einmal den Header (Tabelle 1), der eine LĂ€nge von 42 Bytes belegt. In den ersten 20 Bytes eines Archivs befindet sich ein Headertext, der sich in der aktuellen Version „ZOO 2.00 Archive“ nennt. Der Text selbst hat fĂŒr die Archive keine Bedeutung, sondern bezieht sich nur auf die verwendete Programmversion mit der das Archiv bearbeitet wurde. Die minimale Version, die erforderlich ist um ein File zu entpacken, findet man ein paar Bytes weiter (Byte 32-33).

Wichtiger ist der in den Bytes 20-23 befindliche TAG (was soviel wie Schildchen oder Etikett bedeutet). Dieser TAG, mit dem Wert FDC4A7DC, dient zur eigentlichen Identifizierung des Archivs und findet sich auch nachher in allen Directory-EintrÀgen wieder.

Interessant fĂŒr unser weiter Vorgehen sind die Bytes 24-27, denn die enthalten eine Zeiger auf die eigentlichen Archivdaten. Lesen wir diesen Wert aus so erhĂ€lt man die Position des ersten Directorys mit Informationen ĂŒber die erste Archivierung.

Apropos Werte auslesen - dazu muß gesagt werden das alle EintrĂ€ge die mehr als ein Byte belegen im 8086er Format gespeichert sind. Diese mĂŒssen, bevor sie die richtigen Informationen liefern, erst umgedreht werden. So wird z.b. aus dem Wert DC A7 der Wert A7 DC.

Ein Blick auf die Directories

Diese sind vom Aufbau alle identisch. Sie unterscheiden sich nur durch eine unterschiedliche LÀnge aufgrund des Pfadnamens, wenn vorhanden. Dadurch ist es möglich die Routine in einer Schleife laufen zu lassen.

Im Header haben wir die Position des ersten Directoryeintrags ermittelt (Tabelle 2). Auf den ersten 4 Bytes treffen wir wieder auf den TAG , dessen Funktion ja nun bekannt sein dĂŒrfte. In dem Lang wort ab Byte 6 finden wir einen Zeiger der auf den nĂ€chsten Directory-Eintrag des Archivs weist. Man findet demnach im Header wie auch in jedem Directory-Eintrag einen Zeiger auf das jeweils nĂ€chste Archiv-Directory. Die EintrĂ€ge in den Bytes 14-15 sowie 16-17 enthalten Datum und Uhrzeit des archivierten Programmes und sind im DOS-Format gespeichert (Tabelle 3). Die Inhalte dieser EintrĂ€ge entsprechen denen des Desktops. Das auslesen der Werte geschieht wegen des DOS-For-mats ĂŒber Bit-Kombinationen (Listing 2).

Kommen wir nun zu den Kommentaren eines Archiveintrags. Diese sind nicht unbedingt ein Teil des Directorys, da der Inhalt der Kommentare nicht in NĂ€he des archivierten Files stehen muß. Was im Directory-Eintrag zu finden ist sind die Position und LĂ€nge des Kommentars. Ist kein Kommentar vorhanden ist der Inhalt beider EintrĂ€ge gleich Null.

BYTES INHALTE
0 - 19 Header Text
20 - 23 sogen. TAG FDC4A7DC
24 - 27 Startadr. der Archivdaten
28 - 31 Inv. Wert von Startadr.
32 - 33 min. Vers. zun entpacken der Files
34 Typ des Archivheaders (Inhalt 1)
35 - 38 Zeiger a. Archivkommentar
39 - 40 LĂ€nge des Kommentars
41 Ànderbar durch Gen. Limit

Tabelle 1: Inhalte des Headers

Nachfolgend zu den Kommentarinfos, also ab Byte 38, finden wir den Filenamen des archivierten Programmes mit einer festen LĂ€nge von 13 Bytes. Und zwar ohne Pfad, der folgt spĂ€ter. Damit kommen wir zum variablen Teil eines Directorys, wie oben schon erwĂ€hnt durch verschieden lange Pfadnamen. Dadurch verschieben sich natĂŒrlich auch die Positionen der nachfolgenden EintrĂ€ge. Die LĂ€nge des Pfades finden wir in Byte 57, den Pfadnamen ab Byte 58 bis ermittelte LĂ€nge. Wird ein Programm ohne Pfadnamen archiviert so hat die ermittelte LĂ€nge den Wert 2. Die max. LĂ€nge des Pfadnamens betrĂ€gt 256 Bytes. Der Filename sowie Pfadname sind reine ASCII-Daten und können somit ohne weitere Umwandlung verarbeitet werden.

Ich möchte noch erwÀhnen das sich die Bytepositionen in der Tabelle 2 auf den jeweils ermittelten Zeiger beziehen. So errechnen sich die Positionen der EintrÀge durch den im vorherigen Directory bzw. Header ermittelten Wert plus Anzahl der Bytes. Das ist im Listing nicht immer der Fall. Dort werden die meisten EintrÀge relativ zur Position des Datenzeigers ermittelt, weil es schneller ist.

„Fragezeichen“-EintrĂ€ge...

... sind die EintrĂ€ge die in den Tabellen kursiv dargestellt sind und nur der VollstĂ€ndigkeit halber aufgefĂŒhrt sind. Zum einen sind die Funktionen der Bytes selbst in der ZOO Dokumentation nicht genau erklĂ€rt, zum anderen war auch nach verschiedenen Archivierversuchen keine Änderung der Inhalte bzw. andere Werte als Null enthalten. Mit Ausnahme der Bytes 41 im Header sowie des Version Flag Bits im Directory, welche auf Änderung des Generation Limits eines Archiv reagieren (dessen Zweck ich aber auch noch nicht begreife). Deswegen kann zu diesen EintrĂ€gen, welche Funktion sie auch immer haben mögen, keine weiteren Angaben machen (die Funktion von den Fileattributen dĂŒrfte wohl klar sein, aber auch hier sind keine Inhalte zu vermelden). Allerdings haben diese EintrĂ€ge keinen Einfluß auf unsere Routine.

BYTES INHALTE
8-3 TAG s. Header
4 Typ des Directorys (Inhalt 0)
5 Packnethode 0=NO/1=LZW
6-9 Zeiger auf nÀchstes Directory
10 - 13 Position dieses Directory
14 - 15 Datun
16 - 17 Zeit beide DOS Fornat
18 - 19 CRC PrĂŒfsunne
28 - 23 OriginalgrĂ¶ĂŸe des Files
24 - 27 gepackte GrĂ¶ĂŸe des Files
28 - 29 min. Vers, zun entpacken der Files
30 File gelöscht 0=Nein/1=Ja
31 Filestruktur tInhelt 83
32 - 35 Zeiger auf Kommentar
36 - 37 LĂ€nge des Kommentars
38 - 50 Filenane 13 Bytes
51 - 52 LĂ€nge des Variablen Teils
53 immer 7F
54 - 55 CRC des Directorys
56 Inhalt 0
57 LĂ€nge des Pfadnamens
58 Pfadname lÀnge s. Byte 57

nach Pfadnamen:

Filesystem ID (0)
Fileattribute 24 Bits (0)
Vers. Flag Bit (Gen. Limit)
File Version Nummer (0)

Tabelle 2: Inhalte eines Directory-Eintrags

DATUM

   
Bit 0-4 Tag
Bit 5-8 Monat
Bit 9-15 Jahr + 88

ZEIT

   
Bit 0-4 Sek mal 2
Bit 5-10 Minuten
Bit 11-15 Stunden

Tabelle 3: Datum und Zeit Format

Ein bißchen DOS?

Nach soviel Theorie sollten wir nun zur Praxis ĂŒbergehen. Bevor wir jedoch zur eigentlichen Routine kommen noch ein paar Worte zum Listing 2. Da die Datum- und ZeiteintrĂ€ge im DOS-Format gespeichert sind mĂŒssen diese Bitorientiert gelesen werden. Der Procedure werden fĂŒr den zu ermittelten Wert jeweils das erste und letzte Bit sowie der Wert aus dem die Bitfolgen entnommen werden sollen ĂŒbergeben. Im Falle eines gesetzten Bits wird dieses mit der entsprechenden Wertigkeit multipliziert. ZurĂŒck erhalten wir das daraus resultierende Ergebnis. Diese Routine wird fĂŒr Datum und Zeit je dreimal aufgerufen. Zu beachten ist das das Jahr mit 80 addiert und die ermittelten Sekunden mit 2 multipliziert werden.

Nur richtig positionieren

Die Routine besteht im Grunde nur aus dem richtigen Positionieren des Datenzeigers und dem anschließendem auslesen der Inhalte. Wie setzen daher den Datenzeiger aus das 24 Byte im Header und lesen den Inhalt der 4 Bytes mittels dem Befehl INSTR aus. Nach entsprechender Umwandlung, welche die Procedure CONVERT ĂŒbernimmt, erhalten wir in der Variablen ZOOSTART die Position des ersten Directoryeintrags. Nachdem nun die erste Position bekannt ist schieben wir den Datenzeiger auf das Byte 6 in AbhĂ€ngigkeit des voher ermittelten Wertes und erhalten den Zeiger auf das nĂ€chste Directory, der in ZOONEXT gespeichert wird. Die restl. EintrĂ€ge werden dann mit RELSEEK d.h. relativ zur Position des Datenzeigers, eingelesen. Das bringt Geschwindigkeitsvorteile. Nach einlesen aller benötigten EintrĂ€ge des aktuellen Directorys weisen wir der Variablen ZOOSTART den Inhalt von ZOONEXT zu und befinden uns dann im nĂ€chsten Directory. Die Schleife wird solange durchlaufen bis der Zeiger auf das nĂ€chste Directory gleich null ist. Das bedeutet das kein weiter Eintrag mehr vorhanden ist.

Wie die Ausgabe im einzelnen zu erfolgen hat muß halt noch programmiert werden. Ich denke, einem guten Menu zum entpak-ken der Programme ließe sich, mehr oder weniger, leicht realisieren.

Fehler im Programm

Bei dieser Gelegenheit möchte ich auf einen Fehler hinweisen den ich, beim schreiben dieser Routine, zu spĂŒren bekam. Ich arbeite mit dem ZOO Programm aus der PD-Serie der ST Computer und der Shell namens ZOOBOY. Einer dieser beiden Programme löscht den Filenamen bei Verwendung von Wildcards, insbesondere ., nicht richtig. Ist z.b. beim archivieren mit dieser Wildcard der Filename kĂŒrzer als der vorherige so wird ein Rest des vorherigen File-Namens an den aktuellen angehĂ€ngt. Es ist umso unverstĂ€ndlicher da dieses von ZOO nicht erkannt wird. Wurde also ein Archiv mit Wildcards angelegt kann es sein das, bei Verwendung dieser Routine, mehr an den File-Namen „dranhĂ€ngt“ als eigentlich soll. Ich hoffe das diese Routine dem einen oder anderen nĂŒtzlich sein wird.

Quellennachweis:
ST-PD 345

DIM a(3) byte=256 word=65535 c|=5 CLR zoo_start CLR zoo_next ' OPEN "i",#1,"A:demo.zoo" length=LOF(#l) ' ' Lesen des Header Textes ' header$=INPUT$(20,#1) PRINT header$ ' ' Zeiger auf Start der Archivdaten ' SEEK #1,24 zoo_start$=INPUT$(4,#1) @convert(zoo_start$,zoo_start) SEEK #1,zoo_start @zoo_list ' ~INP(2) END ' PROCEDURE zoo_list DO ' ' Zeiger auf nĂ€chsten Directory Eintrag ' SEEK #1,zoo_start+6 in$=INPUT$(4,#1) @convert(in$,zoo_next) ' ' IF zoo_next>0 ' ' ===> DATUM RELSEEK #1,4 in$=INPUT$(2,#1) ' @bit ' ' ===> ZEIT RELSEEK #1,0 m$=INPUT$(2,#1) ' @bit ' ' ==>CRC PRÜFSUMME RELSEEK #1,0 in$=INPUT$(2,#1) @convert(in$,crc) ' ' ==> ORIGINALGRÖßE RELSEEK #1,0 org_size$=INPUT?(4,#1) @convert(org_size$,org_size) ' ' ===> GEPACKTE GRÖßE RELSEEK #1,0 now_size$=INPUT$(4,#1) @convert(now_size$,pac_size) ' ' ==> FILE GELÖSCHT RELSEEK #1,2 delet=INP(#1) ' ' ===> ZEIGER AUF KOMMENTAR RELSEEK #1,1 com_pomter$=INPUT$(4,#1) @convert(com_pointer$, com_pointer) ' ' ==> LÄNGE DES KOMMENTARS RELSEEK #1,0 com_len$=INPUT$(2,#1) @convert(com_len$,com_len) ' ' ===> FILENAME RELSEEK #1,0 fname$=INPUT$(13, #1) ' ' ===> pfad DES FILES ' SEEK #1,zoo_start+57 RELSEEK #1,6 pfad_length=INP(#1) RELSEEK #1,0 pfad$=INPUT$(pfad_length, #1) ' ' Das auslesen des Kommentars sollte hier ' geschehen, da ein SEEK Befehl hier besser ' ist als ein RELSEEK. ' @ausgabe ' SWAP zoo_start,zoo_next ENDIF LOOP UNTIL zoo_start=0 OR zoo_next=0 CLOSE #1 RETURN PROCEDURE ausgĂ€be PRINT AT(1,3);" CRC | Orig Size | Pack Size"; PRINT " | Del | Filename | Pfad"; PRINT AT(1,4);STRING$(80,"-"); PRINT AT(1,c|);HEX$(crc,4); PRINT AT(9,c|);org_size; PRINT AT(23,c|);pac_size; IF delet<>0 PRINT AT(32,c|);"Yes"; ELSE PRINT AT(32,c|);"No"; ENDIF PRINT AT(38,c|);fname$; PRINT AT(52,c|);pfad$; INC c| RETURN PROCEDURE convert(VAR convert$,convert) ' ' variable: ' a() = Array furs richtige Format ' byte = Faktor 256 fur Word Format ' Word = Faktor 65535 fĂŒr Longword Format ' ' RĂŒckgabe ' convert = wert im gewandelten Format ' lg=LEN(convert$) CLR i| REPEAT ' => String in Bytes aufteilen a(i|)=ASC(MID$(convert$,i|+1)) INC i| UNTIL i|=lg ' ' ==> errechnen des Wertes h_byte=a(0)+byte*a(1) l_byte=a(2)+byte*a(3) convert=h_byte+word*l_byte+l_byte RETURN @bit(0,4,datum, tag) @bit(5,8,datum,monat) @bit (9,15,datum,jahr) ADD jahr,80 PRINT tag,monat,jahr ' PROCEDURE bit(a,b,x,VAR value) CLR value,n FOR i=a TO b IF BTST(x,i)=TRUE value=value+1*2^n ENDIF INC n NEXT i RETURN
Michael Weber