Struktur eines ZOO-Archivs

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
Aus: ST-Computer 11 / 1991, Seite 148

Links

Copyright-Bestimmungen: siehe Über diese Seite