[转] [Flash/Flex] <What can you do with bytes ?> 第二章 第十节:解析二进制数据

本文介绍如何使用Adobe Flash Professional CS5.5创建的SWF文件进行深入解析,包括读取文件头信息、处理压缩数据、解析帧率及帧数等关键信息,并通过自定义函数解析特殊数据结构。此外还介绍了如何利用AS3SWF库进行更高级别的SWF文件解析。
[url]http://bbs.9ria.com/thread-81262-1-1.html[/url]


资讯类型: 翻译
来源页面: http://www.bytearray.org/wp-content/projects/wcydwb/Chapter%202%20-%20Everyday%20bytes.pdf
资讯原标题: <What can you do with bytes ?> -Parsing binary data
资讯原作者: Thibault Imbert
翻译词数: 4775 词
我的评论:
对这篇文你有啥看法,跟贴说说吧!欢迎口水和板砖,哈哈。欢迎大家和我们一同分享更多资讯。
本帖最后由 sun11086 于 2011-4-20 12:53 编辑


解析二进制数据

解析二级制数据也是非常有用的.在接下来的例子中,我们将会解析一个swf文件并从中提取出我们需要的信息.自从FP9中引入了as3,ApplicationDomain类允许开发者在运行时使用,隔离和检索类定义.

swf例子
限制就是我们不能获取那些我们不知道其名字的类.例如,你知道一个类定义的名字,你只需要使用ApplicationDomain.getDefinition就可以了.现在我们需要检索swf库中的所有链接类,遍历他们,检索每个定义,并且使用他们.我们所寻找的数据其实就是在swf中的名为"SYMBOL"的类标签.

为了实现这一功能,我们需要去了解swf的结构,和往常一样,我们首先要做的事情就是学习其文件格式说明文档.swf说明文档链接:http://www.adobe.com/devnet/swf.html

让我们来看一看swf的结构是什么样的.对于开始阶段最重要的东西就是文件头.它包含了很多信息.
下面我们来看一下官方文档对swf的说明.
SWF文件头
字段 类型 说明
标志 UI8 标识字节:如果是“F”表示未压缩,如果是“C”表示压缩(SWF6之后才有)
标志 UI8 始终为"W"
标志 UI8 始终为"S"
版本号 UI8 一个字节的版本号(如0x06表示SWF6)
文件长度 UI32 完成文件的长度有多少字节
显示尺寸 RECT结构 以twips为单位的显示尺寸(20twips=1px)
帧速 UI16 每秒的帧速,通过一个8.8小数方式显示。(整数和小数各占8位)
帧总数 UI16 文件中帧的总数。
复制代码
正如你所看到的,types由 无符号整形(UI8) 或无符号整形(UI6)表示,并且可以在comment列中看到一些说明.fp不会在as这一级别来处理这些数据,这就意味着我们需要使用uint(32 bit)来存储一个UI8(8位的uint),虽然这一行为会浪费内存,但是目前为止,在as3中我们还没有一个更有效的基元数据来处理这一数据.
让我们来做一个简单的例子,现在你已经熟悉了所有和ByteArray相关的函数.我们首先要做的就是抓取一些当前swf中的字节.
我们只需要调用swf显示列表中,任意可视显示对象中的LoaderInfo对象的bytes属性就可以实现这一目的.如下例所示:
import flash.display.DisplayObject;
import flash.display.Sprite;

var s:DisplayObject = new Sprite();

addChild ( s );

// outputs : [object ApplicationDomain]
trace ( s.loaderInfo.applicationDomain );

// outputs : FWSb
trace ( s.loaderInfo.bytes );
复制代码
我们可在输出面板看到类似于FWS的字符串.
现在我们引用对应的bytes然后尝试检索出第一个字符串和swf版本,我只需要通过ByteArray.readUTFBytes()方法来检索前三个字符即可.
import flash.utils.ByteArray;

// outputs : [object ApplicationDomain]
trace ( this.loaderInfo.applicationDomain ); What can you do with bytes ? – Chapter 2 – Everyday bytes

// grab the current SWF bytes
var swfBytes:ByteArray = this.loaderInfo.bytes;

// outputs : FWS
trace ( swfBytes.readUTFBytes(3) );
复制代码
事实上我们读取了3个字节,每个字节代表一个字符 .最终我们得到了F,W,S.非常简单吧?
就像swf文档中所示,我们只需要继续读bytes就可以获取到SWF的版本,让我们来继续读取后面的字节:
import flash.utils.ByteArray;

// outputs : [object ApplicationDomain]
trace ( this.loaderInfo.applicationDomain );

// grab the current SWF bytes
var swfBytes:ByteArray = this.loaderInfo.bytes;

// outputs : FWS
trace ( swfBytes.readUTFBytes(3) );

// outputs : 11
trace ( swfBytes.readUnsignedByte() );
复制代码
我们发现swf版本信息已经被编译进去了.我们看到swf的版本是11(fp10.2的版本号).然后我们获取swf的大小.你可以想象的到,当你想读取剩下的文件或从一个字节块中解析多个swf时是非常有用的.

正如规范说明中提及的一样:后面的四个字节表示了文件大小用一个无符号的32位整数表示.从这里开始一切变得微妙并且计算机架构也开始来凑热闹.接下来的4个字节是0x4F000000,这就意味了文件的长度1325400064字节,这是一个多么大的数字啊.因此我们并非将所有的byte一次性的都读进内存.

事实上,也没有那么困难,应用本书第一章中的方法,我们只需要记住字节顺序并根据它来操作即可.所以我们只需要在 little-endian 模式下,继续读取接下来的"4个字节"(UI32)即可.
注意在我们读取完文件长度后,我们又切换回了 big endian.
import flash.utils.ByteArray;

// outputs : [object ApplicationDomain]
trace ( this.loaderInfo.applicationDomain );

// grab the current SWF bytes
var swfBytes:ByteArray = this.loaderInfo.bytes;

// outputs : FWS
trace ( swfBytes.readUTFBytes(3) );

// outputs : 11
trace ( swfBytes.readUnsignedByte() );

swfBytes.endian = Endian.LITTLE_ENDIAN;
// outputs : 2372
trace ( swfBytes.readUnsignedInt() );

swfBytes.endian = Endian.BIG_ENDIAN;
复制代码
现在我们来看一下规范说明中又对swf头文件做了哪些说明:
在整个swf文件的前8个字节(也就是文件长度域)后是CWS,它代表着当前swf使用的压缩模式.如果头文件(header)的这个标识的第一个字符为C(0x43),这就意味着在我们使用数据前需要对其解压缩.不过好消息是ByteArray类本身就有原生的压缩和解压缩的函数 compress和 uncompress

我们把下列的代码添加到以前代码后,这样如果swf是经过压缩的,在使用他们时我们就会先进性解压缩.
import flash.utils.ByteArray;

// outputs : [object ApplicationDomain]
trace ( this.loaderInfo.applicationDomain );

// grab the current SWF bytes
var swfBytes:ByteArray = this.loaderInfo.bytes;

// stores the compressed state (C or F)
var compressed:uint = swfBytes.readUnsignedByte();

// skip WS
swfBytes.position += 2;

// outputs : 11
trace ( swfBytes.readUnsignedByte() );

swfBytes.endian = Endian.LITTLE_ENDIAN;

// outputs : 2372
trace ( swfBytes.readUnsignedInt() );

swfBytes.endian = Endian.BIG_ENDIAN;

// creates an empty ByteArray to store the uncompressed SWF bytes
var swfBuffer:ByteArray = new ByteArray();

// copies the bytes
swfBytes.readBytes ( swfBuffer );

const COMPRESSED:uint = 0x43;

// if the SWF we are working on is compressed, we uncompress the swfBuffer
if ( compressed == COMPRESSED )
swfBuffer.uncompress();
复制代码
接下来是RECT标识块.让我们来仔细的看一下RECT.规范说明中时这样说明的(译者注:Twip 中文译为"缇",是一种和屏幕无关的长度单位,目的是为了让应用程序元素输出到不同设备时都能保持一致的计算方式。1px = 20twips):
======
属性 类型 说明
Nbits UB[5] rect结构的各个属性值的位数
Xmin SB[NBits] x轴方向的最小值
Xmax SB[NBits] x轴方向的最大值
Ymin SB[NBits] y轴方向的最小值
Ymax SB[NBits] y轴方向的最大值
复制代码
======
现在我们来看一下RECT 在2进制下的样子:
在这个矩形区域中一共有5个标识:Nbits, Xmin, Xmax, Ymin, Ymax. 无符号的Nbits域占用了前5个字节,它用来表示后4个标识域的长度:
基于这个原理,我们可以推算出每个用来描述舞台尺寸的属性都需要15字节的空间.
000000000000000 ‹ 0 = Xmin
010101011111000 ‹ 11000 = Xmax
000000000000000 ‹ 0 = Ymin
001111101000000 ‹ 8000 = Ymax
复制代码
我们使用下列的代码来解析RECT,帧频和帧数:
import flash.utils.ByteArray;

// outputs : [object ApplicationDomain]
trace ( this.loaderInfo.applicationDomain );

// grab the current SWF bytes
var swfBytes:ByteArray = this.loaderInfo.bytes; What can you do with bytes ? – Chapter 2 – Everyday bytes

// stores the compressed state (C or F)
var compressed:uint = swfBytes.readUnsignedByte();

// skip WS
swfBytes.position += 2;

// outputs : 11
trace ( swfBytes.readUnsignedByte() );

swfBytes.endian = Endian.LITTLE_ENDIAN;

// outputs : 2372
trace ( swfBytes.readUnsignedInt() );

swfBytes.endian = Endian.BIG_ENDIAN;

// creates an empty ByteArray to store the uncompressed SWF bytes
var swfBuffer:ByteArray = new ByteArray();

// copies the bytes
swfBytes.readBytes ( swfBuffer );

const COMPRESSED:uint = 0x43;

// if the SWF we are working on is compressed, we uncompress the swfBuffer
if ( compressed == COMPRESSED )
swfBuffer.uncompress();

var firstBRect:uint = swfBuffer.readUnsignedByte();

var size:uint = firstBRect >> 3;
var offset:uint = (size-3);

var threeBits:uint = firstBRect & 0x7;

var buffer:uint = 0;
var pointer:uint = 0;
var source:uint = swfBuffer.readUnsignedByte();

var xMin:uint = (readBits(offset) | (threeBits << offset)) / 20;
var yMin:uint = readBits(size) / 20;
var wMin:uint = readBits(size) / 20;
var hMin:uint = readBits(size) / 20;

// outputs : 0 550 0 400
trace ( xMin, yMin, wMin, hMin );

var frameRate:uint = swfBuffer.readShort() & 0xFF;

var numFrames:uint = swfBuffer.readShort();

var frameCount:uint = (numFrames >> 8) & 0xFF | ((numFrames & 0xFF) << 8);

// outputs : 24 1
trace ( frameRate, frameCount );

swfBuffer.endian = Endian.LITTLE_ENDIAN;

function readBits(numBits:uint):uint
{
buffer = 0;
var currentMask:uint;
var bitState:uint;
// for the number of bits to read
for ( var i:uint = 0; i<numBits; i++)
{
// we create a mask which goes from left to right What can you do with bytes ? – Chapter 2 – Everyday bytes
currentMask = (1 << 7) >> pointer++;
// we store each bit state resulting from the mask operation
bitState = uint((source & currentMask) != 0);
// we store that bit state by recreating a value
buffer |= bitState << ((numBits - 1) - i);
// when we are running out of byte we read a new one and reset the pointer for the mask
if ( pointer == 8 )
{
source = swfBuffer.readUnsignedByte();
pointer = 0;
}
}
return buffer;
}
复制代码
也许你会开始疑虑,什么东西,一大堆代码,但事实上只要我们一步步的了解,他们并不难.在读取swf版本后,我们需要在little endian模式下读取文件大小,我们可以手动来解析但在这里我们使用endian属性来证明其价值.

之后我们仅仅是RECT块,通过向自定义readBits函数传递读取字节量来读取指定长度的参数..正如在上一章中看到的,flash player 没有提供基元的函数来读取特定长度字节的函数,为此我们需要编写一个自定义函数.

正如前面表中所示,RECT的前5个字节定义了每个属性的长度(xMin, xMax, yMin 和 yMax).所以我们通过简单的右移操作来读取这5个字节.

在本例中因为我们只需要前五个字节,所以我们将后面的3个字节直接忽略.结果如下:
var firstBRect:uint = SWFBytes.readUnsignedByte();

var size:uint = firstBRect >> 3;
var offset:uint = (size-3);

var threeBits:uint = firstBRect & 0x7;
复制代码
然后我们来读取那4个属性.请注意我们是如何从12字节中读取数据并对之前存储的3字节数据做位运算的(译者注: 1px = 20twips)
var source:uint = SWFBytes.readUnsignedByte();

var xMin:uint = (readBits(offset) | (threeBits << offset)) / 20;
var yMin:uint = readBits(size) / 20;
var wMin:uint = readBits(size) / 20;
var hMin:uint = readBits(size) / 20;

// outputs : 0 550 0 400
trace ( xMin, yMin, wMin, hMin );
复制代码
然后我们解析出帧频和帧数.对于帧数,我们采用手动的方法将其从big endian 转换成 little endian:
var frameRate:uint = SWFBytes.readShort() & 0xFF;

var numFrames:uint = SWFBytes.readShort();

var frameCount:uint = (numFrames >> 8) & 0xFF | ((numFrames & 0xFF) << 8);

// outputs : 24 1
trace ( frameRate, frameCount );
复制代码
现在我们已经处理完了header.让我们看看接下来是什么.下面是规范文档对接下来内容的描述:
文件头之后是一系列的标签数据块。所有的标签(Tag)都共用一种标准格式,所以当程序在解析SWF文件的时候可以跳过不认识的标签块。每个标签块里面都有自己的偏移量属性,这个属性仅可以在当前块中使用,不能在其他块中使用。这种方式可以让我们在使用工具处理SWF文件的时候,移除,插入或编辑标签.

在swf规范中我们可以找到对应的结构图:

接下来我们定义SWFTag对象来更好的处理标签:
package
{
public class SWFTag
{
private var _tag:uint;
private var _offset:uint;
private var _endOffset:uint;

public function SWFTag ( tag:uint, offset:uint )
{
_tag = tag;
_offset = offset;
}

public function get tag():uint
{
return _tag;
}

public function set tag(tag:uint):void
{
}

public function get offset():uint
{
return _offset;
}
public function set offset(offset:uint):void
{
_offset = offset;
}

public function get endOffset():uint
{
return _endOffset;
}

public function set endOffset(endOffset:uint):void
{
_endOffset = endOffset;
}
}
}
复制代码
接下来是最终的代码:
import flash.utils.ByteArray;

// outputs : [object ApplicationDomain]
trace ( this.loaderInfo.applicationDomain );

// grab the current SWF bytes
var swfBytes:ByteArray = this.loaderInfo.bytes;

// stores the compressed state (C or F)
var compressed:uint = swfBytes.readUnsignedByte();

// skip WS
swfBytes.position += 2;

// outputs : 11
trace ( swfBytes.readUnsignedByte() );

swfBytes.endian = Endian.LITTLE_ENDIAN;

// outputs : 2372
trace ( swfBytes.readUnsignedInt() );

swfBytes.endian = Endian.BIG_ENDIAN;

// creates an empty ByteArray to store the uncompressed SWF bytes
var swfBuffer:ByteArray = new ByteArray();

// copies the bytes
swfBytes.readBytes ( swfBuffer );

const COMPRESSED:uint = 0x43;

// if the SWF we are working on is compressed, we uncompress the swfBuffer
if ( compressed == COMPRESSED )
swfBuffer.uncompress();

var firstBRect:uint = swfBuffer.readUnsignedByte();

var size:uint = firstBRect >> 3;
var offset:uint = (size-3);

var threeBits:uint = firstBRect & 0x7;

var buffer:uint = 0;
var pointer:uint = 0;
var source:uint = swfBuffer.readUnsignedByte();

var xMin:uint = (readBits(offset) | (threeBits << offset)) / 20; What can you do with bytes ? – Chapter 2 – Everyday bytes
var yMin:uint = readBits(size) / 20;
var wMin:uint = readBits(size) / 20;
var hMin:uint = readBits(size) / 20;

// outputs : 0 550 0 400
trace ( xMin, yMin, wMin, hMin );

var frameRate:uint = swfBuffer.readShort() & 0xFF;

var numFrames:uint = swfBuffer.readShort();

var frameCount:uint = (numFrames >> 8) & 0xFF | ((numFrames & 0xFF) << 8);

// outputs : 24 1
trace ( frameRate, frameCount );

swfBuffer.endian = Endian.LITTLE_ENDIAN;

var parsedTags:Vector.<SWFTag> = browseTables();

function readBits(numBits:uint):uint
{
buffer = 0;
var currentMask:uint;
var bitState:uint;
// for the number of bits to read
for ( var i:uint = 0; i<numBits; i++)
{
// we create a mask which goes from left to right
currentMask = (1 << 7) >> pointer++;
// we store each bit state resulting from the mask operation
bitState = uint((source & currentMask) != 0);
// we store that bit state by recreating a value
buffer |= bitState << ((numBits - 1) - i);
// when we are running out of byte we read a new one and reset the pointer for the mask
if ( pointer == 8 )
{
source = swfBuffer.readUnsignedByte();
pointer = 0;
}
}
return buffer;
}

function browseTables():Vector.<SWFTag>
{
var currentTag:int;
var step:int;
var dictionary:Vector.<SWFTag> = new Vector.<SWFTag>();
var infos:SWFTag;

while ( (currentTag = ((swfBuffer.readShort() >> 6) & 0x3FF)) != 0 )
{
infos = new SWFTag(currentTag, swfBuffer.position);

swfBuffer.position -= 2;
step = swfBuffer.readShort() & 0x3F;

trace ( currentTag );

if ( step < 0x3F )
{
swfBuffer.position += step;

} else
{
step = swfBuffer.readUnsignedInt();
infos.offset = swfBuffer.position; What can you do with bytes ? – Chapter 2 – Everyday bytes
swfBuffer.position += step;
}

infos.endOffset = swfBuffer.position;
dictionary.push ( infos );

}

return dictionary;
}

var linkedSymbols:Vector.<String> = new Vector.<String>();

var symbolClassTag:uint = 76;

for each ( var tag:SWFTag in parsedTags)
{
if ( tag.tag == symbolClassTag )
{
var tagOffset:uint = tag.offset;
swfBuffer.position = tagOffset;
var count:uint = swfBuffer.readShort();

for (var i:uint = 0; i< count; i++)
{
swfBuffer.readUnsignedShort();

var char:uint = swfBuffer.readByte();
var className:String = new String();

while (char != 0)
{
className += String.fromCharCode(char);
char = swfBuffer.readByte();
}
linkedSymbols.push ( className );
}
}
}

// outputs : Symbol1,Symbol1copy,parse_fla.MainTimeline
trace ( linkedSymbols );
复制代码
让我们输出解析出的标签,我们为currentTag添加了trace输出:
function browseTables():Vector.<SWFTag>
{
var currentTag:int;
var step:int;
var dictionary:Vector.<SWFTag> = new Vector.<SWFTag>();
var infos:SWFTag;

while ( (currentTag = ((SWFBytes.readShort() >> 6) & 0x3FF)) != 0 )
{
infos = new SWFTag(currentTag, SWFBytes.position);

SWFBytes.position -= 2;
step = SWFBytes.readShort() & 0x3F;

trace ( currentTag );

if ( step < 0x3F )
{
SWFBytes.position += step;

} else What can you do with bytes ? – Chapter 2 – Everyday bytes
{
step = SWFBytes.readUnsignedInt();
infos.offset = SWFBytes.position;
SWFBytes.position += step;
}

infos.endOffset = SWFBytes.position;
dictionary.push ( infos );

}

return dictionary;
}
复制代码
我们得到如下的输出结果:
69
77
9
86
82
76
1
复制代码
看起来不错吧,通过swf规范文档我们可知道每个标签的代表的意思:
• 1 ShowFrame
• 9 SetBackgroundColor
• 69 FileAttributes
• 76 SymbolClass
• 77 Metadata
• 82 DoABC
• 86 DefineSceneAndFrameLabelData
复制代码
现在我们有了标签的索引表,通过简单的循环,我们可以遍历并且知道每个标签的在读取出的swf中的位置,我们创建了一个基本的索引:
for each ( var tag:SWFTag in parsedTags )
{
/* outputs :
69 15 19
77 25 1311
9 1313 1316
86 1322 1333
82 1441 4358
76 4364 4415
1 4417 4417
*/
trace ( tag.tag, tag.offset, tag.endOffset );
}
复制代码
现在我们有了一个充满SWFTag对象的向量(Vector),它被用来描述每个标签的开始和结束文职,现在通过这种方法我们可以轻松的遍历这个向量并且可以直接跳至我们想去的地方.在下面的代码中,我们通过遍历Vector.<SWFTag>来寻找我们需要的标签(SYMBOLCLASS):
var symbolClassTag:uint = 76;

for each ( var tag:SWFTag in parsedTags ) What can you do with bytes ? – Chapter 2 – Everyday bytes
{
if ( tag.tag == symbolClassTag )
{
var tagOffset:uint = tag.offset;
SWFBytes.position = tagOffset;
var count:uint = SWFBytes.readShort();

for (var i:uint = 0; i< count; i++)
{
SWFBytes.readUnsignedShort();

var char:uint = SWFBytes.readByte();
var className:String = new String();

while (char != 0)
{
className += String.fromCharCode(char);
char = SWFBytes.readByte();
}
/* outputs :
Symbol1
Symbol1copy
parse_fla.MainTimeline
*/
trace ( className );
}
}
}
复制代码
我们创建了一个Vector.<String>来存储类名:
var linkedSymbols:Vector.<String> = new Vector.<String>();

const symbolClassTag:uint = 76;

for each ( var tag:SWFTag in parsedTags)
{
if ( tag.tag == symbolClassTag )
{
var tagOffset:uint = tag.offset;
SWFBytes.position = tagOffset;
var count:uint = SWFBytes.readShort();

for (var i:uint = 0; i< count; i++)
{
SWFBytes.readUnsignedShort();

var char:uint = SWFBytes.readByte();
var className:String = new String();

while (char != 0)
{
className += String.fromCharCode(char);
char = SWFBytes.readByte();
}
linkedSymbols.push ( className );
}
}
}

// outputs : Symbol1,Symbol1copy,parse_fla.MainTimeline
trace ( linkedSymbols );
复制代码
现在我们可以通过这些类名来提取当前swf中所有的链接类了.当我们需要实例化RLS中所有的类时,这一功能将十分的有用.
SWFExplorer是我前不久写的一个类库,通过下面的方法使用:
var explorer:SWFExplorer = new SWFExplorer();

explorer.load ( new URLRequest ( "library.swf" ));

explorer.addEventListener ( SWFExplorerEvent.COMPLETE, assetsReady );

function assetsReady (e:SWFExplorerEvent):void
{

// outputs : org.groove.Funk,org.funk.Soul,org.groove.Jazz
trace( e.definitions );

// outputs : org.groove.Funk,org.funk.Soul,org.groove.Jazz
trace( e.target.getDefinitions() );

// ouputs : 3
trace( e.target.ge
复制代码
高级SWF解析
如果你想进一步的解析SWF文件更多的细节,你可以使用一个超棒的类库 AS3SWF,这个类库是由 Claus Wahlers 开发并且支持所有的swf标签.让我们来做一个简单的测试,首先我们下载AS3SWF:https://github.com/claus/as3swf
将swf链接好,创建一个空白的fla文档,在主帧上添加如下代码:
import com.codeazur.as3swf.SWF;

var swf:SWF = new SWF(root.loaderInfo.bytes);

trace(swf);
复制代码
运行后我们得到如下的高级描述:
[SWF]
Header:
Version: 11
FileLength: 197456
FileLengthCompressed: 197456
FrameSize: (550,400)
FrameRate: 24
FrameCount: 1
Tags:
[69:FileAttributes] AS3: true, HasMetadata: true, UseDirectBlit: false, UseGPU: false, UseNetwork: false
[77:Metadata] <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<xmp:CreatorTool>Adobe Flash Professional CS5.5</xmp:CreatorTool>
<xmp:CreateDate>2011-03-11T00:17:09-08:00</xmp:CreateDate>
<xmp:MetadataDate>2011-03-11T00:17:58-08:00</xmp:MetadataDate>
<xmp:ModifyDate>2011-03-11T00:17:58-08:00</xmp:ModifyDate> What can you do with bytes ? – Chapter 2 – Everyday bytes
</rdf:Description>
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:format>application/x-shockwave-flash</dc:format>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#">
<xmpMM:InstanceID>xmp.iid:CA4C9A390A20681188C6B9B04E99E63C</xmpMM:InstanceID>
<xmpMM:DocumentID>xmp.did:CA4C9A390A20681188C6B9B04E99E63C</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>xmp.did:C94C9A390A20681188C6B9B04E99E63C</xmpMM:OriginalDocumentID>
<xmpMM:DerivedFrom rdf:parseType="Resource">
<stRef:instanceID>xmp.iid:C94C9A390A20681188C6B9B04E99E63C</stRef:instanceID>
<stRef:documentID>xmp.did:C94C9A390A20681188C6B9B04E99E63C</stRef:documentID>
<stRef:originalDocumentID>xmp.did:C94C9A390A20681188C6B9B04E99E63C</stRef:originalDocumentID>
</xmpMM:DerivedFrom>
</rdf:Description>
</rdf:RDF>
[09:SetBackgroundColor] Color: #FFFFFF
[86:DefineSceneAndFrameLabelData]
Scenes:
[0] Frame: 0, Name: Scene 1
[82:DoABC] Lazy: true, Length: 196066
[76:SymbolClass]
Symbols:
[0] TagID: 0, Name: as3swc_fla.MainTimeline
[01:ShowFrame]
[00:End]
Scenes:
Name: Scene 1, Frame: 0
Frames:
[0] Start: 0, Length: 7
复制代码
现在我们在刚刚的fla中添加一个简单的shape,并再次运行:
[SWF]
Header:
Version: 11
FileLength: 197527
FileLengthCompressed: 197527
FrameSize: (550,400)
FrameRate: 24
FrameCount: 1
Tags:
[69:FileAttributes] AS3: true, HasMetadata: true, UseDirectBlit: false, UseGPU: false, UseNetwork: false
[77:Metadata] <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
<xmp:CreatorTool>Adobe Flash Professional CS5.5</xmp:CreatorTool>
<xmp:CreateDate>2011-03-11T00:17:09-08:00</xmp:CreateDate>
<xmp:MetadataDate>2011-03-11T00:20:42-08:00</xmp:MetadataDate>
<xmp:ModifyDate>2011-03-11T00:20:42-08:00</xmp:ModifyDate>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:format>application/x-shockwave-flash</dc:format>
</rdf:Description>
<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#">
<xmpMM:InstanceID>xmp.iid:CB4C9A390A20681188C6B9B04E99E63C</xmpMM:InstanceID>
<xmpMM:DocumentID>xmp.did:CB4C9A390A20681188C6B9B04E99E63C</xmpMM:DocumentID>
<xmpMM:OriginalDocumentID>xmp.did:C94C9A390A20681188C6B9B04E99E63C</xmpMM:OriginalDocumentID>
<xmpMM:DerivedFrom rdf:parseType="Resource">
<stRef:instanceID>xmp.iid:C94C9A390A20681188C6B9B04E99E63C</stRef:instanceID>
<stRef:documentID>xmp.did:C94C9A390A20681188C6B9B04E99E63C</stRef:documentID>
<stRef:originalDocumentID>xmp.did:C94C9A390A20681188C6B9B04E99E63C</stRef:originalDocumentID>
</xmpMM:DerivedFrom>
</rdf:Description>
</rdf:RDF>
[09:SetBackgroundColor] Color: #FFFFFF
[86:DefineSceneAndFrameLabelData]
Scenes: What can you do with bytes ? – Chapter 2 – Everyday bytes
[0] Frame: 0, Name: Scene 1
[83:DefineShape4] ID: 1, ShapeBounds: (1250,7510,1090,6089), EdgeBounds: (1260,7500,1100,6079)
FillStyles:
[1] [SWFFillStyle] Type: 0 (solid), Color: ff000000
[2] [SWFFillStyle] Type: 0 (solid), Color: ffcc9933
LineStyles:
[1] [SWFLineStyle2] Width: 20, StartCaps: round, EndCaps: round, Joint: round, Color: ff000000
ShapeRecords:
[SWFShapeRecordStyleChange] MoveTo: 7500,6079, FillStyle1: 2, LineStyle: 1
[SWFShapeRecordStraightEdge] Horizontal: -6240
[SWFShapeRecordStraightEdge] Vertical: -4979
[SWFShapeRecordStraightEdge] Horizontal: 6240
[SWFShapeRecordStraightEdge] Vertical: 4979
[SWFShapeRecordEnd]
[26:PlaceObject2] Depth: 1, CharacterID: 1, Matrix: (1,1,0,0,0,0)
[82:DoABC] Lazy: true, Length: 196066
[76:SymbolClass]
Symbols:
[0] TagID: 0, Name: as3swc_fla.MainTimeline
[01:ShowFrame]
[00:End]
Scenes:
Name: Scene 1, Frame: 0
Frames:
[0] Start: 0, Length: 9
Defined CharacterIDs: 1
Depth: 1, CharacterId: 1, PlacedAt: 5, IsKeyframe
复制代码
漂亮!你可以清楚看到从对简单对象的复制到声音生成和复杂解析,ByteArray都可以无任何限制的解决,而且还有很多可以去探索的事情!
import * as React from 'react'; import { ActivityIndicator, Image, ScrollView, Text, TextInput, View } from 'react-native'; import { rotateImage } from '../modules/imaging'; import { toBase64Image } from '../utils/base64'; import { Agent } from '../agent/Agent'; import { InvalidateSync } from '../utils/invalidateSync'; import { textToSpeech } from '../modules/openai'; function usePhotos(device: BluetoothRemoteGATTServer) { // Subscribe to device const [photos, setPhotos] = React.useState<Uint8Array[]>([]); const [subscribed, setSubscribed] = React.useState<boolean>(false); React.useEffect(() => { (async () => { let previousChunk = -1; let buffer: Uint8Array = new Uint8Array(0); function onChunk(id: number | null, data: Uint8Array) { // Resolve if packet is the first one if (previousChunk === -1) { if (id === null) { return; } else if (id === 0) { previousChunk = 0; buffer = new Uint8Array(0); } else { return; } } else { if (id === null) { console.log('Photo received', buffer); rotateImage(buffer, '270').then((rotated) => { console.log('Rotated photo', rotated); setPhotos((p) => [...p, rotated]); }); previousChunk = -1; return; } else { if (id !== previousChunk + 1) { previousChunk = -1; console.error('Invalid chunk', id, previousChunk); return; } previousChunk = id; } } // Append data buffer = new Uint8Array([...buffer, ...data]); } // Subscribe for photo updates const service = await device.getPrimaryService('19B10000-E8F2-537E-4F6C-D104768A1214'.toLowerCase()); const photoCharacteristic = await service.getCharacteristic('19b10005-e8f2-537e-4f6c-d104768a1214'); await photoCharacteristic.startNotifications(); setSubscribed(true); photoCharacteristic.addEventListener('characteristicvaluechanged', (e) => { let value = (e.target as BluetoothRemoteGATTCharacteristic).value!; let array = new Uint8Array(value.buffer); if (array[0] == 0xff && array[1] == 0xff) { onChunk(null, new Uint8Array()); } else { let packetId = array[0] + (array[1] << 8); let packet = array.slice(2); onChunk(packetId, packet); } }); // Start automatic photo capture every 5s const photoControlCharacteristic = await service.getCharacteristic('19b10006-e8f2-537e-4f6c-d104768a1214'); await photoControlCharacteristic.writeValue(new Uint8Array([0x05])); })(); }, []); return [subscribed, photos] as const; } export const DeviceView = React.memo((props: { device: BluetoothRemoteGATTServer }) => { const [subscribed, photos] = usePhotos(props.device); const agent = React.useMemo(() => new Agent(), []); const agentState = agent.use(); // Background processing agent const processedPhotos = React.useRef<Uint8Array[]>([]); const sync = React.useMemo(() => { let processed = 0; return new InvalidateSync(async () => { if (processedPhotos.current.length > processed) { let unprocessed = processedPhotos.current.slice(processed); processed = processedPhotos.current.length; await agent.addPhoto(unprocessed); } }); }, []); React.useEffect(() => { processedPhotos.current = photos; sync.invalidate(); }, [photos]); React.useEffect(() => { if (agentState.answer) { textToSpeech(agentState.answer) } }, [agentState.answer]) return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}> <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}> {photos.map((photo, index) => ( <Image key={index} style={{ width: 100, height: 100 }} source={{ uri: toBase64Image(photo) }} /> ))} </View> </View> <View style={{ backgroundColor: 'rgb(28 28 28)', height: 600, width: 600, borderRadius: 64, flexDirection: 'column', padding: 64 }}> <View style={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}> {agentState.loading && (<ActivityIndicator size="large" color={"white"} />)} {agentState.answer && !agentState.loading && (<ScrollView style={{ flexGrow: 1, flexBasis: 0 }}><Text style={{ color: 'white', fontSize: 32 }}>{agentState.answer}</Text></ScrollView>)} </View> <TextInput style={{ color: 'white', height: 64, fontSize: 32, borderRadius: 16, backgroundColor: 'rgb(48 48 48)', padding: 16 }} placeholder='What do you need?' placeholderTextColor={'#888'} readOnly={agentState.loading} onSubmitEditing={(e) => agent.answer(e.nativeEvent.text)} /> </View> </View> ); }); 请修改并优化源代码
10-01
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>进制换练习工具</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "Microsoft YaHei", Arial, sans-serif; padding: 20px; background-color: #f5f7fa; color: #333; line-height: 1.6; } .container { max-width: 1000px; margin: 0 auto; background-color: #fff; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); padding: 25px; } h1 { text-align: center; color: #2c3e50; margin-bottom: 20px; font-weight: 600; font-size: 28px; position: relative; padding-bottom: 15px; } h1:after { content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 80px; height: 4px; background: linear-gradient(to right, #3498db, #2ecc71); border-radius: 2px; } .description { text-align: center; margin-bottom: 25px; color: #7f8c8d; font-size: 16px; } .controls { display: flex; justify-content: space-between; flex-wrap: wrap; margin-bottom: 25px; gap: 15px; } .btn-group { display: flex; gap: 10px; } .toggle-btn { padding: 10px 18px; border: none; border-radius: 6px; background-color: #3498db; color: #fff; cursor: pointer; font-size: 14px; transition: all 0.3s; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } .toggle-btn:hover { background-color: #2980b9; transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.15); } .toggle-btn:active { transform: translateY(0); } #hideAll { background-color: #e74c3c; } #hideAll:hover { background-color: #c0392b; } .filter-group { display: flex; gap: 10px; align-items: center; } .filter-select { padding: 10px 15px; border-radius: 6px; border: 1px solid #ddd; background-color: #fff; font-size: 14px; cursor: pointer; } .search-box { padding: 10px 15px; border-radius: 6px; border: 1px solid #ddd; font-size: 14px; width: 200px; } table { width: 100%; border-collapse: collapse; background-color: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); } th, td { padding: 14px 15px; text-align: center; border-bottom: 1px solid #eee; } th { background-color: #2c3e50; color: #fff; font-weight: 500; position: sticky; top: 0; } tr:nth-child(even) { background-color: #f8f9fa; } tr:hover { background-color: #f1f8ff; } .category { background-color: #e8f4fd; color: #2c3e50; font-weight: 500; } .answer { background-color: #f5f5f5; cursor: pointer; transition: all 0.3s; position: relative; } .answer:hover { background-color: #e9f7fe; } .answer.show { background-color: #fff; font-weight: 500; color: #27ae60; } .difficulty { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 12px; margin-left: 8px; } .easy { background-color: #d5f4e6; color: #27ae60; } .medium { background-color: #fff3cd; color: #f39c12; } .hard { background-color: #f8d7da; color: #e74c3c; } .footer { text-align: center; margin-top: 30px; color: #7f8c8d; font-size: 14px; } @media (max-width: 768px) { .controls { flex-direction: column; } .btn-group, .filter-group { justify-content: center; } .search-box { width: 100%; } th, td { padding: 10px 8px; font-size: 14px; } } </style> </head> <body> <div class="container"> <h1>进制换练习工具</h1> <p class="description">包含40道进制换练习题,点击答案单元格可查看答案,支持筛选和搜索功能</p> <div class="controls"> <div class="btn-group"> <button class="toggle-btn" id="showAll">显示全部答案</button> <button class="toggle-btn" id="hideAll">隐藏全部答案</button> </div> <div class="filter-group"> <select class="filter-select" id="typeFilter"> <option value="all">所有类型</option> <option value="decimal">十进制换</option> <option value="binary">二进制换</option> <option value="octal">八进制换</option> <option value="hex">十六进制换</option> </select> <input type="text" class="search-box" id="searchInput" placeholder="搜索题目..."> </div> </div> <table> <thead> <tr> <th>题号</th> <th>题目描述</th> <th>答案(点击显示/隐藏)</th> </tr> </thead> <tbody> <tr><td>1</td><td>十进制25二进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="11001">点击显示答案</td></tr> <tr><td>2</td><td>二进制1101十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="13">点击显示答案</td></tr> <tr><td>3</td><td>十进制46八进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="56">点击显示答案</td></tr> <tr><td>4</td><td>八进制57十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="47">点击显示答案</td></tr> <tr><td>5</td><td>十进制63十六进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="3F">点击显示答案</td></tr> <tr><td>6</td><td>十六进制2A十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="42">点击显示答案</td></tr> <tr><td>7</td><td>二进制10110八进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="26">点击显示答案</td></tr> <tr><td>8</td><td>八进制34二进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="11100">点击显示答案</td></tr> <tr><td>9</td><td>二进制110011十六进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="33">点击显示答案</td></tr> <tr><td>10</td><td>十六进制1F二进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="11111">点击显示答案</td></tr> <tr><td>11</td><td>十进制18二进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="10010">点击显示答案</td></tr> <tr><td>12</td><td>二进制10101十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="21">点击显示答案</td></tr> <tr><td>13</td><td>十进制72八进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="110">点击显示答案</td></tr> <tr><td>14</td><td>八进制62十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="50">点击显示答案</td></tr> <tr><td>15</td><td>十进制95十六进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="5F">点击显示答案</td></tr> <tr><td>16</td><td>十六进制3C十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="60">点击显示答案</td></tr> <tr><td>17</td><td>二进制11100八进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="34">点击显示答案</td></tr> <tr><td>18</td><td>八进制45二进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="100101">点击显示答案</td></tr> <tr><td>19</td><td>二进制101110十六进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="2E">点击显示答案</td></tr> <tr><td>20</td><td>十六进制4D二进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="1001101">点击显示答案</td></tr> <tr><td>21</td><td>十进制33二进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="100001">点击显示答案</td></tr> <tr><td>22</td><td>二进制11110十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="30">点击显示答案</td></tr> <tr><td>23</td><td>十进制29八进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="35">点击显示答案</td></tr> <tr><td>24</td><td>八进制76十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="62">点击显示答案</td></tr> <tr><td>25</td><td>十进制127十六进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="7F">点击显示答案</td></tr> <tr><td>26</td><td>十六进制6E十进制 <span class="difficulty easy">简单</span></td><td class="answer" data-ans="110">点击显示答案</td></tr> <tr><td>27</td><td>二进制100111八进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="47">点击显示答案</td></tr> <tr><td>28</td><td>八进制23二进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="10011">点击显示答案</td></tr> <tr><td>29</td><td>二进制110101十六进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="35">点击显示答案</td></tr> <tr><td>30</td><td>十六进制7A二进制 <span class="difficulty medium">中等</span></td><td class="answer" data-ans="1111010">点击显示答案</td></tr> <tr class="category"><td>31</td><td>八进制27十六进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="17">点击显示答案</td></tr> <tr class="category"><td>32</td><td>十六进制1C八进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="34">点击显示答案</td></tr> <tr class="category"><td>33</td><td>八进制42十六进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="22">点击显示答案</td></tr> <tr class="category"><td>34</td><td>十六进制39八进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="71">点击显示答案</td></tr> <tr class="category"><td>35</td><td>八进制56十六进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="2E">点击显示答案</td></tr> <tr class="category"><td>36</td><td>十六进制4B八进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="113">点击显示答案</td></tr> <tr class="category"><td>37</td><td>八进制61十六进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="31">点击显示答案</td></tr> <tr class="category"><td>38</td><td>十六进制5D八进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="135">点击显示答案</td></tr> <tr class="category"><td>39</td><td>八进制74十六进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="3C">点击显示答案</td></tr> <tr class="category"><td>40</td><td>十六进制6F八进制 <span class="difficulty hard">困难</span></td><td class="answer" data-ans="157">点击显示答案</td></tr> </tbody> </table> <div class="footer"> <p>进制换练习工具 © 2023 | 计算机基础学习</p> </div> </div> <script> // 获取所有答案单元格和按钮 const answerCells = document.querySelectorAll('.answer'); const showAllBtn = document.getElementById('showAll'); const hideAllBtn = document.getElementById('hideAll'); const typeFilter = document.getElementById('typeFilter'); const searchInput = document.getElementById('searchInput'); const tableRows = document.querySelectorAll('tbody tr'); // 单个答案单元格点击切换显示/隐藏 answerCells.forEach(cell => { cell.addEventListener('click', () => { const isShow = cell.classList.contains('show'); if (isShow) { cell.textContent = '点击显示答案'; cell.classList.remove('show'); } else { cell.textContent = cell.getAttribute('data-ans'); cell.classList.add('show'); } }); }); // 显示全部答案 showAllBtn.addEventListener('click', () => { answerCells.forEach(cell => { cell.textContent = cell.getAttribute('data-ans'); cell.classList.add('show'); }); }); // 隐藏全部答案 hideAllBtn.addEventListener('click', () => { answerCells.forEach(cell => { cell.textContent = '点击显示答案'; cell.classList.remove('show'); }); }); // 类型筛选功能 typeFilter.addEventListener('change', filterTable); // 搜索功能 searchInput.addEventListener('input', filterTable); function filterTable() { const filterValue = typeFilter.value; const searchValue = searchInput.value.toLowerCase(); tableRows.forEach(row => { const questionCell = row.cells[1]; if (!questionCell) return; const questionText = questionCell.textContent.toLowerCase(); let shouldShow = true; // 应用类型筛选 if (filterValue !== 'all') { if (filterValue === 'decimal' && !questionText.includes('十进制')) { shouldShow = false; } else if (filterValue === 'binary' && !questionText.includes('二进制')) { shouldShow = false; } else if (filterValue === 'octal' && !questionText.includes('八进制')) { shouldShow = false; } else if (filterValue === 'hex' && !questionText.includes('十六进制')) { shouldShow = false; } } // 应用搜索筛选 if (shouldShow && searchValue) { shouldShow = questionText.includes(searchValue); } // 显示或隐藏行 row.style.display = shouldShow ? '' : 'none'; }); } </script> </body> </html>
最新发布
10-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值