Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Регистрация Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > Psycho Tiger

Оценить эту запись

Байтшкодим: Header

Запись от Psycho Tiger размещена 08.09.2010 в 16:17
Обновил(-а) Psycho Tiger 13.09.2010 в 23:25

Представим себе ситуацию: нужно загрузить swf`ку и требуется срочным образом узнать о её характеристиках, до того как swf`ка сама поймёт, что она swf`ка и сможет нам это сказать. Нормальные люди хранят, например, xml файл с нужным описанием или что нибудь-ещё. Но не мы.

К тому же, мы совсем не признаем сжатие swf`ек. Мы считаем, что сжатые swf`ки - пережиток прошлого, с нашими пропускными способностями каналов связи это оскорбление экономить на размере.

А теперь чуть серьезней: будем смотреть на байткод с точки зрения практического применения. Применение найти сложно, поэтому мы притащили задачу описанную выше за уши. Несжатая swf`ка гораздо проще воспринимается на глаз в HEX-редакторе, чем сжатая. Поэтому работать будем с несжатой swf`кой. Сегодня мы разберём Header swf`ки - кусок информации, в котором содержится её размеры, framerate и кое-что другое.

Итак, что такое байткод?
Цитата:
Байт-код или байтко́д (англ. byte-code), иногда также используется термин псевдоко́д — машинно-независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт-кода эквивалентны одной или нескольким командам ассемблера. Трансляция в байт-код занимает промежуточное положение между компиляцией в машинный код и интерпретацией.
Теперь давайте с вами договоримся: я буду называть байткодом всю ерунду, которая содержится внутри swf файла. Конкретно сегодня мы будем работать с заголовком. Код, который исполняется AVM внутри тега doABC я буду называть abc-кодом. Для тех кто не понял что такое abc-код скажу проще: это те команды, которые компилируются из AS в тот-самый-байткод. О нём я, возможно, расскажу как нибудь позже.

Итак, нам понадобится:
Калькулятор
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
Не очень красивое форматирование, но каждый момент я проговорю отдельно.
Итак, поехали.
Первый байт:
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`е.
Код AS3:
_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);
Код AS3:
private function log(...args):void {
	var l:int = args.length;
	for (var i:int = 0; i < l; i++) _console.appendText(args[i].toString() + "\n");
}
log - это типа аналог trace, чтобы выводило на экран. Мне так хочется.

Мы создаём urlStream и просим его загрузить testFull.swf, теперь нужно дождаться пока поступят байты: это дело контролирует onProgress.
Теперь толстый момент: размер Header`а не определен заранее и в целях упрощения подачи материала мне хотелось бы чтобы при начале его разбора он полностью был загружен. Обычно он занимает байтов 20, мы подождем когда загрузим 50, чтобы уж точно наверняка. Я не считаю это за "плохой" приём: скорость интернета измеряется в мегабитах - 30 байт... О чем речь?

Код AS3:
private function onProgress(event:ProgressEvent):void {
	log("Progress event. BytesAviable: " + _urlStream.bytesAvailable);
	if (_urlStream.bytesAvailable > 50) {
		_urlStream.removeEventListener(ProgressEvent.PROGRESS, onProgress);
		parse(); //50 байтов для хеадера нам хватит заглаза
}
		}
Код AS3:
		private function parse():void
		{
 
			_urlStream.endian = Endian.LITTLE_ENDIAN;
			if (_urlStream.readUTFBytes(3) != "FWS") {
				log("Invalid file");
				return;
			}
SWF структура для unsigned integer`ов любит быть LITTLE_ENDIAN - и загружать её мы будем так же, т.к. в основном с ними нам и предстоит работать. Сразу скажу, что в случае с битами дело обстоит наоборот - они хранятся как BIG_ENDIAN - но с ними мы будем работать чуть позже и обходными путями.
Вот например из оф. доки пример, как хранится число:
Цитата:
The 32-bit value 0x456e7120 is stored as 20 71 6e 45.
Сразу считаем первые 3 байта как строку и если она не такая, как нам нужна - то вываливаемся и прекращаем работу. Всё просто.

Далее идёт:
Цитата:
Version UI8 Single byte file version (for example, 0x06 for SWF 6)
И это снова byte. Просто считываем byte и выводим.
Код AS3:
log("Version of file: " + _urlStream.readByte().toString());
Далее:
Цитата:
FileLength UI32 Length of entire file in bytes
UI32, обратите внимание. Это 32 бита, т.е. 4 байта. Ещё тонкий момент: это дело хранит размер файла после декомпрессии. В нашем случае - FWS - разжимать нечего, поэтому размер файла совпадает с оригинальным. В случае с CWS они будут расходиться: это сделано для более эффективного разжимания.
32 бита считывает метод readUnsignedInt:
Код AS3:
log("File length: " + _urlStream.readUnsignedInt());
А вот теперь сложнее.
Цитата:
FrameSize RECT Frame size in twips
Есть тип данных такой - RECT, это как видно из названия прямоугольник. Он задает 4 свойства: xMin, xMax, yMin, yMax. Вроде всё просто, элементарный прямоугольник, но он немного сложен своим заданием: выравнивание происходит не по байтам, а по битам.
Как это? Сейчас объясню. Например вот группа байтов:
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) и потом оттуда тянутся биты в нужном количестве.
Вот реализация:
Код AS3:
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:
Код AS3:
			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);
В случае Header`а xMin и yMin всегда по нулям, а xMax и yMax - это stageWidth и stageHeight "по умолчанию". Результаты они возвращают в твипсах, т.е. 1/20 пикселя.
Но это не проблема, ведь так?
Код AS3:
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
Это типа первый байт на дробную часть, второй байт на целую. Чтобы дробную сделать дробной нужно её поделить на 255, т.е. на максимально возможное значение в байте. Тогда он будет лежать между [0,1]. Он вылезает, если задавать FPS, например 30.2. В 99% случаев первый байт будет по нулям. Тем не менее:
Код AS3:
var frameRate:Number = _urlStream.readUnsignedByte() / 255 + _urlStream.readUnsignedByte();
log("Frame Rate: " + frameRate);
Обратите внимание, выделено 16 битов (UI16) и мы считали 2 раза по 8, т.е. те же 16!

Ну и закрывает это дело:
Цитата:
FrameCount UI16 Total number of frames in file
На этот раз число действительно "целое" - а не как в прошлый раз, поэтому просто считываем 16 бит:
Цитата:
var frameCount:int = _urlStream.readShort();
log("Total frames: " + frameCount);
На этом кончается Header флешки.
Есть такой любопытный момент:
Код:
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 i.o. вне форума
i.o.
 
Аватар для i.o.
Спасибо, очень интересно

ПС: встречаются опечатки вначале и в середине статьи.
Старый 08.09.2010 20:22 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Поправил, что нашел + исправил неточность про LITTLE_ENDIAN - оно справедливо не ко всей swf`ке. Спасибо, если найдёте ещё - ткните носом )
Или на ты перейдем?
Обновил(-а) Psycho Tiger 08.09.2010 в 20:38
Старый 08.09.2010 21:58 i.o. вне форума
i.o.
 
Аватар для i.o.
Psycho Tiger, если мне, то давай на ты )

Кстати про "любопытный момент" - может это из-за того, что ты swf разжимал?
Старый 08.09.2010 22:58 Котяра вне форума
Котяра
 
Аватар для Котяра
любопытный момент касается разного количества байтов в хедере и в реальности? Если бы не коммент i.o. не догадался бы)
Втыкал минут пять на лог)
Интресная статья. А источники можешь привести?
Откуда инфа бралась - ссылку на спеку swf, расшифровку итп.
ps: очепятко
Цитата:
Это означает, что 2 байт всегда W, второй байт всегда S. Делов то!
Старый 08.09.2010 23:44 alexcon314 вне форума
alexcon314
Цитата:
А источники можешь привести? Откуда инфа бралась - ссылку на спеку swf, расшифровку итп.
Psycho Tiger сам рулит. Это только начало. Приготовьтесь к настоящей драке.
ЗЫ. pardon за флейм.
Старый 09.09.2010 00:20 MrPoma вне форума
MrPoma
 
Аватар для MrPoma
С тех пор как прочитал спецификацию, не дает покоя этот RECT в заголовке. Ну зачем он там? Пару байт сэкономить?
Старый 09.09.2010 01:51 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
MrPoma, затем что формат создавался тогда, когда кроме диалапа ничего не было. к тому же за создание формата swf я готов пожать руку человеку, который его придумал. пожалуй наиболее грамотного кодирование сложно сейчас встретить.
Старый 09.09.2010 13:31 Котяра вне форума
Котяра
 
Аватар для Котяра
Цитата:
Psycho Tiger сам рулит. Это только начало. Приготовьтесь к настоящей драке.
То, что тигра молодец - я в курсе, но не с потолока же он инфу брал. Я не говорю что статья - копипаста или перевод, но ссылки на спеку swf, расшифровку итп, не помешают.
Я что-то подобное, про расшифоровку хедеров читал (правда на аглицком и намного короче)
Старый 09.09.2010 13:55 iNils вне форума
iNils
 
Аватар для iNils
Спецификация swf
Два года назад я начал переводить ее и бросил надо будет закончить.
Старый 09.09.2010 14:28 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Да, изучал по официально, что привёл iNils.
Ещё есть у alexref`а - её не смотрел в силу отсутствия времени, но говорят она лучше:
http://www.m2osw.com/swf_alexref.html

@Котяра: ага, спасибо, поправил.
Обновил(-а) Psycho Tiger 09.09.2010 в 14:55
Старый 09.09.2010 16:15 arkadattx вне форума
arkadattx
Psycho Tiger, отличная статья, как раз недавно интересовался. Надо будет уделить время более подробному "втыканию" в rect (на нем застопорился в последний раз)

iNils, а можно глянуть на полуфабрикат? Проще для восприятия. Спасибо.
Старый 09.09.2010 16:40 iNils вне форума
iNils
 
Аватар для iNils
Цитата:
iNils, а можно глянуть на полуфабрикат?
В ближайшем будущем не получится.
Старый 09.09.2010 22:46 dimarik вне форума
dimarik
 
Аватар для dimarik
Аха! Есть еще люди в русских селениях ) По-правде, рад что появилась такая интересная статья.

Однако! У форума наклевывается вторая жизнь. Гуру, поддержим?

Цитата:
за создание формата swf я готов пожать руку человеку, который его придумал. пожалуй наиболее грамотного кодирование сложно сейчас встретить.
Полностью согласен. Довольно ненавязчивый и простой способ доставить данные пользователю.
Обновил(-а) dimarik 09.09.2010 в 22:49
Старый 09.09.2010 23:42 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Мне кстати забавно, что среди комментариев к статье нет людей, которые просили меня рассказать хоть немного о байткоде, зато есть комментарии гуру, которые его знают вдоль и поперёк.

@dimarik: я буду очень рад если ты напишешь overview статью о байткоде в целом: я не люблю писать про общности, зато люблю о них читать =)
Старый 10.09.2010 04:37 Hidest вне форума
Hidest
 
Аватар для Hidest
Нет людей, которые просили, но есть люди, которые именно с удовольствием читают подобные статьи! Чем их больше, тем лучше, пусть и не всем, но определенно некоторым интересующимся - точно! А внимание гуру только показывает правильное направление мысли и своих помыслов!
Старый 10.09.2010 12:13 etc вне форума
etc
 
Аватар для etc
swf-ку мог бы разжать обычным uncompress().
Старый 11.09.2010 13:48 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
etc, разве uncompress будет правильно работать в то время, когда флешка ещё не полностью загружена?

@Hidest: ну некоторые мои коллеги прямо говорили - "Расскажи о байткоде!"
Старый 12.09.2010 04:03 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
ну тогда можно взять loaderInfo.bytes. там уже всё расжатое лежит
Обновил(-а) BlooDHounD 12.09.2010 в 16:34
Старый 12.09.2010 12:04 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Блин. Я в статье гружу это дело через URLStream, а не Loader и соответственно у меня нету loaderInfo. Загрузив первые 50 байт я не думаю что можно будет спокойно так вызвав uncompress получить валидную FWS...
Старый 12.09.2010 16:33 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
Psycho Tiger, уважаемый. прочтите ещё рас моё предыдущие сообщение.
Старый 12.09.2010 16:57 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Я умею брать loaderInfo только у DisplayObject`а. Преобразовывать байты в него я умею только через Loader#loadBytes. Вывод: либо грузить это дело через Loader сразу, либо отдавать через loadBytes. В примере я разбирал вариант с URLStream. SWF`ку начинаем парсить до того, как она полностью загружена, т.е. на loadBytes её не отдашь.
Что я упустил?
Старый 12.09.2010 22:44 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
Psycho Tiger, то что можно грузить через load и делать всё без презервативов.
Старый 13.09.2010 01:20 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Об этом я тоже писал, что ради прикола притащим практическое применение за уши и будем грузить URLStream`ом =)
Статья не о загрузке данных, а о парсе, всё же.
Старый 13.09.2010 02:38 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
так тебе и говорят что парсить удобнее через loaderInfo.bytes так как в него уже расжатые данные поступают при загрузке.
Старый 13.09.2010 10:33 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Блин.
Смотри: loaderInfo я могу выдернуть у content`а, который станет доступен только по окончанию загрузки, а я хочу начать парсинг после первых 50 загруженных байт (ну приблуда у меня такая, хочется показать совсем неопытному читателю, что нам совсем не нужно даже флешку загружать - хоть обрубай поток после этого). Читать данные пока загрузка не окончена позволяет только URLStream (ну, сокет не в счет), поэтому выбор и пал на него.
Старый 13.09.2010 13:43 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
Psycho Tiger, приехали:
1. а как у Loader ты отслеживаешь прогресс загрузки?
2. и как использование Loader помешает тебе отрубать поток?
3. что кроме религии тебе мешает начать парсинг после первых 50 загруженных байт используя Loader?
4. неопытный читатель не поймёт почему ты используешь URLStream для загрузки swf.
5. кроме URLStream и сокета читать данные позволяет Loader.

p.s.: я устал тебе твердить то, что в хелпе пишут чёрным по белому. твоя самоуверенность и голословные фразы ничем не подтверждены. ты хоть из малейшего уважения к болиее опытным собеседникам в следующий раз поглядывай в хелп. на сим я удаляюсь.
Старый 13.09.2010 14:21 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
_loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgress);

Про то что Loader грузит потоком - не знал, честно, спасибо. Тут дело в том, что я не так тебя понял: я думал ты хотел чтобы я взял loaderInfo который у DisplayObject`а существует (то есть у root`а), а не contentLoaderInfo.
Спасибо что поправил.
Старый 13.09.2010 15:07 BlooDHounD вне форума
BlooDHounD
 
Аватар для BlooDHounD
Psycho Tiger, ну ваще-то он не спотолка у рута берётся. он к нему от loader'а падает, которым его загрузили.
Старый 13.09.2010 15:40 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Да, сейчас вспомнил что читал об этом где то. Надо знания свои углубить, а то многое валяется на поверхности.
Старый 13.09.2010 19:00 i.o. вне форума
i.o.
 
Аватар для i.o.
Цитата:
Loader грузит потоком
а я вот не совсем понял как этот поток прервать.. Просто unload() / unloadAndStop()?
Старый 13.09.2010 19:16 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
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 i.o. вне форума
i.o.
 
Аватар для i.o.
Вот ведь, искал и не заметил. Спасибо)
Старый 13.09.2010 23:03 silin вне форума
silin
 
Аватар для silin
мимо дискуссии про загрузку, оно важно, канеш, но как соус
респект Тигре за основное блюдо: это, имо, серьезная работа (надо же все перелопатить, докопаться, оформить и выложить) и это забота о нас (не надо теперь лопатить и копаться - пришел и все взял, крсота)
короче, спасибо: такие вещи очень нужны
вещи тут не только инструменты, что ты даешь, но и принцип: чем больше даешь, тем больше твое
извиняюсь за многословность, но ведь 256й день же..
Обновил(-а) silin 13.09.2010 в 23:20
Старый 13.09.2010 23:09 Psycho Tiger вне форума
Psycho Tiger
 
Аватар для Psycho Tiger
Приятно очень
Но это крупица по сравнению с тем, что выкладываешь ты. Короче тебе двойной спасибо-респект)
Старый 14.09.2010 09:27 i.o. вне форума
i.o.
 
Аватар для i.o.
да, silin - молодец! С праздничком )
 

 


Часовой пояс GMT +4, время: 01:19.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.