Что такое flasm
flasm 1.41 - ассемблер/дизассемблер
Также существует возможность встраивать команды flasm в
Кроме оптимизации, дизассемблирование flasm'ом предоставляет возможность "порыться внутри" кода, что помогает глубже вникнуть в ActionScript. Flasm не декомпилятор, он позволяет получить читабельное представление кода, но не исходный
Ищете простое описание? Хорошо, можете не читать всю страницу. Для начала разберитесь с использованием. Потом прочитайте про виртуальную машину Flash и убедитесь, что поняли концепцию регистров и стека. Дизассемблируйте несколько своих флеш-клипов, начинайте с
Скачать
Windows binary: flasm14.zip
Mac OS X binary: flasmac14.tgz
Linux binary (x86): flasmlinux14.tgz
Документация (этот файл на английском) включена в архивы.
Хотите сами скомпилировать из исходников?
Платформонезависимые исходники: flasm14src.zip
Вам понадобится GCC или CC компилятор с установленными пакетами flex, bizon и zlib. По идее должно скомпилироваться без проблем. Тестировалось с djgpp на win98se, cygwin на win98se и win2k, mac os X и Linux.
Для cygwin нужно установить еще и пакет mingw и mingw'овскую версию zlib'а из MinGW packages repository. Под Windows,
Новое в версии 1.41
gotoAndStop/gotoAndPlay. Я отследил следующе: существует недокументированный аргумент для
Обновления в предыдущих версиях
Использование
Распакуйте дистрибутив в любое место. Поскольку flasm работает из командной строки, потребуется открыть досовское (MSDOS) окно (под Windows). На MAC'ах потребуется открыть окно терминала: Applications/Utilities/Terminal. Теперь, используя DOS-команду CD, перейдите в директорию с flasm'ом. Например:
cd c:\Flash\flasm (windows)или
cd Flash/flasm (mac)
Вызванный без параметров, flasm покажет список существующих ключей:
flasm ключ имя-файла
ключи
-d Дизассемблировать swf-файл с выводом в консоль
-a Ассемблировать указанный проект flasm
-u Обновить swf-файл, заменить макросы flasm
-z Сжать swf-файл Zlib'ом
-x Распаковать swf-файл
-d foo.swf
Дизассемблировать foo.swf в консоль.
-d foo.swf > foo.flm
Дизассемблировать foo.swf, вывод перенаправить в foo.flm.
Альтернатива - просто вызвать flasm без ключа: flasm foo.swf
Файл foo.flm будет создан в той же папке.
-a foo.flm
Ассемблировать foo.flm и обновить swf, указанный внутри.
Создается резервная копия оригинала с расширением .$WF
-u foo.swf
Дизассемблировать foo.swf во временный файл.
Выполняет макросы flasm, встроенные в SFW.
Автоматически выполняет элементарные оптимизации: удаляет дублирующиеся нотации, заменяет 0.0 на 0, перестраивает пул констант.
Обновляет SWF. Создается резервная копия оригинала с расширением .$WF
В принципе неплохая идея обновлять финальные swf-файлы с ключом -u.
Может они и не станут работать заметно быстрее, но по крайней мере будут немного меньше.
-x foo.swf
Только для Flash MX. Распаковывает foo.swf, создается резервная копия оригинала с расширением .$WF
-z foo.swf
Жмет foo.swf. Создается резервная копия оригинала с расширением .$WF.
Исходник необязательно должен быть МХ'овым, просто после сжатия он будет воспроизводиться только МХ плейером.
Для удобства работы с swf-файлами, можно добавить flasm в контекстное меню Windows, вызываемое правым щелчком мыши. Для этого запустите windows explorer (проводник), выберите View > Folder Options > File Types и выберите Flash player movie (или подобный) тип, связанный с расширением .swf. Нажмите кнопку Edit, затем кнопку New. В поле Action введите Disassemble. Нажмите кнопку Browse, перейдите в директорию, в которой находится flasm и дважды щелкните на flasm.exe. Не задавайте никаких параметров. Нажмите OK, Close и еще раз Close. Теперь щелкните правой кнопкой мыши на любом swf-файле и в открывшемся контекстном меню выберите Disassemble - будет создан дизассемблированный файл с расширением .flm. Возможна дальнейшая автоматизация, добавлением flasm -uflasm -a
Если не хотите делать этого, попробуйте воспользоваться winflasm - графической оболочкой (GUI) для flasm.
Защита ActionScript-кода
Поскольку формат SWF-файла открыт, то любой созданный вами контент может быть из этого файла извлечен. Задача flasm'а — помочь вам оптимизировать код, превратив его в байтовый код. Flasm, в отличие от других продуктов, имеющихся на рынке, не помогает делать обратное преобразование байткода в обычный код ActionScript, который можно было бы украсть через буфер обмена.
К сожалению, полумеры типа включения в каждый кадр специальных шифрующих выражений и даже низкоуровневое автоматическое манипулирование с помощью инструментов запутывания кода (obfuscator'ы) не сделают ваш ActionScript действительно защищенным (не говоря уже о довольно бесполезных тегах protect и enableDebugger). Это всего лишь вопрос времени — когда следующие версии смотрелок
Тем не менее, существуют инструменты, позволяющие поэкспериментировать с запутыванием. Старейший из них, это obfu от Dave Hayden (Дэйв Хэйдэн). Он использует простой трюк управления потоком, что заводит декомпиляторы в тупик. Другой кажется, идет тем же путем. Viewer Screwer от Робина Дебройла (Robin Debreuil), это настоящий запутыватель, он переименовывает все ваши переменные, делая код просто нечитабельным, хотя и доступным при этом. Я не пробовал ничего из вышеперечисленного. Тем не менее, исходя из прочитанного я понял, что ни один из этих инструментов не достиг стадии стабильного, готового к выпуску продукта, хотя инструмент Робина кажется мне самым перспективным и многообещающим в этом направлении.
Если вы хотите спрятать сложный 3D-движок, в разработке которого вы провели сотни часов, вам может помочь оптимизация его с помощью flasm'а. Это сделает кражу ваших скриптов намного более сложной задачей. Обычные инструменты декомпиляции умеют распознавать определенные закономерности (patterns) в байтовом коде, соответствующие высокоуровневым выражениям ActionScript. Производя оптимизацию, вы уничтожаете такие закономерности. Посмотрите этот простой пример.
Дополнение: вышенаписанное некоторых сбивает с толку. Они просто спрашивают меня, как это сделать :) Я должен сказать: вы будете работать недели с вашим проектом, задавшись целью оптимизации и неплохим побочным эффектом в виде усложнения жизни декомпиляторов. Не ищите здесь какие-то скрытые ключи или обещания.
И снова скажу, все это об утаивании алгоритмов, но не паролей. Нет способа спрятать пароль в машине пользователя — только некоторые техники, основанные на работе с сервером, могут предоставить вам дополнительную степень безопасности.
Если Вы программируете на Cи и хотите сами реализовать защиту от детрансляторов, почитайте мои соображения по этому поводу и углубляйтесь в код flasm'а. Все
История проекта
dave@opaque.net в сотрудничестве с Damien Morton выпустил flasm в апреле 2001 года. Оригинальная версия могла дизассемблировать главный таймлайн
Виртуальная машина Flash
Каждое простое выражение языка ActionScript среда Flash компилирует в пару простых команд байтового кода (байткода). Например, a=b*b; превращается в
constants 'a', 'b'
push 'a', 'b'
getVariable
push 'b'
getVariable
multiply
setVariable
Код приведенный выше — визуальное представление байтовых кодов, созданных с помощью flasm. Flash проигрыватель/плагин работает внутри виртуальной машины, которая интерпретирует байткоды.
Я вызываю команды (actions) внутри кадров или, другими словами, по событиям появления блоков команд. Flash выполняет блоки команд друг за другом, так что поток выполнения кода в блоке никогда не прерывается ни событием gotoAndPlay(), ни аналогичными ему командами. Думаете, реальное параллельное выполнение кода должно быть лучше, чем это? Я уверен, что это должно эффективно повлиять на стабильность Flash Player'а, которая теперь стала довольно высокой, принимая во внимание все вещи, происходящие в сложном клипе.
Стек
Виртуальная машина Flash основана на стеке, то есть вы не можете сослаться на конкретную ячейку памяти. Стек — это место в памяти, где данные могут храниться таким образом, что последняя вошедшая (pushed) величина будет извлечена (poped) из стека первой. Все команды читают (pop) операнды из стека и кладут результат (если таковой имеется) в стек. Операнд может быть целым числом (integer), строкой (string), числом с плавающей запятой (float) или ссылкой на объект (на самом деле являясь его именем) и т.п.
Дальнейшее объяснение стека от Роберта Пеннера (Robert Penner):
Если вам знакомы методыArray.pushиArray.pop, знайте, что эти команды подобны манипуляциям со стеком. Стек похож на массив значений, за исключением того факта, что в каждый отдельный момент времени вы можете получить значение только последнего (верхнего) элемента, или положить сверху еще одинэлемент-значение или поменять местами два последние элемента.
Например, для сложения двух чисел вам нужно положить оба эти числа в стек, затем вызвать командуadd. Командаaddвынет из стека два верхних числа, сложит их и положит полученное число в стек.
Заметьте, что команда pop не вызывает ошибок, даже если стек пуст.
Есть две команды, которые дают вам дополнительные возможности для работы со стеком: dup и swap. dup дублирует верхнее значение стека, swap меняет местами два верхних значения. В настоящее время Flash dup и swap, вы можете увидеть это при дизассеиблинге, но две эти команды имеют очень важное значение для оптимизации.
Каждое выражение ActionScript, независимо от его сложности, очищает стек после себя, чтобы избежать переполнения памяти. Работая с Flash, вы не видите весь этот байткод и вам не нужно об этом беспокоиться. Но производя с помощью flasm изменения на уровне байткода вы должны всегда помнить, что находится в стеке. Неправильные манипуляции со стеком чаще всего не приводят к ошибкам в проигрывателе. Вы не увидите десять тысяч мертвых записей в стеке, которые были порождены одним из циклов написанного вами кода, но скорость работы проигрывателя может очень снизиться и вероятно в
Пул констант
В начале каждого блока команд, в котором некие переменные, методы или строки используются более одного раза, Flash создает так называемый пул констант. Фактически, если хотя бы одна переменная используется дважды, создается пул для всех строк данного блока кода. Вот пример:
constants 'bottom', 'paused', 'aliensleft', 'fire'
В пуле констант может храниться до 65535 строк (теоретически). В последствии на них можно ссылаться из вашего кода через однобайтовый (первые 256 строк пула) или двухбайтовый индекс (остальные строки пула). Чаще всего в пуле хранится не более 256 строк, так что вы редко встретите в swf двухбайтовые ссылки. Практически, количество строк ограничено общим размером команды constants, а этот размер не может быть более 65535 байт, как и размер любой другой команды.
Помещение в стек строк или методов, а не ссылок на них в виде констант, отличается только размером кода, но не скоростью выполнения. Допустим, вы помещаете в стек строку "paused". push 'paused' после определения соответствующей константы (constants definition) сгенерирует байткод, выглядящий на самом деле как
push byte: the second constant from the list
"поместить в стек однобайтовую ссылку на вторую константу в из пула констант", а не
push string: 'paused'
т.е. "поместить в стек всю строку "paused", заметьте, строка займет в стеке значительно больше места, чем однобайтовая ссылка. Хотя сам Flash никогда не переопределяет пул констант
В режиме обновления (flasm -u foo.swf) flasm пересобирает все константы, удаляя пустые строки и те строки, на которые ссылка в коде встречается только один раз.
Регистры
Виртуальная машина Flash обладает четырьмя регистрами, которые адресуются как r:0, r:1, r:2, r:3. Доступ к регистрам осуществляется намного быстрее, чем доступ к переменным, так что лучше хранить наиболее часто используемые переменные в регистрах. Только r:0 в настоящее время используется Flash, так что вам есть где развернуться с вашей собственной оптимизацией. Чтобы сохранить setRegister:
push 'paused'
getVariable
setRegister r:1
Теперь значение переменной paused хранится в регистре r:1. В следующий раз, вместо того, чтобы обращаться к этой переменной через выражение paused, используйте выражение push r:1.
Примечание: В отличите от большинства других команд, setRegister не забирает верхнее значение из стека! Если вам не нужно хранить в стеке это значение, вы должны вручную вытолкнуть его оттуда, применив команду pop.
Область видимости
Я провел некоторое тестирование видимости регистров и стека из разных кадров.
Регистры не глобальны. Значение, хранящееся в регистре, если оно определено в одном из кадров области видимости _root, будет доступно только в этом кадре. Если там же определена некая функция или прикреплен мувиклип, они могут получить доступ к этому значению в регистре. Похоже на то, что как только в swf встречается тег showFrame, регистры снова исчезают, (т.е. очищаются?).
Во Flash 5 стек был глобальным. Если в первом кадре поместить в стек значение, то в пятом кадре это значение можно успешно трассировать. Оно также доступно из мувиклипов. Это означает, что Flash 5 не очищал стек для вас. Но во Flash MX ситуация изменилась: похоже, проигрыватель Flash MX очищает содержимое стека после выполнения каждого блока команд.
Что касается меня, я всегда подхожу к стеку и регистрам как к локальным, относительно кадров или событий. Даже если сейчас стек позволяет большее, все равно его поведение полностью зависит от внутреннего поведения
Насчет констант. Если пул констант определен в начале кадра, то он доступен из любой функции этого кадра, его не нужно переопределять. Flash компилирует код ActionScript именно таким образом, я никогда в дизассемблированном коде не встречал определений констант в функциях. И напротив, каждое событие имеет свой собственный пул констант.
Не очень понятно? Извините — приглашаю к дальнейшим исследованиям.
Синтаксис Ассемблера
Flasm 1.41 дизассемблирует/асссемблирует все команды, поддерживаемые Flash, независимо от версии. Однако со времен изначального flasm произошло много изменений в синтаксисе, так что сначала подекомпилируйте с помощью flasm 1.41, прежде чем попытаетесь компилировать.
Каждый flasm-проект должен начинаться с команды movie 'moviename.swf'. Где moviename.swf — имя вашего исходного .$wf. Если по каким-либо причинам обновление исходного файла невозможно, он сохраняется неизменным и резервная копия не создается.
Если Flash встречает атрибут compressed сразу после имени клипа movie 'moviename.swf' compressed),compressed определяет сжатие уже обновленного
Flasm чувствителен к регистру символов (за исключением строковых значений, которые могут быть чувствительными к регистру). Если в своих строках вы используете одинарные кавычки, превращайте их в 'it\'s beautiful'. Вы также можете заключать строки в двойные кавычки: "it's beautiful".
Комментарии точно такие же, как в ActionScript:
// вычисление расстояния
Многострочные комментарии:
/* вычисление
расстояния */
Ключевые концепции ассемблера и некоторые его команды описаны в этом документе, если вы дизассемблируете любой из ваших
Полный алфавитный указатель команд
| add | and | bitwiseAnd | bitwiseOr |
| bitwiseXor | branch | branchIfTrue | callFrame |
| callFunction | callMethod | chr | concat |
| constants | decrement | delete | delete2 |
| divide | dup | duplicateClip | enumerate |
| enumerateValue | equals | function | getMember |
| getProperty | getTimer | getVariable | getUrl |
| getUrl2 | gotoAndPlay | gotoAndStop | gotoFrame |
| gotoLabel | greaterThan | ifFrameLoaded | ifFrameLoadedExpr |
| #include | increment | initArray | initObject |
| instanceOf | int | lessThan | mbChr |
| loadMovie | loadMovieNum | loadVariables | loadVariablesNum |
| mbLength | mbOrd | mbSubstring | modulo |
| multiply | nextFrame | new | newMethod |
| not | oldAdd | oldEquals | oldLessThan |
| or | ord | play | pop |
| prevFrame | push | random | removeClip |
| return | setMember | setProperty | setRegister |
| setTarget | setTargetExpr | setVariable | shiftLeft |
| shiftRight | shiftRight2 | startDrag | stop |
| stopDrag | stopSounds | strictEquals | stringEq |
| stringLength | stringLessThan | substring | subtract |
| swap | swfAction | targetPath | toggleQuality |
| toNumber | toString | trace | typeof |
| var | varequals | with |
Я ввел некоторые дополнительные конструкции для соответствия структуре
frame
defineButton
defineMovieClip
movie
on
onClipEven
placeMovieClip
Также поддерживаются теги protect, enableDebugger и enableDebugger2.
Пожалуйста, не изменяйте структуру swf-файла! Это означает, что не нужно удалять, заменять или добавлять контейнеры для блоков команд! Хорошо, вы можете добавить или удалить некоторые события без особого вреда. Но если вы удалите кадр или измените id мувиклипа, flasm больше не сможет найти их или любые связанные с ними выражения при ассемблировании.
Неизвестные команды
Flasm понимает каждую команду у Flash 3/4/5/MX*, за исключением возможных подразновидностей кода, которые теперь Flash может использовать. Часть места для кода зарезервировано для приложений сторонних разработчиков. Например, программа Quicktime фирмы Apple добавляет для команд Quicktime - тег 0xAA. Flasm способен дизассемблировать/ассемблировать команды, не понимая их. В таком случае строка при дизассемблировании выглядит следующим образом:
swfAction 0x02 // Unknown action!
Если в команде есть дополнительные данные, в которых присутствует шестнадцатеричная часть:
swfAction 0xAA hexdata 0x43,0x12,0x18 // Unknown action!
Данные показываются как список шестнадцатеричных байтов, разделенных запятой. Если вы определили собственные команды для некоторых своих авторских приложений, то не нужно включать тег длины в поле шестнадцатеричных данных - длина вычислится и добавится автоматически, если будет обнаружено ключевое слово шестнадцатеричных данных. И не забудь, что только код 0x80 может быть дополнительными данными.
* Все команды Flash MX, которые я обнаружил, здесь есть. Но возможно некоторые и пропустил. Если обнаружите swfAction при дизассемблировании, сообщите пожалуйста!
Теги protect, enableDebugger и enableDebugger2
Как намек на авторизацию программы, Macromedia ввела protect (защиту). Она означает, что автор конкретного swf не хочет, чтобы тот открывался в среде разработки Flash. В действительности, подобная защита не действенна. Любая программа, которая имеет дело с swf, может просто ее игнорировать. Во Flasm protect можно увидеть, с возможностью ее добавления или удаления. Вы можете разместить ее в любом месте swf, хотя обычно она находится protect - не команда, поэтому она должна быть вне блоков команд, за их пределами. Компилятор Flash кодирует пароли как текст, длиной 28 символов. Flasm показывает кодированную строку, но не пароль. В действительности, первые 3 символа кажется всегда "$1$", вероятно это идентификатор схемы шифрования или
enableDebugger - другая попытка обеспечить безопасность содержимого swf. Если всегда защищаться паролем (Flasm будет показывать закодированную строку), то этот тег даст вам возможность "дистанционной отладки" swf. Если вы не знаете пароль, отладчик не позволит проникнуть внутрь. Если удалить пароль, отладчик тоже не позволит проникнуть внутрь. Но если изменить параметр у enableDebugger, как '$1$.e$7cXTDev5MooPv3voVnOMX1', то пустой пароль будет принят. Печально.
Уясним это четко и ясно: вышеуказанные теги, включая кодированные пароли, не дают реальной защиты и могут быть благополучно удалены или изменены.
Flash MX позволяет осуществлять отладку на уровне исходного кода, если есть новый тег enableDebugger2, который используется вместо enableDebugger. Хотя у него нет принципиальных отличий, Flasm не покажет другой тег (63) или содержимое внешнего файла, используемого отладчиком, не зная
Вложенные включения (includes)
Я получал просьбы, включить во flasm поддержку макроса #include, чтобы помочь поддерживать большие проекты, где было бы возможно быстро оперировать 5.000 строчек #include 'loop.flm', то этот макрос будет заменен содержимым loop.flm. Можно точно так же использовать вложенные и многократные вложения: foo.flm включает routine.flm, который включает loop.flm и calc.flm. Максимальная глубина вложений - 10.
Типы данных и команда push
Итак, push это основная команда в swf и мы подробно на ней остановимся. С тех пор как вы можете помещать все виды значений на стек, команда push имеет внутренний атрибут типа в swf. В то время как вы не видите и не можете обращаться к
| push-тип | Количество байт | Содержимое | Пример |
| 0 | string length + 1 | string | push 'Hello' |
| 1 | 4 | float | push Y_PROPERTY |
| 2 | 0 | null | push NULL |
| 3 | 0 | undefined | push UNDEF |
| 4 | 1 | register | push r:2 |
| 5 | 1 | boolean | push TRUE |
| 6 | 8 | double | push 3.1415926 |
| 7 | 4 | integer | push 25 |
| 8 | 1 | constant (0-255) | push 'Hello' |
| 9 | 2 | constant (256-65534) | push 'Hello' |
Строки должны заключаться в одинарные или двойные кавычки и могут содержать \b, \f, \n, \r, \t и \\. Нельзя прерывать линии внутри строки. Если flasm обнаружит оператор push 'Hello', он сперва поищет в пуле текущий блок действий. Если строка в нем определена, то будет помещена 1- или 2-байтовая ссылка (push-тип 8 или 9), если нет, то сама строка (тип 0).
Целые числа распознаются, как десятичные и шестнадцатеричные символы (0xF0F0). Числа с плавающей точкой: -3.1415926. Так же поддерживаются символы 9.4e-10_NAN, POSITIVE_INFINITY и NEGATIVE_INFINITY, определяются как с плавающей точкой.
0.0 считается, как число с плавающей точкой, 0 - как целое. Внутри себя компилятор Flash хранит 0, как число с плавающей точкой 0.0. В режиме обновления (update) flasm будет автоматически заменять все случаи 0.0 на 0, экономя 4 байта на каждой такой замене.
Push-тип 1 используется во Flash, чтобы только хранить свойство значений. Смотри getProperty/setProperty, чтобы увидеть список свойств констант. Flash 4 хранил все числовые значения, как строчные
Однако, Flash не единственная программа для создания swf. Сейчас я знаю, как минимум, одну программу стороннего производителя (3D-Flash Animator), которая использует тип 1 для свойства констант, если это возможно. Все значения, которые не могут быть приведены к -3.1415926f или 100.0f. Вы тоже можете использовать это положение в своих f, будет обработано, как переменная с одинарной точностью и сохранена в виде _NANF, POSITIVE_INFINITYF и NEGATIVE_INFINITYF.
Один оператор push может оперировать составным значений различных типов: push 'Hello', 3.141, XSCALE_PROPERTY.
Управление потоком
Переходы внутри блока команд осуществляются командами branch и branchIfTrue. Любая высокоуровневая конструкция управления ходом выполнения, такая как if (..) then .. else .. или while(..) do .. преобразуется в последовательность команд branch/branchIfTrue. Команда branch производит безусловный переход на указанную метку в коде. Например, конструкция if .. then .. else всегда содержит команду безусловного перехода (т.е. branch) после блока then, в результате выполнения которой происходит переход к концу блока if без выполнения кода в блоке else. Разрешены переходы как вперёд, так и назад (по отношению к команде перехода). В частности, переход назад используется при трансляции циклов.
branchiIfTrue извлекает значение с вершины стека (как правило, это результат работы предшествующей команды сравнения - прим. переводчика). Во Flash 5, если значение == true (извлечённое со стека значение в случае необходимости преобразуется к логическому типу), производится переход на указанную метку. Flash 4 сравнивает числа (а не логические значения) - если условие
Относительные смещения переходов хранятся в swf в виде чисел вместе с каждой командой перехода. При дизассемблировании flasm создаёт уникальные (в пределах одного блока команд - прим. переводчика) метки с именами label1 .. labelNlabelM или branchIfTrue labelN, где labelM и labelN - имена меток в коде, на которые производится переход. Использованные метки должны быть определены label5. Старайтесь использовать осмысленные имена меток (например, LoopStart:, SearchComplete: и им подобные).
Давайте рассмотрим пример в действительности быстрого цикла с обратным отсчётом, который не может быть написан на ActionScript (и не может быть декомпилирован в корректный ActionScript).
push 0,1,2,3,4,5,6,7,8,9,10 loopstart: dup trace branchIfTrue loopstart
Вначале на стек помещаются 10 значений. Примите во внимание, что последнее помещённое на стек значение (в нашем примере это число 10) окажется на вершине стека. Затем в цикле мы создаём копию находящегося на вершине стека значения, так как оно потребуется нам дважды: команда trace возьмёт со стека первое значение, branchIfTrue получит второе в качестве условия цикла. Так как при выполнении branchIfTrue числовое значение на вершине стека будет преобразовано в логическое, то выполнение цикла продолжится до тех пор, пока на вершине стека не окажется 0 (нуль), который будет преобразован в логическое значение false и послужит сигналом к выходу из цикла.
События кнопок
Каждое отдельное событие кнопки типа on(...) содержит одно или несколько из следующих событий:
| idleToOverUp | overUpToIdle | overUpToOverDown |
| overDownToOverUp | overDownToOutDown | outDownToOverDown |
| outDownToIdle | idleToOverDown | overDownToIdle |
| keyPress |
keyPress используется в форме keyPress 'символ' или keyPress константа, например, keyPress 'a' или keyPress _SPACE. Все константы, которые вы можете использовать при разработке Flash, определены как:
| _LEFT | _RIGHT | _UP | _DN | _HOME | _END | _INS |
| _DEL | _BACKSPACE | _ENTER | _PGUP | _PGDN | _TAB | _SPACE |
Вы можете изменять события кнопок с помощью flasm-кода.
getProperty и setProperty
Обработка операндов getProperty/setProperty во Flash в чем-то противоречива. ActionScript-функция getProperty("a",_y) может быть записана следующими способами:
| push 'a', 1 getProperty |
или | push 'a', '1' getProperty |
или | push 'a', Y_PROPERTY getProperty |
Вы (и Flash тоже) можете положить _y в стек как целое, как строку или как число с плавающей точкой. Все укладываемые в стек вышеперечисленные типы данных во Flash 5 обрабатываются без проблем, где нужно производится приведение типов. Во Flash 4, однако, нельзя было положить в стек целое число, а только строку или число с плавающей точкой. Следующая таблица показывает определенные во flasm константы свойств (они являются числами с плавающей точкой с точностью до одного знака после запятой):
| Название во Flash | Номер | Константа flasm | Значение константы |
| _x | 0 | X_PROPERTY | 0.0f |
| _y | 1 | Y_PROPERTY | 1.0f |
| _xscale | 2 | XSCALE_PROPERTY | 2.0f |
| _yscale | 3 | YSCALE_PROPERTY | 3.0f |
| _currentframe | 4 | CURRENTFRAME_PROPERTY | 4.0f |
| _totalframes | 5 | TOTALFRAMES_PROPERTY | 5.0f |
| _alpha | 6 | ALPHA_PROPERTY | 6.0f |
| _visible | 7 | VISIBLE_PROPERTY | 7.0f |
| _width | 8 | WIDTH_PROPERTY | 8.0f |
| _height | 9 | HEIGHT_PROPERTY | 9.0f |
| _rotation | 10 | ROTATION_PROPERTY | 10.0f |
| _target | 11 | TARGET_PROPERTY | 11.0f |
| _framesloaded | 12 | FRAMESLOADED_PROPERTY | 12.0f |
| _name | 13 | NAME_PROPERTY | 13.0f |
| _droptarget | 14 | DROPTARGET_PROPERTY | 14.0f |
| _url | 15 | URL_PROPERTY | 15.0f |
| _highquality | 16 | HIGHQUALITY_PROPERTY | 16.0f |
| _focusrect | 17 | FOCUSRECT_PROPERTY | 17.0f |
| _soundbuftime | 18 | SOUNDBUFTIME_PROPERTY | 18.0f |
| _quality | 19 | QUALITY_PROPERTY | 19.0f |
| _xmouse | 20 | XMOUSE_PROPERTY) | 20.0f |
| _ymouse | 21 | YMOUSE_PROPERTY | 21.0f |
По какой-то причине Flash 5 компилирует getProperty, используя push с числом с плавающей запятой, а setProperty, используя push с целым числом: так, setProperty("box2", _y, getProperty("box1", _y))
push 'box2', Y_PROPERTY, 'box1', 1
getProperty
setProperty
Если вы откомпилируете то же самое для формата Flash 4, то вместо целочисленного push будет использоваться push со строкой.
Flasm по возможности дизассемблирует push с плавающей точкой в одну из констант, перечисленных в таблице выше, потому что этот тип push'а используется Flash'ем только в контексте getProperty/setProperty. Если используются числа или строки, тем не менее, flasm даже не пытается найти их значения. Чтобы делать так, flasm должен видеть, что за этим скрывается, чтобы понять, например, означает ли push 2 push _xscale или этот push 2 производится для вычисления выражения 2 + 2. Жаль, но это за пределами возможностей дизассемблера.
Управление воспроизведением клипа
SWF поддерживает три операции для этой задачи: gotoFrame(номер кадра в качестве операнда), gotoFrame2(номер кадра берется из стека) и gotoLabel(метка кадра в качестве операнда). Во flasm имена операций gotoFrame и gotoLabel совпадают с их аналогами в SWF, а вот операция gotoFrame2 отсутствует. Для вашего удобства gotoFrame2 представлена двумя операциями gotoAndPlay/gotoAndStop. В SWF gotoFrame2 представляет одну операцию с байтовым флагом play/stop. Также, если у вас в проекте несколько сцен, Flash добавляет еще один аргумент – общее число кадров во всех сценах до той, на которую осуществляется переход. Это число будет помещено в стек и эти кадры будут пропущены флеш-плеером. Благодаря этому возможно использование gotoAndPlay/gotoAndStop c внутренней адресацией кадров относительно текущей сцены, вместо абсолютной адресации относительно начала SWF. Запомните, сцен в SWF не существует. В этом случае flasm вам выдаст что-то типа gotoAndStop skip 10. Заметьте, что при этом у вас возникнут проблемы, если в выражении вместо числового номера кадра будет указана строковая метка. Флеш-плеер, не задумываясь, добавит также количество кадров, которое нужно пропустить и попадет на неправильный кадр. Попробуйте использовать _root.gotoAndStop(). В данном случае вместо обычной команды используется метод MovieClip’a. Он не выполняет коррекции и правильно обработает метку.
К тому же, Flash 5 предлагает использовать gotoAndPlay/gotoAndStop методы объекта MovieClip для управления мувиклипами (передавая их, в виде строки). Сравните дизассемблированный код двух эквивалентных ActionScript-конструкций:
// tellTarget("myClip") gotoAndPlay(25);
setTarget 'myClip'
gotoFrame 24
play
end
и
// myClip.gotoAndPlay(25);
push 25, 1, 'myClip'
getVariable
push 'gotoAndPlay'
callMethod
pop
Методы во Flash 5 значительно медленнее старых базовых команд, но у вас есть возможность переписать их, используя ООП. Заметьте, что gotoFrame начинает отсчет кадров с нуля, в то время как методы "верхнего уровня" начинают отсчет кадров с единицы.
GotoLabel редко встречается в дизассемблинге, потому что флеш при экспорте SWF подставляет команду, использующую числовую адресацию кадров. Флеш оставит gotoLabel, только если не сможет вычислить номер кадра (если метка не в том же таймлайне?). Сами метки, тем не менее, все равно в SWF остаются и к ним можно получить доступ, даже если все переходы на эти метки были заменены.
enumerate и enumerateValue
Команда enumerate это нечто особенное. Вот как она работает:
NULL в стек.
Во Flash MX добавлена команда enumerateValue, вычисляющая безымянные объекты, которые уже находятся в стеке (не извлекая предварительно их имя).
Обе команды используются только в for .. in циклах. Flash циклически обрабатывает всех потомков объекта до тех пор, пока не будет найден NULL. Ссылка на текущего потомка хранится в r:0 для доступа из тела цикла. Такой цикл работает эффективнее чем обычный for или while циклы. Снижение производительности может произойти только при работе с очень большими массивами, потому что при этом стек перегружен большим количеством данных.
setTarget и setTargetExpr
Команда setTarget соответствует команде tellTarget в ActionScript. Если целевой объект задан в виде выражения, то используется команда setTargetExpr, которая извлекает из стека
setTarget '/defender' |
или | setTargetExpr |
Команды end на самом деле не существует, Flash использует setTarget '' чтобы отметить, где заканчивается "нацеленный" код.
setTarget '/defender' |
или | setTargetExpr |
Так как любой блок setTarget во Flash 5 оформляется одинаково, я решил отображать их в более "читабельном" виде. Flash 3 или 4 порой грешат отсутствием команд setTarget '' в конце блоков. В этом случае flasm сам добавит их в процессе дизассемблирования, закрывая блоки. Использование вложенных блоков setTarget не допускается.
ifFrameLoaded/ifFrameLoadedExpr
Блоки ifFrameLoaded frameNum .. end и ifFrameLoadedExpr .. end соответствуют теговым командам waitForFrame и waitForFrame2 в swf формате. ifFrameLoadedExpr извлекает номер кадра из стека.
Я выбрал ActionScript-подобные имена потому что ifFrameLoaded не допускается.
Внедрение flasm-кода в ActionScript
Если flasm запускается с ключом -u (flasm -u foo.swf), то он обрабатывает встроенные в ваш ActionScript макросы и добавляет в swf flasm-команды. Это не совсем похоже на внедрение ассемблера в Си или Паскаль. Нужно использовать специальный синтаксис, чтобы Flash смог откомпилировать скрипт без ошибок. В данный момент flasm поддерживает для этого две возможные конструкции в ActionScript: $flasm ... $end и $include(). Например:
$flasm "push 'Hello world!'" "push myTextField" "setVariable" $end
Этот набор команд делает то же самое, что и myTextField = "Hello world!";. Обратите внимание, что $flasm и $end - это переменные, а не функции, так что, пожалуйста, не пишите или . Все $flasm и $end должны быть заключены в двойные кавычки. Точку с запятой после команды ставить не требуется (но допустимо). Блоки $flasm ... $end можно вставлять в любом месте вашего скрипта, так что не беспокойтесь об этом. Есть ли $flasm блока, это не будет работать. Не задавайте кадров или мувиклипов во внедренном flasm. Если вы встраиваете, вы уже внутри
Все flasm-команды работают так, как вы и ожидаете, но нужно учитывать одну важную особенность - если вы объявляете константы во внедренных скриптах, flasm не изменит их значение, а добавит их в блок команд в корневой области. Также flasm реорганизует корневую область, удаляя пустые строки и константы, используемые только один раз, облегчая тем самым swf еще на несколько байт. Заметьте, что flasm не тронет ваши внедренные константы или строки, он перестроит только корневую область, созданную Flash'ем.
Угадайте, что делает команда $include("foo.flm")? Не думаю, что нужно пояснять. Одно важное замечание: используйте только обычные слэши и не используйте обратные в пути к файлу. Последние будут проигнорированы, если не удалены Flash'ем. И еще одно: не вставляйте $include() внутри блока $flasm .. $end.
Если в файле foo.flm объявляются константы, то эти строки будут добавлены в корневую область. Хотя $include("foo.flm") выглядит, как сокращенный аналог команд $flasm; "#include 'foo.flm'"; $end, это не так. Последнее выполняется в момент компиляции и не добавляет константы из foo.flm в корневую область. Вместо этого объявление констант удалит корневую область - будьте внимательны.
Не смотря на то, что flasm 1.41 прекрасно работает со встроенной в MX компрессией, для этого потребуется два дополнительных шага: декомпрессия и обратная компрессия. Если ваш компьютер слабоват, вы можете предпочесть отключить компрессию в настройках публикации. Вы всегда сможете сжать swf через flasm -z на последнем этапе перед выпуском продукта.
Проверка встроенных действий прямо из среды разработки
Конечно, вы можете экспортировать swf-файл, обновить его с помощью flasm, а затем проверить, нет ли ошибок. Но проверка прямо из среды разработки намного привлекательнее. К несчастью, среда разработчика Flash IDE не предусматривает программного интерфейса для вставки программ предварительной обработки типа flasm'а. И к счастью, Sven König нашел один способ, а я реализовал его во flasm'е. С настройкой этого дела придется поморочиться, но зато будет работать просто волшебно, как только вы получите это в свое распоряжение. Я опишу процедуру для Windows, для Macintosh'а она почти такая же. Итак:
1. Для Flash 5: скопируйте flasm.exe, libz.dll и flasm.ini в поддиректорию Browser в установочной директории Flash. Что касается Flash MX, которая где только не хранит свои настройки: сначала найдите нужную вам директорию и копируйте файлы в нее:
Windows 2000 или XP: C:\Documents and Settings\[имя пользователя]\Application Data\Macromedia\Flash MX\Configuration\Browser
Windows 98 или ME: C:\Windows\Application Data\Macromedia\Flash MX\Configuration\Browser
Windows NT: [Windows directory]\profiles\[имя пользователя]\Application Data\Macromedia\Flash MX\Configuration\Browser
Mac OS X: Hard Drive/Users/Library/Application Support/Macromedia/FlashMX/Configuration/Browser
Заметьте, что встраивание flasm'а во Flash на Маках мною не проверялось, так что напишите мне пару строк, если вам удастся это сделать.
2. Переименуйте flasm.exe в iexplore.exe
3. Создайте ярлык для вашего нового iexplore.exe в той же самой поддиректории. Не беспокойтесь, это никак не скажется на реальном броузере.
4. Откройте flasm.ini в текстовом редакторе. Измените значения flaplayer и flabrowser таким образом, чтобы они указывали пути к вашему или чего-то вроде этого. Даже если вы работаете на Win 2000, пожалуйста, используйте короткие имена файлов. Задайте значение flatest как "flaplayer", если хотите тестировать ваши файлы во "flabrowser", если будете тестировать в броузере. На моей машине flasm.ini выглядит как:
flaplayer = C:\GRAPHICS\Flash5~1\PLAYER\FlashPLA.EXE flabrowser = C:\PROGRA~1\INTERN~1\IEXPLORE.EXE flatest = flaplayer
Вы можете изменить значение flatest позже, при тестировании, причем для этого не потребуется никакой перезагрузки, ни машины, ни среды разработки Flash.
6. Все. Теперь откройте ваш файл во Flash, вставьте "HTML" и "use default names"F12 (предпросмотр публикации).
Мы просто заставили Flash думать, что flasm, это броузер. Flash будет компилировать swf, искать ярлык броузера, увидит, что имя (iexplore.exe) подходит и передаст имя
Could not start: c:\...\...\foo.exe
("Не могу запустить такой-то файл.."), из-за того, что в строке flaplayer или flabrowser написан неправильный путь. Исправьте путь и пробуйте снова.
Не работает? Не беспокойтесь, все в порядке, если вы дошли до этого места. Иногда Flash просто не запускает flasm. Введите что-нибудь в окно actions. Или снимите галочку "HTML" в настройках публикации, опубликуйте, установите галочку обратно и снова опубликуйте. Или удалите ярлык для iexplore из директории "browser" опубликуйте, восстановите ярлык.
Директива $include может не срабатывать в среде разработки Flash, если вы предварительно не экспортировали swf в нужное место, потому что Flash иногда компилирует swf в директорию по умолчанию.
Как при этом заниматься отладкой? Стек и регистры: в ресурсах есть ссылка на небольшой дебагер от Pavils Jurjans, вы можете подстроить его под свои нужды. Трассировка и переменные: используйте для этого
Техники оптимизации
Огромные растровые картинки, не оптимизированные векторные, неадекватные показатели частоты кадров, анимация многих клипов одновременно, использование больших XML-файлов, тонны текстовых полей с редактируемыми текстами, высококачественный потоковый звук или просто просмотр swf на Маках — в 95% всех случаев, низкая производительность swf не имеет ничего общего с ActionScript. flasm, хоть и является "еще одним крутым инструментом", не решит вышеуказанные проблемы. Оптимизация с помощью flasm имеет смысл в играх,
Если вы не знаете точно, где "узкое место" вашего проекта: то ли тормозит
В стандартном языке программирования, большая часть времени работы программы протекает в циклах и функциях, вызываемых из этих циклов. А во Flash есть еще и циклы в кадрах и частые или параллельно вызываемые события, они также должны быть исследованы. Период. Не пытайтесь оптимизировать каждую строку вашего кода — вы просто сделаете ее нечитабельной, за исключением некоторых особо важных мест и никто никогда не обратит внимание на 10.000 часов вашей изнурительной работы. После того, как вы обнаружили в коде критическое место, сначала попытайтесь найти для него алгоритм получше. Это место не может быть улучшено? В самом деле? Значит, начинайте изменять ActionScript.
Оптимизации кода ActionScript
Большой вопрос: помогает ли Flash MX повысить производительность? Пока реальное тестирование все еще никем не проведено, вот мое ощущение: в то время, как многие вещи типа производительности XML действительно стали лучше, старый, "устаревший" синтаксис Flash 4 все еще работает быстрее, чем красивый скрипт с точечной нотацией путей и большая часть советов в этой главе продолжает быть актуальной и для устаревших
Долгое время в эти советы не вносились изменения, если некоторые из них покажутся вам бессмысленными или не работающими во Flash MX, пожалуйста, сообщите об этом мне.
Блоки команд всегда выполняются от начала до конца, не прерываясь при этом ни событиями, ни командой gotoAndPlay(). Вот почему любой большой цикл for будет нагружать
Почему стиль кодирования Flash 5 работает медленнее, чем старые команды Flash 4? Casper Shuirink обнаружил забавную практику для Flash player 5: новые классы и методы внутри него создаются при помощи ActionScript! Включая алгоритмы работы со строками, методы массивов и мувиклипов — все. Большая их часть "завернута" в команды Flash 4. Посмотрите, что нашел Каспер. И снова: это не визуальное представление, это настоящий скрипт, использующийся внутри Flash 5.
Нелегко догадаться, из-за чего Flash может работать медленно. Математические функции, включая обработку чисел с плавающей запятой, в общем работают неплохо. Пути к мувиклипам в стиле Flash 5 очень удобны, но чертовски медленно работают. Наихудший пример: myMC.gotoAndStop(), работает в 25 раз медленнее, чем . Там, где важна производительность, всегда обращайтесь к объектам в синтаксисе Flash 4, пишите ../:myVar а не _parent.myVar. Также продолжайте использовать getProperty для составных путей типа a=getProperty("main/mc" add n,_x)eval. Если вы декомпилируете и сравните некоторые ваши файлы, вы сможете все увидеть сами, просто подсчитав количество компилируемых действий. Разве эти действия не считаются "устаревшими" во Flash 5? Читайте:
eval — это нечто особенное по сравнению со, скажем, this или любым другим ключевым словом ActionScript. Фактически, eval — это что-то вроде макроса, он не имеет собственного байткода, но просто пишет свои аргументы в стек во время компиляции. Несомненно, это работает быстрее.
Используйте tellTarget вместо with везде, где это возможно.
Определяйте локальные переменные функций с помощью ключевого слова var, но не используйте var в циклах. Локальные переменные работают быстрее (и вообще относятся к хорошей практике программирования). К несчастью, длина идентификатора также имеет значение, так что выбирайте для переменных короткие имена. Это утверждение можно распространить также и на встроенные функции. Создание шортката для функции t = Math.tan и замена всех вызовов Math.tan на t преследует две цели: во первых, не будет производиться лишний поиск объекта Math, а затем метода tan, и само по себе имя получается короче (а значит, обрабатывается быстрее). Это работает только для методов и функций Flash 5, функции Flash 4 будут замедляться.
Искусство инициализации строк: если вы в своем коде используете строчные функции Flash 4 типа , то сначала инициализируйте строки с помощью выражения a="my string". Flash 5 работает быстрее, если строки проинициализированы с помощью . Похоже на то, что
Некоторые функции работают медленно независимо от того, как вы их вызовете: действительно ужасна, и sp = String.split не помогает. Branden Hall создал собственные строчные функции, которые назначаются поверх используемых по умолчанию во Flash. Там, где для работы со строками невозможно использовать синтаксис Flash 4, используйте функции от Брендана.
Еще один пример: random() быстрее, чем и не такая уж плохая функция. Даже если на Math.random сделана короткая
Используйте a[a.length] = 25 вместо a.push(25). Да! Метод выглядит симпатичнее, ему даже соответствует меньший байткод, но он медленнее.
Насколько мне удалось установить при тестировании, многомерные массивы работают не медленнее, чем одномерные, хотя я слышал от людей, что они все же тормозят.
Трюк с заменой b = a*4 на b = a«2 (побитовый сдвиг) не добавляет скорости в ActionScript.
Flash пытается предварительно вычислять постоянные части ваших выражений. Порядок вычислений зависит от приоритета операторов. Как заметил Robert Penner, rad = Math.PI/180rad = c*Math.PI/180rad = c*(Math.PI/180)
Компилятор Flash также оптимизируют вызовы библиотечных функций с постоянными аргументами. Вызовы типа f = Math.sin(0.25)f = Math.max(3,5)if, условные выражения которых всегда разрешаются в false. Если вы проведете собственные тесты производительности, не смущайтесь, обнаружив вышеприведенные факты.
Немного общей оптимизации:
| не оптимизированный код | оптимизированный код |
| b = a+10; c = b*2; |
c = (b = a+10)*2;// Flash использует быстрый регистр |
| d = a*(b/n+c/n); e = b*(b/n+c/n); |
t = (b+c)/n; d = a*t; e = b*t; |
| for (i=0;i<10;i++) { if (a!=0) { someFunction(i*10); } } |
var d = someFunction; if (a) { for (var i=0;i<100;i+=10) { d(i); } } |
| for (i=0; i<x.length; i++) x[i] *= Math.PI*Math.cos(y); |
var len = x.length; pc = Math.PI*Math.cos(y); for (var i=0; i<len; i+=2) { x[i] *= pc; x[i+1] *= pc; } |
Циклы for и while не показывают различий в скорости выполнения. Все зависит от того, как вы их пишете. Наиболее оптимальная форма for(var i = 10; i--;) {}i = 10; while (i--) {}for, отсутствующая в моем примере, на самом деле находится в теле цикла, так что вы можете сравнивать его с нормальным циклом while.
Цикл for .. in, использующий ключевое слово enumerate, будет пробегать через массив слегка быстрее, чем обычный цикл for — как минимум для массивов среднего размера. Для огромных массивов истинным является обратное утверждение.
Избегайте множественных параллельных функций hitTest() в событиях, это часто встречается в играх. Если игрок должен умереть после любого столкновения с врагом и у вас есть 100 дублированных мувиклипов с врагами, не навешивайте код на событие enterFrame вражеских клипов. Создайте новый мувиклип и вставьте вражеский клип в него. Затем дублируйте врагов внутри этого родительского клипа. Теперь вы можете проверять только один hitTest(), если имеет место столкновение. Если нужно, используйте математику для вычисления конкретного врага, с которым произошло столкновение. Так как большую часть времени никаких столкновений не происходит, вы получите действительно значительное повышение частоты кадров (fps). Если нужно проверять столкновения многих объектов друг с другом, начните с чтения этой статьи.
Flash-player для Макинтоша очень медленный по сравнению с аналогичным для PC. Начинайте тестирование на маках как можно раньше.
Производя тесты, помните: встроенный в среду разработки
Список ни коим образом нельзя считать полным, есть еще тонны вещей, которые я еще сам должен обнаружить. Я по большей части не говорю "Медленнее в 3.45 раза", потому что такие сравнения очень
Flasm-оптимизации
После того, как вы закончили оптимизацию ActionScript, можно приступать к оптимизации с помощью flasm. В общем, есть две важные низкоуровневые штуки, недоступные из ActionScript и таким образом являющиеся предметом для работы flasm'а: стек и регистры. Давайте для начала прооптимизируем простой цикл, используя для этого стек. Вот наш код ActionScript:
for (n=0; n<1000; n++) {
someFunction(n)
}
Flash компилирует этот цикл в следующие байтовые коды:
constants 'n', 'someFunction' // Сохраняет все константы в пуле
// констант
push 'n', 0.0 // Кладет строку 'n' и начальное
// значение 0 в стек
setVariable // Инициализация счетчика цикла: n = 0
label1: // Начало цикла
push 'n'
getVariable // Снова берет значение переменной 'n'
push 1000 // Кладет в стек предельное значение
// для цикла
lessThan // Вычисляет булево выражение: "n<1000?"
not // Инвертирует: теперь "n >= 1000?"
branchIfTrue label2 // Если в стеке "true", идет к его концу
push 'n' // Тело цикла
getVariable // Снова берет значение 'n'
push 1, 'someFunction' // Кладет в стек кол-во аргументов (1)
// и имя функции
callFunction // Вызывает функцию с n в качестве
// аргумента
pop // Выталкивает из стека возможный
// результат выполнения функции — циклу
// он не нужен
push 'n', 'n' // Дважды кладет 'n' в стек
getVariable // Снова вычисляет 'n'
increment // теперь в стеке n+1
setVariable // n = n+1
branch label1 // безусловно переходит на начало
// цикла
label2: // конец цикла — вызванный
// вышеприведенным branchIfTrue
Сразу можно заметить, что переменная n вычисляется здесь множество раз. Действие getVariable работает медленнее, чем операции со стеком и n используется только как локальный счетчик. Почему бы не отказаться от переменной n, хранить счетчик в стеке, использовать его снова и снова, таким образом устранив все вызовы getVariable? Нам также не нужно объявление пула констант, так как n исчезнет, а имя функции someFunction будет использовано всего один раз. Количество переходов (jumps) также может быть уменьшено до одного. Мы знаем, что должны вызвать someFunction(0), так что нет необходимости проверять условие в начале цикла. Посмотрите на оптимизированную версию:
push 0 // Не нужно дублировать 0.0, и целое число 0
// справится
loopStart: // Выбираем осмысленное название
dup // Дублируем (dup) счетчик — наша функция
// "съест это"
push 1, 'someFunction'' // Кладем в стек кол-во аргументов (1)
// и имя функции
callFunction // производятся вызовы функции с аргументом n
pop // Выталкиваем из стека неиспользуемый
// результат выполнения функции
// Теперь счетчик снова наверху стека
increment // Увеличиваем его
dup // Дублируем (dup) счетчик — вычисление
// условия "съест" его
push 1000 // Кладем в стек конечное значение цикла
lessThan // Вычисляем условие: counter < 1000?
branchIfTrue loopStart // Прыгаем в начало цикла, счетчик наверху
// стека
pop // Следует удалить счетчик из стека после
// завершения цикла
Мы даже можем сделать больше. Скажем, если наша функция заполняет массив некими вычисленными значениями, то нет разницы, считать ли от 0 до 999 или в обратном порядке от 999 "вниз" до 0. В этом случае мы можем избавиться от команды lessThan, так как команда branchIfTrue умеет преобразовывать значение 0 в "false", а все другие числа в "true".
push 1000
loopStart:
decrement
dup
push 1, 'someFunction''
callFunction
pop
dup
branchIfTrue loopStart
pop
Мы переместили команду decrement на вершину цикла, потому что в противном случае команда branchIfTrue автоматически привела бы к выходу из цикла, если значение переменной counter равно 0 и не позволило бы нам выполнить someFunction(0).
Как видите, мы пришли к весьма чистой версии цикла и это будет работать намного быстрее, чем оригинальный цикл Flash. Насколько именно это будет быстрее работать, зависит от того, что делает функция someFunction(). На следующем шаге мы оптимизируем ее.
Теперь подумаем, стоит ли вообще использовать регистры? Они быстрее, чем переменные, но все же медленнее, чем стек. Почему бы не держать все величины в стеке, так, чтобы они перемещались наверх точно в тот момент, когда они нужны?
Проблема в том, что если вы работаете с двумя или более переменными, ваш алгоритм может затребовать их в иной очередности, чем та, в которой они хранятся. Если некое значение требуется, скажем, в начале и в конце вашей функции — тогда нет проблем, оно счастливо живет себе на дне стека, ожидая момента, когда его позовут и позволяет вам работать с другими значениями, находящимися наверху. Но для часто затребуемых значений это не работает. В то время как у нас есть команда swap, меняющая местами два верхних значения в стеке, мы не можем получить прямой доступ к третьему. Даже если найдете некий иллюзорный метод доступа сразу ко многим переменным, вы просто тормознете выполнение кода большим количеством команд swap.
В общем: dup конечно быстрее чем setRegister r:1 и затем push r:1. Но если вам нужно делать swap, ситуация меняется: комбинация dup/swap не быстрее, чем setRegister/push. Фактически, она будет даже немного медленнее, если вы сольете push r:1 с другими ему подобными: push r:3, r:1, X_PROPERTY
Хватит слов, приступим к оптимизации реального кода ActionScript. Давайте напишем функцию получения минимального значения в массиве чисел. Наш код выглядит так:
Array.prototype.min = function () {
var m = Number.MAX_VALUE;
var i = this.length;
while (i-- > 0) {
if (this[i] < m)
m = this[i];
}
return m;
}
Если мы начнем цикл с var m = this[0]; и затем будем считать "вниз" до 1 вместо 0, функция выполнится немного быстрее. Хотя это нам не поможет. Мы попробуем делать проверку на 0 позже в байткодах. Проверка на 1 не нужна.
Мы можем также попробовать этот код:
Array.prototype.min = function () {
var m = Number.MAX_VALUE;
for (var i in this) {
if (this[i] < m)
if (typeof(this[i]) == 'number')
m = this[i];
}
return m;
}
Думаю, что в данном случае это лучшая из всех возможных for .. in в более эффективные байткоды, чем в случае с
push 'Array' // Array.prototype.min
getVariable
push 'prototype'
getMember
push 'min'
function ()
push 'this'
getVariable // вычислить 'this'
setRegister r:1 // сохранить его в r:1
pop // удалить его из стека
push 1.79769313486231e+308 // Number.MAX_VALUE
setRegister r:2 // сохранить Number.MAX_VALUE в r:2
pop // удалить его из стека
push 'this.length'
getVariable // вычислить this.length
// цикл вниз до 0
loopStart: // counter в стеке
decrement // counter--
setRegister r:0 // сохранить counter в r:0
push r:0, r:2, r:1, r:0 // стек:
// counter, this, min, counter, counter
getMember // стек:
// this[counter], min, counter, counter
lessThan // стек:
// min<this[counter]?, counter, counter
branchIfTrue loopContinue // стек: counter, counter
// если условие не выполняется, найдено
// новое минимальное значение
// (а мы ищем min)
push r:1, r:0 // стек: counter, this, counter, counter
getMember // стек: this[counter], counter, counter
setRegister r:2 // сохранить this[counter] в r:2 как новое
// значение min
pop // удалить его из стека
// стек: counter, counter
loopContinue: // проверить, достигли ли мы уже 0:
branchIfTrue loopStart // стек после команды branch: counter
// цикл закончен, теперь почистим стек:
pop // вынем последнее значение counter
// (равно 0)
push r:2 // заберем min из r:2
return // возвращаем min
end
setMember // назначаем функции Array.prototype.min
Функция оптимизирована для больших случайных массивов. Самое "трудное" место оптимизировано максимально — все команды push скомпонованы в одно выражение. Мы даже допускаем множественные присвоения одинаковых значений переменной min (когда min == this[counter])
А вот оптимизированная версия цикла for .. in:
function () push 'this' getVariable setRegister r:1 pop push 1.79769313486231e+308 setRegister r:2 pop push 'this' enumerate loopStart: setRegister r:0 push NULL equals branchIfTrue loopEnd push r:2, r:1, r:0 getMember lessThan branchIfTrue loopStart push r:1, r:0 getMember setRegister r:2 pop branch loopStart loopEnd: push r:2 return end
Как видите, эти два варианта частично похожи. Интересное место представляет собой enumerate. Все ссылки на элементы массива укладываются Флэшем в стек за одно действие. На самом дне хранится значение NULL, цикл постоянно ищет его как условие своего завершения. Такой код в
Итак, насколько быстры наши функции после всего этого? Оптимизированная версия "нормального цикла" в три раза быстрее, чем самая быстрая версия на ActionScript. А оптимизированная версия цикла for .. in быстрее приблизительно
И последнее замечание: кроме оптимизации существующего кода ActionScript, во Flash есть такие вещи, которые вы просто не можете делать (либо они получатся крайне неэффективными), не привлекая байтовые коды. Посмотрите на высказывание Роберта Пеннера о передаче различного количества аргументов в функции.
Двойные отрицания
В некоторых случаях Flash пишет в вашем коде двойные отрицания (nots). Рассмотрите код if (a<=b) ... else ... Поскольку указаны только lessThan и branchIfTrue, а не moreThan или branchIfFalse, созданы две инверсии:
push 'b' getVariable push 'a' getVariable lessThan // a>b? not // now inverted: a<=b? not // prepare for branch to the else condition: again a>b? branchIfTrue elseCondition
Как вы видите, Flash 5 не слишком гибко трактует ваши требования, не меняет порядок операндов в выражении и не использует иной образец для условного оператора if. Это действительно не имеет смысла. Единственной причиной этого может быть попытка преобразования в Boolean. Однако следующая команда, которую вы видите в коде – branchIfTrue. И эта команда преобразует типы непосредственно. Таким образом, flasm автоматически удалит эти nots в update mode (в режиме модификации).
Благодарности
Мои особые благодарности предназначены людям из рассылки Flashcoders, чьи идеи помогли мне лучше понять оптимизацию и выразились в вышеупомянутых примерах:
Rasheed Abdal-Aziz, Ralf Bokelberg, Robin Debreuil, Zeh Fernando, Gary Fixler, Branden Hall, Dave Hayden, Damien Morton, Amos Olson, Robert Penner, Casper Schuirink.
Устаревший синтаксис
Поскольку Macromedia продолжает называть некоторые команды "устаревшими" (deprecated), я также решил оставить мои нижеследующие выкладки неизменными. Они были написаны достаточно давно, оказались верными, и теперь, вероятно, относятся к Flash 7 (или Flash XXL). Моя единственная ошибка - MX IDE не поддерживает устаревшие команды.
Всякий раз при использовании команд или синтаксиса
Когда вышел релиз Flash 6, он не повлиял на работу созданных ранее
Если код Flash 4 не будет поддерживаться во Flash 6 IDE (весьма вероятно), это значит, что вам придется внести некоторые изменения в ваш код и только если вы обновляете его в
Если нет – в конце концов, есть возможность полностью заменить скрипт. Если нет – кто-нибудь напишет утилиту для этого. Можете ли вы представить себе, что Flash 6 не сможет открыть Flash 5
Что касается меня, я посоветую не беспокоиться насчет устаревших команд, а просто писать код, наиболее соответствующий решению вашей задачи. Тем не менее, я не убеждаю вас забывать о синтаксисе Flash 5. Использовать Flash 4 имеет смысл лишь в случае проблем со скоростью.
Различия в размерах файлов
После компиляции из flasm-исходника или публикации swf из flasm, вы в большинстве случаев обнаружите, что ваш swf уменьшился на несколько байт, даже если вы ничего не меняли в байткоде. Помимо тривиальной оптимизации, которую flasm делает при обновлении (update mode), для этого есть еще одна причина. По определению, Flash может использовать в swf блоки длиной в 2 или 6 байт. Причем 6 байт требуются только в том случае, если длина блока (в исходнике) превышает 62 байта. Flash, тем не менее, довольно часто использует в swf записи длиной в 6 байт там, где достаточно 2 байт. Хотя flasm в некоторых случаях делает так же, большинство блоков оптимизируются в процессе компиляции. Например, я экономлю 400 байт на файле в 90 Кб, ничего не оптимизируя. Не знаю, есть ли тут особая выгода, но по крайней мере, можно радоваться неожиданному подарку от flasm.
Большие скрипты
Хотя хорошей практикой остается удерживать размер (компилированного) кода в пределах 64 Кб на фрейм, иногда код получается гораздо большего объема. Но основные actions – константы, функции и другие, - ограничены 64k по причине длины поля в 2 байта. Превышение станет возможным с того момента, когда Flash создаст (в 99% случаях) постоянное согласование всех переменных и методов. К сожалению, Flash сам не может сообщить вам, что код слишком велик. Компилятор флеша записывает превышающие значения длины записей без ошибок или предупреждений. Если вы пытаетесь воспроизвести swf, у
Ошибки и сбои
Adobe LiveMotion 2 помещает в события странный код. Flasm укажет на непонятный "скрипт", который вовсе не предназначен для выполнения, однако вводит в заблуждение (или попросту засоряет память). Наиболее вероятно, это ошибка в LiveMotion, поскольку это не соответствует формату swf. Возможно, ОНИ знают
Мне не известно о других ошибках. Если вы обнаружите любую нетривиальную ошибку – без стеснения вышлите мне ваш файл. Поясню: если вы дизассемблируете swf и ассемблируете его обратно без изменений, результирующий файл обязан работать должным образом.
Режим обновления (Update mode) также должен работать. Во flasm нет ничего необъяснимого или каких-либо недокументированных особенностей.
Если у вас возникла проблема - должно быть, в нашем инструменте есть серьезная ошибка, и ваше сообщение для нас очень ценно.
Ресурсы
Данная страница в оригинале лежит по адресу http://flasm.sourceforge.net или http://www.nowrap.de/flasm.html.
Исходник на том же сервере: http://sourceforge.net/projects/flasm, хотя несколько устаревший. Так как я – единственный, кто работает над flasm в настоящее время, бессмысленно постоянно синхронизировать все ресурсы. Возьмите самый свежий файл отсюда.
Оригинальная страница flasm: http://www.opaque.net/~dave/flasm/. Там постоянно находится первая версия и полезные объяснения Дейва по поводу
Замечательный пример - 3D-движок от Florian Krüsch, оптимизированный при помощи flasm.
Сравните анимацию дерева, сделанную Amos Olson: стандартный ActionScript и оптимизированный.
Посмотрите path finding swf сделанный с помощью flasm от Casper Schuirink. Здесь исходник.
Грустная история: David Emberton поддерживал flasmaniacs рассылку посвященную исключительно flasm. Его провайдер неожиданно прекратил поддержку рассылки, теперь ни рассылка, ни архив не доступны. Спасибо Branden Hall, новый SWFcoders mailing list стартовал (19.08.02). Для подписки пошлите пустое сообщение на swfcoders-subscribe@chattyfig.figleaf.com (нормальный режим рассылки) или swfcoders-digest-subscribe@chattyfig.figleaf.com (рассылка в режиме дайджеста).
Многие вопросы касаемые ActionScript обсуждаются в популярном листе, модерируемом Branden Hall, на http://chattyfig.figleaf.com. Это место, где можно многое узнать также и про оптимизацию flasm. Хотя не вздумайте спросить там
Flashcoders Wiki обещает превратиться в самый совершенный и современный ресурс по ActionScript, очень информативно.
http://www.openswf.org ресурс для обсуждения формата swf. В настоящее время доступны только описания по Flash 4. Будет ли больше?
Вы можете захотеть посетить Macromedia Flash player format and SDK licensing на http://www.macromedia.com/software/Flash/open/licensing/. С тех пор как я перестал пользоваться их ресурсом, я не знаю, насколько подробна их специализация Flash 5.
На сайте посвященном прототипам вы найдете функции, используемые Flash, но улучшенные (для скорости и универсальности), а также много полезных новых. Часто лучше начинать оптимизацию flasm с одной из них.
Pavils Jurjans написал небольшой debugger для flasm, полезного при внедрении кода flasm в ActionScript. Отладчик показывает стек и регистрирует контент. Я надеюсь,
Albert Chosky сделал файлы подсветки синтаксиса flasm для EditPlus. Если вы используете EditPlus, полезно будет скачать их.
Для людей, которые не любят работать с командной строкой и не хотят регистрировать flasm как обработчик swf в эксплорере, есть winflasm от Sharriff Aina - простой оконный интерфейс для flasm.
Файл подсветки синтаксиса для UltraEdit, предоставленный анонимным русским flasmer. Не знаю, насколько он закончен, но спасибо в любом случае.
Условия использования
Да, flasm абсолютно бесплатен и распространяется согласно BSD-style лицензии:
Copyright (c) 2001, 2002 Opaque Industries and Igor Kogan
All rights reserved.
Распространение и использование в исходном или бинарном коде, с изменениями или без, разрешаются при условии, что нижеследующие соглашения выполнены:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Macromedia(r) и Flash(tm) зарегистрированные торговые марки, собственность Macromedia, Inc., в США и/или других странах.
Macromedia(r) не является спонсором, партнером или гарантом данного продукта и/или услуг.
История изменений
flasm 1.4
полностью поддерживает новые возможности, предоставленные Flash MX:
initialize event - параметры компонента больше не ставятся с событием load в отличие от использования для smartclips в Flash 5, а с новым initialize событием.
initMovieClip tag - новый тег работает с component initialization: смотри greaterThan - противоположность lessThan, теперь используем в циклах и условных перходах, instanceOf - проверяет, является ли объект экземпляром класса, strictEquals - см. во Flash MX help "===" и "!==" операторы; enumerateValue
libz.dll (включенный в дистрибутив) должен быть помешен в директории flasm или Windows. В настоящее время, flasm не может воспроизводить сжатые swf непосредственно. Прежде всего, вы должны будете произвести декомпрессию: flasm -x foo.swf вы сможете потом сжать ваш swf при помощи: flasm -z foo.swf Эта "неспособность" также касается среды разработки, где вам придется снять выделение с опции "compress" в диалоговом окне "publish settings".
flasm 1.36
flasm 1.35
flasm.ini используется только в embedding режиме. Сообщите мне, если есть желание перенастраивать flasm.
waitForFrameExpr/waitForFrame .. else skip .. заменена на ifFrameLoaded/ifFrameLoadedExpr и поддерживает стандартный синтаксис: ifFrameLoaded frameNum .. end, ifFrameLoadedExpr .. end
flasm 1.32
Number.MAX_VALUE и Number.MIN_VALUE вычисляются правильно.getUrl.
$include. Исправлены некоторые ошибки в $include.
Ключевое слово, используемое во flasm для unknown actions support переименовано в swfAction чтобы предотвратить конфликт с Branden Hall's ACK eventEngine.
add.
flasm 1.3
parse error" добавлены несколько специальных сообщений. Пожалуйста скажите мне, где Вам хотелось бы видеть предупреждения.
gotoAndPlay/gotoAndStop команд благодаря Sven König, который заметил ошибку.
flasm 1.22
NAME_PROPERTY исправлено.
\b, \f, \n, \r, \t и \\. Строка кода ActionScript, содержащая эти символы, теперь будет дизассемблирована без создания неожиданных окончаний строк или других аномалий.
flasm 1.21
foo.$$$ в foo.swf.
53.39999999999964 вместо 53.4.
toString соответствующее a = String(b); в AS.
flasm 1.2
Это была моя первая "официальная" версия, главным отличием от оригинала Дейва была способность иметь дело с целым swf. Добавлено дизассемблирование событий, несколько отсутствующих байткодов, исправлено несколько ошибок. Не помню точно все изменения, этой страницы еще не было.
Состояние проекта
Плохие новости: я не собираюсь реализовать новые восхитительные возможности. Время идет и я уже довольно сильно потерял к этому интерес. Сейчас погружен в другие вещи: Linux, Python и т.д. Хорошие новости: flasm работает достаточно стабильно, а исправления глюков (если будут такие) будут проводиться. Возможно, сейчас лучшее время для того, чтобы поднять flasm на уровень выше и сделать его Самым Важным Инструментом Flash для каждого :)
Вот кое-что из того, что я хотел бы видеть в новых релизах:
Генерирование XML
Чтобы flasm парсил XML и на лету писал для него соответствующие
P.S. Это намерение стало устаревшим, так как во Flash MX производительность работы с XML повысилась, но только для Flash MX player, Flash 5 player по прежнему слаб.
Защита от декомпиляторов
Задача не легкая, но выполнимая. Сейчас я могу запутать текущие версии декомпиляторов, но они потратят всего неделю, чтобы это пофиксить, так что "вешать" декомпилятор - не очень хорошая идея. Лучшей схемой защиты является та, при которой не возникают ошибки в декомпиляторе, но код просто не работает после перекомпиляции. Я все еще не знаю точно, как это сделать.
Второй лучший способ: декомпилятор на самом деле не производит выполнение кода или воспроизведение swf, так что наличие определенных свойств во время выполнения может быть использовано для создания "непредсказуемых" ветвлений кода. Если вы сделаете что-то вроде if (root._width>0), у вас есть совершенное ветвление, его результат предопределен и в то же время он не может быть определен obfuscator'ом. Он может декодировать if, но не знает, куда ведет ветка кода. В блоке else (мертвый код) вы можете разместить любой сбивающий с толку мусор. Теперь управляющий поток нечитаем, потери в скорости минимальны, ведь произошла только замена безусловного перехода на условный.
Третий лучший способ: запутывание. Это означает переименование всех переменных, функций и мувиклипов в нечитаемый мусор. Робин Дебройл реализовал этот метод в своем Viewer Screwer'е. По разным причинам выполнить такую задачу очень сложно. Можно было бы создавать все типы объектов, основываясь на именах объектов во время выполнения. Но это означает, что вы никогда не будете уверены в правильной работе вашего "запутывальщика" на любом swf без побочных эффектов.
Опасная возможность размещения кода в других тегах, скажем, в тегах для растровых изображений и перехода туда и обратно, скорее убьет сам Flash player. Декомпилятор все еще будет способен переходить по условиям и декодировать это. Так что я лучше буду оперировать обработкой условных переходов так, как того ожидает Flash player.
Замена переменных регистров в режиме IDE
Если кто-то пишет $r1 = a+b в ActionScript, flasm обновит swf и сохранит a+b в r1. Это ясная и хорошая концепция, предложенная когда-то Eli Jehoel, но (вопреки ожиданиям) это не так легко реализовать. В двух словах - flasm должен будет постоянно вести учет содержимого стека.
ActionScript profiler/tracer
Трэйсер очень нужен - если вы работали с flasm из среды разработки Flash, вы это уже знаете. Профайлер: flasm писал бы код профайлера в начале/конце каждого кадра/события/функции. Затем значения (количество вызовов, максимальное время выполнения) были бы сохранены, скажем, в Object.profiler и к ним можно было бы получить доступ из ActionScript. Это скорее задача интерфейса Flash, ее не так сложно реализовать на языке C. Во Flash нам нужно скроллируемое/перетаскиваемое/масштабируемое/минимизируемое окно, содержащее результаты трассирования и записи профайлера. В профайлере пользователю надо дать возможность сортировать записи по различным критериям (вы знаете, это как в ms outlook).
Как вы можете помочь
Если вы более-менее понимаете формат SWF, знаете C и слышали о Flex и Bison, вы можете помочь в разработке. В настоящее время код не очень чист, так что не стесняйтесь спрашивать меня. И в любом случае есть хорошие шансы, что вы окажетесь лучшим разработчиком, чем я!
Я хочу знать о вашем опыте применения flasm в реальных проектах. Что-то нелогично? Баг-репорты? Нужны новые возможности? Пожалуйста не спрашивайте хоть о
Кстати о Маках. У меня нет Мака и я пока не собираюсь его заводить. Было бы хорошо, если бы
Но еще интереснее узнать о ваших оптимизациях. Если я пропустил
И напоследок: я не считаю свой английский ужасным. Не думаю также, что он хорош (в любом случае он сух и беден в сравнении с моим русским или немецким). Если вы видите в этом документе
Наслаждайтесь