Байтшкодим: Header
Представим себе ситуацию: нужно загрузить swf`ку и требуется срочным образом узнать о её характеристиках, до того как swf`ка сама поймёт, что она swf`ка и сможет нам это сказать. Нормальные люди хранят, например, xml файл с нужным описанием или что нибудь-ещё. Но не мы.
К тому же, мы совсем не признаем сжатие swf`ек. Мы считаем, что сжатые swf`ки - пережиток прошлого, с нашими пропускными способностями каналов связи это оскорбление экономить на размере.
А теперь чуть серьезней: будем смотреть на байткод с точки зрения практического применения. Применение найти сложно, поэтому мы притащили задачу описанную выше за уши. Несжатая swf`ка гораздо проще воспринимается на глаз в HEX-редакторе, чем сжатая. Поэтому работать будем с несжатой swf`кой. Сегодня мы разберём Header swf`ки - кусок информации, в котором содержится её размеры, framerate и кое-что другое.
Итак, что такое байткод?
Цитата:
Байт-код или байтко́д (англ. byte-code), иногда также используется термин псевдоко́д — машинно-независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт-кода эквивалентны одной или нескольким командам ассемблера. Трансляция в байт-код занимает промежуточное положение между компиляцией в машинный код и интерпретацией.
Итак, нам понадобится:
Калькулятор
HEX-редактор
Берём любую swf`ку и открываем её HEX-редактором. Видим много страшных непонятных чисел, делаем умное лицо, морщим лоб и усиленно пытаемся декомпилировать swf`ку в уме. Не получилось? То-то. Но если посмотреть на первые 3 байта swf`ки можно заметить, что там вполне такие нормальные символы: FWS или CWS. Это сигнатура файла. Она есть у каждого файла и обозначает его тип. Вырезав их любой файл потеряет "голову" и мало кто его признает за своего.
Глядим на первый байт: там стоит буква F (0x46) или буква C (0x43) - это сокращения от Compressed и Full. Если первая буква C - Вам не повезло, придётся менять положение. К сожалению изменение первой буквы результата не даст, swf`ку надо "пережать". Я пользуюсь программкой cws2fws, консольное приложение - для меня вполне удобное.
Итак, swf`ка пережата и имеет сигнатуру FWS. Другое дело! Теперь можно хвастаться своим коллегам что мы умеем пережимать swf`ки.
Давайте попробуем сперва разобраться на пальцах, как этот самый байткод в Header`е устроен. Работать будем с версией swf файла 9 и 10, спецификации формата меняются от версии к версии. Header для 9 и 10 одинаков, так что тут выбор за вами.
Вот вырезка из спецификации:
Цитата:
SWF File Header
Field Type Comment
Signature UI8 Signature byte:
“F” indicated uncompressed
“C” indicates compressed (SWF 6 or later only)
Signature UI8 Signature byte always “W”
Signature UI8 Signature byte always “S”
Version UI8 Single byte file version (for example, 0x06 for SWF 6)
FileLength UI32 Length of entire file in bytes
FrameSize RECT Frame size in twips
FrameRate UI16 Frame delay in 8.8 fixed number
Field Type Comment
Signature UI8 Signature byte:
“F” indicated uncompressed
“C” indicates compressed (SWF 6 or later only)
Signature UI8 Signature byte always “W”
Signature UI8 Signature byte always “S”
Version UI8 Single byte file version (for example, 0x06 for SWF 6)
FileLength UI32 Length of entire file in bytes
FrameSize RECT Frame size in twips
FrameRate UI16 Frame delay in 8.8 fixed number
Итак, поехали.
Первый байт:
Signature UI8 Signature byte
UI8 означает Unsigned Integer 8 bit, т.е. размер один байт.
Следующие 2 байта:
Signature UI8 Signature byte always “W”
Signature UI8 Signature byte always “S”
Это означает, что 2 байт всегда W, третий байт всегда S. Делов то!
ВАЖНО! Метод загрузки, изложенный ниже писался мной до получения новых технических знаний. Даже сжатую флешку можно отдать обычному Loader`у, а после взять у него байты по contentLoaderInfo.bytes и они будут расжаты, поэтому в практическом применении стоит грузить именно через Loader и байты брать от него же.
Однако я считаю что суть этой статьи не в методах загрузки SWF`ки, а именно в парсе, поэтому статью переписывать я не буду. Спасибо за внимание.
Теперь пока не забыли, быстренько оформим всё это дело в коде. По идее, надо бы сделать ради этого отдельный класс, но мне лень. Лепим в Main`е.
_console = new TextField(); _console.autoSize = TextFieldAutoSize.LEFT; _console.multiline = true; super.addChild(_console); log("Start loading"); _urlStream = new URLStream(); _urlStream.load(new URLRequest("testFull.swf")); _urlStream.addEventListener(ProgressEvent.PROGRESS, onProgress); _urlStream.addEventListener(Event.COMPLETE, onComplete);
private function log(...args):void { var l:int = args.length; for (var i:int = 0; i < l; i++) _console.appendText(args[i].toString() + "\n"); }
Мы создаём urlStream и просим его загрузить testFull.swf, теперь нужно дождаться пока поступят байты: это дело контролирует onProgress.
Теперь толстый момент: размер Header`а не определен заранее и в целях упрощения подачи материала мне хотелось бы чтобы при начале его разбора он полностью был загружен. Обычно он занимает байтов 20, мы подождем когда загрузим 50, чтобы уж точно наверняка. Я не считаю это за "плохой" приём: скорость интернета измеряется в мегабитах - 30 байт... О чем речь?
private function onProgress(event:ProgressEvent):void { log("Progress event. BytesAviable: " + _urlStream.bytesAvailable); if (_urlStream.bytesAvailable > 50) { _urlStream.removeEventListener(ProgressEvent.PROGRESS, onProgress); parse(); //50 байтов для хеадера нам хватит заглаза } }
private function parse():void { _urlStream.endian = Endian.LITTLE_ENDIAN; if (_urlStream.readUTFBytes(3) != "FWS") { log("Invalid file"); return; }
Вот например из оф. доки пример, как хранится число:
Цитата:
The 32-bit value 0x456e7120 is stored as 20 71 6e 45.
Далее идёт:
Цитата:
Version UI8 Single byte file version (for example, 0x06 for SWF 6)
Далее:
Цитата:
FileLength UI32 Length of entire file in bytes
32 бита считывает метод readUnsignedInt:
А вот теперь сложнее.
Цитата:
FrameSize RECT Frame size in twips
Как это? Сейчас объясню. Например вот группа байтов:
11010100 10101010 011011111
Каждый байт "как-бы" самодостаточен и несет собственную смысловую нагрузку - как было выше. В случае с RECT ситуация другая:
11010100 10101010 011011111
Цветом выделены группировки.
Эти бесстыдники не то чтобы своё место удержать не могут - они ещё и на чужое лазят. Нужно их сгруппировать обратно.
Во flash`е работа с битами проходит не под полным контролем - нету флага переноса, например, благодаря которому можно было бы биты легко перекидывать и других "низкоуровневых" вещей. Я уже писал про биты - если у Вас это вызывает затруднения - прочитайте мои прошлые статьи. Объекта "бит" у нас тоже нету, поэтому будем крутиться: записываем все значения в int и uint. Например, комбинация битов 110 в int будет выглядеть так:
00000000 00000000 00000000 00000110
Печально, но тут никуда не денешься.
Теперь снова о RECT: первые 5 битов в конструкции означают количество битов, отведенных под xMin, xMax, yMin, yMax по отдельности.
Например, если первые 5 битов 01111 (это 15), то потом 4 раза по 15 битов будут заданы xMin, xMax, yMin, yMax.
Ради этого я написал класс BitMachine. Суть его в том, что ему передается любой IDataInput (например наш _urlStream) и потом оттуда тянутся биты в нужном количестве.
Вот реализация:
package { import flash.utils.ByteArray; import flash.utils.IDataInput; /** * ... * @author Artem Shlagin */ public class BitMachine { private var _data:IDataInput; private var _position:int; private var byte:int; public function BitMachine(dataInput:IDataInput = null) { _data = dataInput; _position = 8; //чтобы при первом считывании мы загрузили байт } public function getBits(length:int):uint { var result:uint = 0; var offset:int = 0; do { if (_position >= 8) { //мы вышли за границу байта. Нужно загрузить новый byte = _data.readUnsignedByte(); _position = 0; } var cp:int = byte; //создали копию байта. Мы не должны его каверкать cp &= (255 >>> _position); //Маскируем биты, которые мы уже прошли. То есть левее от position все биты 0 var ba:int = 8 - _position; //Количество битов которые мы ещё можем считать с этого байта (bytesAviable) var toRead:int = Math.min(length, ba); //сколько будем считывать битов с этого байта cp >>>= ba - toRead; //удаляем биты справа, тем самым получаем эффект считывания байтов слева-направо offset = Math.min(length, 8); //если считываем более чем с одного байта - придется двигать биты в результате result <<= offset; //биты читаем слева направо, поэтому сперва сдвинем старые биты (старые биты слева) result |= cp; //А потом к старым битам шлём новые length -= toRead; //изменили длину _position += toRead; //сместили позицию } while (length > 0); return result; } } }
Теперь парсим этот самый RECT:
var bm:BitMachine = new BitMachine(_urlStream); var nBits:int = bm.getBits(5); log("NBits " + nBits.toString(2)); var xMin:int = bm.getBits(nBits); var xMax:int = bm.getBits(nBits); var yMin:int = bm.getBits(nBits); var yMax:int = bm.getBits(nBits);
Но это не проблема, ведь так?
log("xMin: " + xMin, "xMax: " + xMax, "yMin: " + yMin, "yMax: " + yMax); log("Flash size: " + (xMax / 20) + "/" + (yMax / 20));
Цитата:
FrameRate UI16 Frame delay in 8.8 fixed number of frames per second
var frameRate:Number = _urlStream.readUnsignedByte() / 255 + _urlStream.readUnsignedByte(); log("Frame Rate: " + frameRate);
Ну и закрывает это дело:
Цитата:
FrameCount UI16 Total number of frames in file
Цитата:
var frameCount:int = _urlStream.readShort();
log("Total frames: " + frameCount);
log("Total frames: " + frameCount);
Есть такой любопытный момент:
Код:
Start loading Progress event. BytesAviable: 3667 Version of file: 10 File length: 3659 NBits 1111 Frame Rate: 30 Total frames: 1 xMin: 0 xMax: 16000 yMin: 0 yMax: 12000 Flash size: 800/600
Спасибо за внимание.
Всего комментариев 35
Комментарии
08.09.2010 20:06 | |
Спасибо, очень интересно
ПС: встречаются опечатки вначале и в середине статьи. |
08.09.2010 20:22 | |
Поправил, что нашел + исправил неточность про LITTLE_ENDIAN - оно справедливо не ко всей swf`ке. Спасибо, если найдёте ещё - ткните носом )
Или на ты перейдем? |
|
Обновил(-а) Psycho Tiger 08.09.2010 в 20:38
|
08.09.2010 21:58 | |
Psycho Tiger, если мне, то давай на ты )
Кстати про "любопытный момент" - может это из-за того, что ты swf разжимал? |
09.09.2010 00:20 | |
С тех пор как прочитал спецификацию, не дает покоя этот RECT в заголовке. Ну зачем он там? Пару байт сэкономить?
|
09.09.2010 13:55 | |
Спецификация swf
Два года назад я начал переводить ее и бросил надо будет закончить. |
09.09.2010 14:28 | |
Да, изучал по официально, что привёл iNils.
Ещё есть у alexref`а - её не смотрел в силу отсутствия времени, но говорят она лучше: http://www.m2osw.com/swf_alexref.html @Котяра: ага, спасибо, поправил. |
|
Обновил(-а) Psycho Tiger 09.09.2010 в 14:55
|
09.09.2010 16:40 | |
Цитата:
iNils, а можно глянуть на полуфабрикат?
|
09.09.2010 22:46 | |
Аха! Есть еще люди в русских селениях ) По-правде, рад что появилась такая интересная статья.
Однако! У форума наклевывается вторая жизнь. Гуру, поддержим? Цитата:
за создание формата swf я готов пожать руку человеку, который его придумал. пожалуй наиболее грамотного кодирование сложно сейчас встретить.
|
|
Обновил(-а) dimarik 09.09.2010 в 22:49
|
10.09.2010 12:13 | |
swf-ку мог бы разжать обычным uncompress().
|
11.09.2010 13:48 | |
etc, разве uncompress будет правильно работать в то время, когда флешка ещё не полностью загружена?
@Hidest: ну некоторые мои коллеги прямо говорили - "Расскажи о байткоде!" |
12.09.2010 04:03 | |
ну тогда можно взять loaderInfo.bytes. там уже всё расжатое лежит
|
|
Обновил(-а) BlooDHounD 12.09.2010 в 16:34
|
12.09.2010 16:33 | |
Psycho Tiger, уважаемый. прочтите ещё рас моё предыдущие сообщение.
|
12.09.2010 22:44 | |
Psycho Tiger, то что можно грузить через load и делать всё без презервативов.
|
13.09.2010 01:20 | |
Об этом я тоже писал, что ради прикола притащим практическое применение за уши и будем грузить URLStream`ом =)
Статья не о загрузке данных, а о парсе, всё же. |
13.09.2010 02:38 | |
так тебе и говорят что парсить удобнее через loaderInfo.bytes так как в него уже расжатые данные поступают при загрузке.
|
13.09.2010 15:07 | |
Psycho Tiger, ну ваще-то он не спотолка у рута берётся. он к нему от loader'а падает, которым его загрузили.
|
13.09.2010 15:40 | |
Да, сейчас вспомнил что читал об этом где то. Надо знания свои углубить, а то многое валяется на поверхности.
|
13.09.2010 19:00 | |
Цитата:
Loader грузит потоком
|
13.09.2010 19:16 | |
Loader#close
Код:
/// Cancels a load() method operation that is currently in progress for the Loader instance. public function close () : void; |
13.09.2010 19:38 | |
Вот ведь, искал и не заметил. Спасибо)
|
13.09.2010 23:03 | |
мимо дискуссии про загрузку, оно важно, канеш, но как соус
респект Тигре за основное блюдо: это, имо, серьезная работа (надо же все перелопатить, докопаться, оформить и выложить) и это забота о нас (не надо теперь лопатить и копаться - пришел и все взял, крсота) короче, спасибо: такие вещи очень нужны вещи тут не только инструменты, что ты даешь, но и принцип: чем больше даешь, тем больше твое извиняюсь за многословность, но ведь 256й день же.. |
|
Обновил(-а) silin 13.09.2010 в 23:20
|
13.09.2010 23:09 | |
Приятно очень
Но это крупица по сравнению с тем, что выкладываешь ты. Короче тебе двойной спасибо-респект) |
14.09.2010 09:27 | |
да, silin - молодец! С праздничком )
|
Последние записи от Psycho Tiger
- Тонкости и трюки ActionScript`а, которые... бесполезны (10.05.2011)
- Vkontakte: как пользоваться wall.post, нужен ли теперь wall.savePost? (05.03.2011)
- А пятый контер-страйк хорош. (19.01.2011)
- Пацаны, гоу Вконтакте? (21.12.2010)
- Давайте начистоту (18.12.2010)