Das ByteArray bieten eine neue Möglichkeit entsprechende Binäredaten auch von anderen Programmen oder in anderen Formaten in Flash zu lese, verarbeiten oder auch zu erstellen.
Dieser Artikel behandelt die Grundlagen von ByteArrays und versucht anhand des ZIP Formats die Möglichkeiten genauer zu erklären.
Bittet beachtet das dies kein Grundlagenkurs für Bits und Bytes ist, es sollte also vorher ein gewisses Wissen über das Binäre (BIN) und Hexadecimale (HEXDEC) System vorhanden sein.
Inhalt
Grundsätzlich handelt es sich bei jeder Datei auf den PC um eine Binäredatei, wenn man also eine Übersicht des entsprechenden Formats hat, kann man jedes Format einlesen.
So sieht der einfache Text "Hello World" wie folgt aus:
0100100001100101011011000110110001101111001000000101011101101111011100100110110001100100
Dies sieht auf den ersten Blick recht kompliziert aus, wenn man aber weis das jeder Buchstabe nun aus 8 Zeichen (Bits) besteht bekommt, kann man diesen zerlegen.
Dadurch erhält man eine bessere Übersicht auch wenn man noch nicht viel damit anfangen kann:
00: 0100 1000
01: 0110 0101
02: 0110 1100
03: 0110 1100
04: 0110 1111
05: 0010 0000
06: 0101 0111
07: 0110 1111
08: 0111 0010
09: 0110 1100
10: 0110 0100
Wird nun jede Zeile in eine Zahl umgewandelt, kann man anhand einer ASCII Tabelle (Wikipedia) genau nachprüfen welcher Text hier verborgen ist.
Die erste Zeile "0100 1000" stellt eine 72 da welches den Buchstaben "H" entspricht.
00: 0100 1000 = 72 (H)
01: 0110 0101 = 101 (e)
02: 0110 1100 = 108 (l)
03: 0110 1100 = 108 (l)
04: 0110 1111 = 111 (o)
05: 0010 0000 = 32 ( )
06: 0101 0111 = 87 (W)
07: 0110 1111 = 111 (o)
08: 0111 0010 = 114 (r)
09: 0110 1100 = 108 (l)
10: 0110 0100 = 100 (d)
Wie man hier sieht kann man also wenn man weis, wie die Daten aufgebaut sind, die relevanten Information auslesen.
Eine Übersicht der ASCII Tabelle findet man unter anderem unter der URL http://www.torsten-horn.de/techdocs/ascii.htm.
Anbei eine Übersicht der möglichen ByteArray Typen mit jeweiligen Beispiel damit man ein Gefühl davon bekomme wie ein solches Puzzel zusammen gesetzt werden muss.
Damit man sich die einzelenen Typen genauer vorstellen kann, sind in den Klammern (...) jeweilige Bit Längen zu sehen.
Boolean
Dieser Typ kennt nur "true" oder "false", es wird ein Byte gelesen (0000 0000).
Byte
Liefert eine positive/negative 8-bit Zahl zwischen -128 und 127 zurück, es wird ein Byte gelesen (0000 0000).
Bytes
Eine Gruppe von Byte, die mögliche Zahl hängt von der Anzahl der Bytes ab (0000 0000 ...).
Double
Liefert eine positive/negative 64-bit Zahl nach IEEE 754 Standard zurück, es werden 8 Bytes gelesen (0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000).
Float
Liefert eine positive/negative 32-bit Zahl nach IEEE 754 Standard zurück, es werden 4 Bytes gelesen (0000 0000 0000 0000 0000 0000 0000 0000).
Integer (Int)
Liefert eine positive/negative 32-bit Zahl wischen -2147483648 und 2147483647 zurück, es werden 4 Bytes gelesen (0000 0000 0000 0000 0000 0000 0000 0000).
Short
Liefert eine positive/negative 16-bit Zahl zwischen -32768 und 32767 zurück, es werden 2 Bytes gelesen (0000 0000 0000 0000).
UnsignedByte
Liefert eine positive 8-bit Zahl zwischen 0 und 255 zurück, es wird 1 Byte gelesen (0000 0000).
UnsignedInteger (UnsignedInt)
Liefert eine positive 32-bit Zahl zwischen 0 und 4294967295 zurück, es werden 4 Bytes gelesen (0000 0000 0000 0000 0000 0000 0000 0000).
UnsignedShort
Liefert eine positive 16-bit Zahl zwischen 0 und 65535 zurück, es werden 2 Bytes gelesen (0000 0000 0000 0000).
UTF
Liefet einen UTF8 Text zurück, die länge dieses Text wird durch einen UnsignedShort am Anfang festgestellt.
Dieser UnsignedShort gibt an aus wie vielen Bytes der UTF8 Text besteht.
Beispiele:
1 Zeichen: 0000 0000 0000 0001 0000 0000
2 Zeichen: 0000 0000 0000 0010 0000 0000 0000 0000
3 Zeichen: 0000 0000 0000 0011 0000 0000 0000 0000 0000 0000
Object
Liefert ein Object im AMF Format zurück, die Bytelänge hängt hier von den Daten in diesem Object ab. (0000 0000 ...)
Der Unterschied zwischen "signed" und "unsigned" ist relative einfach zu erklären.
Bei "unsigned" Datentypen sind nur positive Werte Möglich, dafür sind hier aber höhere Werte möglich Beispiel (0 - 255).
Bei "signed" Datentypen dagegen bestimmt das höchste Bit ob es sich um eine positive oder negative Zahl handelt.
Dadurch sind nun Werte von -127 bis +127 möglich, man kann also durch den richtigen Datentypen entsprechende Bytes sparen.
Wenn keine negativen Werte benötigt werden (jetzt und in Zukunft) so ist die Verwendung von "signed" Datentypen eine Speicherplatzverschwendung.
Da wir eine ZIP Datei auslesen bzw. verarbeiten wollen müssen wir uns erst näher das entsprechende Format ansehen.
Es existiert für jede Binäredatei eine entsprechende Spezifikation die das Format genauer beschreibt.
Aber es ist natürlich klar, das nicht alle Spezifikationen frei erhältlich sind.
Das Format einer ZIP Datei sieht also grob wie folgt aus:
| Name | Größe | Bemerkung | |
|---|---|---|---|
| file header signature | 4 bytes | 0x04034b50 | |
| version | 2 bytes | ||
| bit flags | 2 bytes | ||
| compression method | 2 bytes | 8 = DEFLATE, 0 = UNCOMPRESSED | |
| last modify time | 2 bytes | ||
| last modify date | 2 bytes | ||
| crc-32 | 4 bytes | ||
| compressed size | 4 bytes | ||
| uncompressed size | 4 bytes | ||
| file name length | 2 bytes | ||
| extra field length | 2 bytes | ||
| file name | file name length | Größe hängt von "file name length" ab | |
| extra field | extra field length | Größe hängt von "extra field length" ab | |
| file data | compressed size | Größe hängt von "compressed size" ab | |
Aufgrund der Tabelle wissen wir, wieviele Bytes welches Feld besitzt, nun müssen wir nur noch die richtigen Datentypen verwenden umd die entsprechenden Felder auslesen zu können.
Damit dies so verständlich wie möglich ist, ist hier die Tabelle von vorhin mit entsprechenden Datentypen die wir zum auslesen verwenden.
Wie man sieht verwenden wir für alle 4 bytes Felder readUnsignedInt() (auser für die "file header signature") und für alle 2 bytes Felder readUnsignedShort().
Die dynamischen Felder werden je nach Inhalt mit readUTFBytes(...) bzw. readBytes(...) ausgelesen.
| Name | Größe | Datentyp | |
|---|---|---|---|
| file header signature | 4 bytes | readInt() | |
| version | 2 bytes | readUnsignedShort() | |
| bit flags | 2 bytes | readUnsignedShort() | |
| compression method | 2 bytes | readUnsignedShort() | |
| last modify time | 2 bytes | readUnsignedShort() | |
| last modify date | 2 bytes | readUnsignedShort() | |
| crc-32 | 4 bytes | readUnsignedInt() | |
| compressed size | 4 bytes | readUnsignedInt() | |
| uncompressed size | 4 bytes | readUnsignedInt() | |
| file name length | 2 bytes | readUnsignedShort() | |
| extra field length | 2 bytes | readUnsignedShort() | |
| file name | file name length | readUTFBytes([file name length]) | |
| extra field | extra field length | readBytes(...,0,[extra field length]) | |
| file data | compressed size | readBytes(...,0,[compressed size]) | |
Wir verwenden hier bei den meisten Werten ein "unsigned" Datentyp, da negative Werte für Größenangaben oder Zeit und Datum nicht negative vorkommen.
Es gibt aber durchaus auch Formate die nur mit "signed" Datentypen arbeiten.
Die entsprechende ZIP Datei wurde in das ByteArray "file.data" geladen, welches wird nun für die Auswertung verwenden.
Zuerst stellen wird die Byte Reihenfolge mit file.data.endian = Endian.LITTLE_ENDIAN; auf Little Endian(Wikipedia).
Da in einer ZIP Datei mehrere Dateien existieren können verwenden wir eine Schleife while(file.data.bytesAvailable) {...} die so lange läuft, so lange noch Daten vorhanden sind.
Um nicht jede Variable einzeln definieren zu müssen, verwenden wir ein Objekt für die Daten var zip_file:Object =....
Nun fangen wir an das erste Feld auszulesen, da dieses 4 Bytes lang ist verwenden wir hier signature: file.data.readInt().
Bei einem validen Eintrag sollte die Signatur den Wert "0x04034b50" haben, also prüfen wir zuerst ob dies der Fall ist und falls nicht ist wohl die ZIP Datei zu Ende.
Danach lesen wird die weiteren Werte ein jeweilis mit file.data.readUnsignedShort() für 2 Bytes und mit file.data.readUnsignedInt() für 4 Bytes.
Laut Spezifikation ist der "file name" und das "extra field" dynamisch. Um den "file name" als Text auszulesen werden wird deshalb file.data.readUTFBytes(zip_file.file_name_length), hier wird genau die Anzahl von Zeichen eingelesen die bei "file name length" definiert ist.
Before wir nun die dynamischen Felder wie "extra field" oder "file data" auslesen prüfen wir natürlich zuerst ob der Wert größer als 0 ist um Fehler zu vermeiden.
Das "extra field" werde mit file.data.readBytes(zip_file.extra_field,0,zip_file.extra_field_length); gelesen und das Ergebniss wird in "zip_file.extra_field" geschrieben.
Der eigentliche Inhalt des Eintrags wird mit file.data.readBytes(zip_file.data,0,zip_file.compressed_size); gelesen und das Ergebniss wird in "zip_file.data" geschrieben.
Anschließend geben wir alle gesammelten Informationen mit trace() aus.
...
if (file.data.length > 0) {
file.data.endian = Endian.LITTLE_ENDIAN;
while(file.data.bytesAvailable) {
var zip_file:Object = {
signature: file.data.readInt(),
extra_field: new ByteArray(),
data: new ByteArray()
};
if (zip_file.signature == 0x04034b50) {
trace('- Found ZIP File at position:' + file.data.position);
zip_file.data.endian = Endian.LITTLE_ENDIAN;
zip_file.version = file.data.readUnsignedShort();
zip_file.bitflags = file.data.readUnsignedShort();
zip_file.compress_method = file.data.readUnsignedShort();
zip_file.last_modify_time = file.data.readUnsignedShort();
zip_file.last_modify_date = file.data.readUnsignedShort();
zip_file.crc32 = file.data.readUnsignedInt();
zip_file.compressed_size = file.data.readUnsignedInt();
zip_file.uncompressed_size = file.data.readUnsignedInt();
zip_file.file_name_length = file.data.readUnsignedShort();
zip_file.extra_field_length = file.data.readUnsignedShort();
zip_file.file_name = file.data.readUTFBytes(zip_file.file_name_length);
if (zip_file.extra_field_length > 0) {
file.data.readBytes(zip_file.extra_field,0,zip_file.extra_field_length);
}
if (zip_file.compressed_size > 0) {
file.data.readBytes(zip_file.data,0,zip_file.compressed_size);
}
trace(
'>> local file header >>',
'Version: ' + zip_file.version,
'Bit Flags: ' + zip_file.bitflags,
'Compress Method: ' + zip_file.compress_method,
'Last Modify Time: ' + zip_file.last_modify_time,
'Last Modify Date: ' + zip_file.last_modify_date,
'CRC32: ' + zip_file.crc32,
'Compressed Size: ' + zip_file.compressed_size,
'Uncompressed Size: ' + zip_file.uncompressed_size,
'File Name Length: ' + zip_file.file_name_length,
'Extra Field Length: ' + zip_file.extra_field_length,
'File Name: ' + zip_file.file_name,
'Extra Field: ' + zip_file.extra_field,
'RAW size: ' + zip_file.data.length
);
} else {
trace('- WARNING: No valid ZIP File at position: ' + file.data.position + '/' + file.data.bytesAvailable + ' (' + zip_file.signature + ')');
break;
}
}
}
...
Wie man hier sieht, ist es relative einfach ein Format zu verarbeiten wenn man die genaue Bit bzw. Byte Reihenfolge kennt.
Dadurch das bei einem ByteArray automatisch die Position gewechselt wird, wenn wir was auslesen, sparen wir uns die Arbeit um die genaue Position angeben zu müssen.
Je nachdem ob wir file.data.readUnsignedShort() oder file.data.readUnsignedInt() verwenden, wird die aktuelle Position um 2 bzw. 4 erweitert.
Leider können wir die Daten in zip_file.data nicht direkt verwenden, da nur Adobe AIR DEFLATE direkt entpacken kann.
Für reines ActionScript muss ein entsprechendes Unterprogramm geschrieben werden um auf die Daten direkt zugreifen zu können.
