- 浏览: 3056587 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
我前两天在NetOA方面确实是有点懈怠了。不为别的,正是为了这篇将提到的脚本的分析。虽然没把分析做彻底,不过我觉得现在已经足够使用,顺便拿出来说说。
上个周末,汉公突然跟我提起FFDSystem的话题,然后有人联系我做Quartett!的汉化。自从跟汉公和明大合作参与汉化以来,我基本上就是做脚本处理的相关工作比较多;汉公解决破解的棘手问题,而明大主要完成打包问题,也兼做脚本编辑器,视具体分工而定。这次也不例外,汉公主攻了资源文件的破解和资源抽取,资源的打包还没做,脚本这块就暂时交给了我。一般,如果脚本是没经过处理的文本,那也就没我什么事了;这次遇到的果然还是经过处理了的二进制脚本。
一拿到已经从Script.dat中提取出来的脚本文件,我吓了一跳:文件名居然都是MD5……汉公那边果然还没把资源破解完善。不过没关系,只要文件内容是对的就能开工。可以确认的是,脚本(准确说是给到我手上的脚本)的后缀名是tkn。
打开其中的第一个文件,0a69b4afebd6d64527a21e3f1aa993f9.tkn。内容如下:
读起来似乎很郁闷(?),其实看到有那么多ASCII字符我已经很开心了。可以辨认出最开头的TOKENSET(但此时还无法判断那个d是什么)、ase_path、nclude等等。进一步观察可以发现那些看似被剪掉了的字符都在,前面的base_path、include就是如此。编辑器里显示不出来只是因为大于0x7F的字节被解释成双字节字符编码(DBCS)中一个双字节字符的首字节,也就是例如说0x81把base_path中的b(0x62)给“吃”了。
在上述截图范围内,我总共识别出了这些:base_path、include、Script/BaseInstruction.txt、motion、Main等字串。观察它们前后的规律:这些字串总是以0结尾,是标准的C string;这些字串的前面总是有一个大于0x7F的字节(留意到0x81和0x83),而在那个字节之前似乎总是有3个00字节,前面又是一个非00的字节。
为了方便分析,我写了一个小程序来抽取出我感兴趣的信息,辅助分析。
对应上面内容而提出出来的内容:
(格式是:字符串起始地址 一个奇怪的数字 字符串之前的那个字节 字符串内容)
经观察,发现字符串之前的那个字节似乎是某种操作码或者类型,而再前面的那个似乎是个什么奇怪的数字,会连续有好几个相同的,然后又增大一点。
接下来,突然发觉原来0x85也是个重要的数值;也有以这个数值打头的字符串,但一般都是长度为一的符号,所以先前被忽略了。想了想,干脆把0x80开始到0x88开头的,其之前是三个00的东西全部都扫描一遍。于是在之前的程序上修改了一下判断条件,得到下面代码:
opcode_analysis.cs:
这段代码本身没什么稀奇,只有第57行到62行的内容有点诡异:居然把变量赋值给自己了?
不不,再怎么说我也不可能犯这种错误。这其实是C# 3.0里的一个有趣语法,initializer。可以通过initializer,在使用new关键字构造新实例的时候指定其中一些字段的值;等号左边的是字段名,右边则是字面量或者变量名(或者表达式)。编译器能够正确识别出看似是同名字的token之间的区别,因而能够正确赋值。好吧我承认这不是好的编程习惯,大家看到了千万不要学,要引以为戒……
另外,那个if里一大堆对currentByte的判断后来也重构到外面一个单独的MatchOpcode()方法里去了。像上面这样写实在太恶心……也要引以为戒哦
虽然没什么稀奇,还是说下这个文件里的流程:
1、检查作为参数文件是否存在,并且是否后缀为tkn。检查不通过则退出程序。
2、获取一个Shift-JIS和一个UTF-16LE字符集的Encoding实例,并使用它们创建Shift-JIS的输入流和UTF-16LE的输出流。
3、校验脚本文件的特征码(signature)。这里假设头12个字节都是特征码。
4、校验成功后,给输出流写出一个字节序标记(BOM,Byte Order Mark)。这本来应该不需要手工做的,但我一直没弄清楚为什么我明明在创建utf16le时指定要BOM系统却不帮我自动做……
5、创建一个队列来记录最近的三个字节。使用一个变量(lastOpcode)来记录最近的第四个字节。
6、扫描文件直到遇到文件结束。如果遇到了连续的3个00,则读入其后的一个字节,并判断是否在[0x80, 0x88]的范围内;满足的话则读入一个C string并输出记录。
7、程序结束。
于是我得到了更新版的记录:
(格式与前面相同)
于是我恍然大悟:那“奇怪的数字”居然是脚本源文件行号!而被认为是操作码或者类型的那个字节,则用于指定后面字符串的类型:可以是符号、十进制数字、十六进制数字、标识符、字符串、符号等。形象点看,如下图:
(红色的是行号,黄色的是类型,绿色的是字符串内容)
但位于脚本的0xC到0xF的那个数字(上图紫色部分)是什么意思还让我伤了下脑筋。观察了一下,发现从0a69b4afebd6d64527a21e3f1aa993f9.tkn提取出来的“东西”一共有1237个,而那意义不明的数字是0x876 = 2166,还差了不少。但总觉得它们应该有关系。突然想起我前面是用了个很糟糕的办法来提取记录,有连续的3个00字节才满足条件。但假如行号超过了0xFF = 255行的话这个条件就不成立了。赶紧把程序修改为第三版,按照新的理解去读入“行号”和“类型”两个数据,确认那个数字确实就是文件里总的token数。
然后我才理解了signature里那TOKENSET的含义……这看似是二进制的脚本其实根本没有编译过的二进制脚本之魂。
编译的前端至少有两部,scan和parse。Scan阶段处理词法分析,会把源文件切分成一个个token,而parse阶段处理文法分析,会根据上下文无关文法来尝试“理解”这些token,构造语法树(进而构造抽象语法树)。但这里我所看到的脚本只对脚本源文件做了scan,然后直接把scan的结果保存成“二进制脚本”了。真够OTL的。
简单点说,这个“二进制脚本”完整保留了脚本源文件的文本信息,而且还多加了些行号、类型等信息进去。缺少的是被去除了的注释。
那就很好办了不是么。于是把所谓的反编译程序写了出来:
ScriptDecompiler.cs
中间有些代码是为了插入缩进的,忽略那部分吧……
得到的脚本看起来像是这样:
中间是有很多空行没错。那些原本应该是有注释的地方,或者本身就是空行(为了让代码好看)。这里我只是把原始脚本的状态尽量复原了出来而已。
==========================================================
暂时来说,这样就够用了。这个脚本处理已经让我们能做很多事。要进一步做的话,我可以把文法分析也做出来,方便对脚本更仔细的分析。但这两天肯定是没时间做那种事情咯……
Until then...
==========================================================
P.S. 上述代码皆以BSD许可证的形式发布。请有兴趣的人在遵循BSD许可证的前提下自由使用这些代码。
P.S.S. 其实上面代码值得吐槽的地方N多。例如说我完全没使用try-catch语句来处理可能出现的异常,又例如我在第一份代码里把一个Queue转变成数组再做相等性比较(极其恶心,本来自己写个循环数组就解决了)。……这些都是所谓的“原型代码”,目标是尽可能快的写出代码来验证自己的一些设想是否正确。偷懒不加异常处理、宁可别扭的使用标准库里的容器也不自己封装一个,都是出于同一原因。看倌们请多多包涵这些地方 XD
P.S.S.S. 唉,不过我偷懒也真是过分了。后一份代码里居然没把BinaryWriter改回用StreamWriter……
上个周末,汉公突然跟我提起FFDSystem的话题,然后有人联系我做Quartett!的汉化。自从跟汉公和明大合作参与汉化以来,我基本上就是做脚本处理的相关工作比较多;汉公解决破解的棘手问题,而明大主要完成打包问题,也兼做脚本编辑器,视具体分工而定。这次也不例外,汉公主攻了资源文件的破解和资源抽取,资源的打包还没做,脚本这块就暂时交给了我。一般,如果脚本是没经过处理的文本,那也就没我什么事了;这次遇到的果然还是经过处理了的二进制脚本。
一拿到已经从Script.dat中提取出来的脚本文件,我吓了一跳:文件名居然都是MD5……汉公那边果然还没把资源破解完善。不过没关系,只要文件内容是对的就能开工。可以确认的是,脚本(准确说是给到我手上的脚本)的后缀名是tkn。
打开其中的第一个文件,0a69b4afebd6d64527a21e3f1aa993f9.tkn。内容如下:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 00000000 54 4F 4B 45 4E 53 45 54 64 00 00 00 76 08 00 00 TOKENSETd...v... 00000010 0C 00 00 00 85 23 00 0C 00 00 00 81 62 61 73 65 ....?.....|ase 00000020 5F 70 61 74 68 00 0C 00 00 00 83 2E 2E 2F 00 16 _path.....?./.. 00000030 00 00 00 85 23 00 16 00 00 00 81 69 6E 63 6C 75 ...?.....(nclu 00000040 64 65 00 16 00 00 00 83 53 63 72 69 70 74 2F 42 de.....ゴcript/B 00000050 61 73 65 49 6E 73 74 72 75 63 74 69 6F 6E 2E 74 aseInstruction.t 00000060 78 74 00 20 00 00 00 81 6D 6F 74 69 6F 6E 00 20 xt. ...[otion. 00000070 00 00 00 81 4D 61 69 6E 00 20 00 00 00 85 28 00 ...`ain. ...?.
读起来似乎很郁闷(?),其实看到有那么多ASCII字符我已经很开心了。可以辨认出最开头的TOKENSET(但此时还无法判断那个d是什么)、ase_path、nclude等等。进一步观察可以发现那些看似被剪掉了的字符都在,前面的base_path、include就是如此。编辑器里显示不出来只是因为大于0x7F的字节被解释成双字节字符编码(DBCS)中一个双字节字符的首字节,也就是例如说0x81把base_path中的b(0x62)给“吃”了。
在上述截图范围内,我总共识别出了这些:base_path、include、Script/BaseInstruction.txt、motion、Main等字串。观察它们前后的规律:这些字串总是以0结尾,是标准的C string;这些字串的前面总是有一个大于0x7F的字节(留意到0x81和0x83),而在那个字节之前似乎总是有3个00字节,前面又是一个非00的字节。
为了方便分析,我写了一个小程序来抽取出我感兴趣的信息,辅助分析。
对应上面内容而提出出来的内容:
(格式是:字符串起始地址 一个奇怪的数字 字符串之前的那个字节 字符串内容)
0x1C 0xC 0x81 base_path 0x2B 0xC 0x83 ../ 0x3B 0x16 0x81 include 0x48 0x16 0x83 Script/BaseInstruction.txt 0x68 0x20 0x81 motion 0x74 0x20 0x81 Main
经观察,发现字符串之前的那个字节似乎是某种操作码或者类型,而再前面的那个似乎是个什么奇怪的数字,会连续有好几个相同的,然后又增大一点。
接下来,突然发觉原来0x85也是个重要的数值;也有以这个数值打头的字符串,但一般都是长度为一的符号,所以先前被忽略了。想了想,干脆把0x80开始到0x88开头的,其之前是三个00的东西全部都扫描一遍。于是在之前的程序上修改了一下判断条件,得到下面代码:
opcode_analysis.cs:
using System; using System.Collections.Generic; using System.IO; using System.Text; namespace FFDSystemAnalysis { sealed class Analyzer { private static readonly byte[ ] SIGNATURE = { ( byte )0x54, ( byte )0x4F, ( byte )0x4B, ( byte )0x45, ( byte )0x4E, ( byte )0x53, ( byte )0x45, ( byte )0x54, ( byte )0x64, ( byte )0x0, ( byte )0x0, ( byte )0x0 }; static void Main( string[ ] args ) { if ( !args[ 0 ].EndsWith( ".tkn" ) ) return; if ( !File.Exists( args[ 0 ] ) ) return; string infile = args[ 0 ]; string outfile = infile + ".txt"; Encoding utf16le = new UnicodeEncoding( false, true ); Encoding jis = Encoding.GetEncoding( 932 ); using ( BinaryReader reader = new BinaryReader( File.OpenRead( infile ), jis ) ) { using ( BinaryWriter writer = new BinaryWriter( File.Create( outfile ), utf16le ) ) { byte[ ] sig = reader.ReadBytes( SIGNATURE.Length ); if ( !Equals( sig, SIGNATURE ) ) { Console.WriteLine( "Wrong signature" ); return; } // write UTF-16LE BOM writer.Write( ( ushort ) 0xFEFF ); Queue<byte> queue = new Queue<byte>( 3 ); queue.Enqueue( reader.ReadByte( ) ); queue.Enqueue( reader.ReadByte( ) ); queue.Enqueue( reader.ReadByte( ) ); byte lastOpcode = 0; while ( reader.BaseStream.Position < reader.BaseStream.Length ) { byte currentByte = reader.ReadByte( ); if ( currentByte == 0x080 || currentByte == 0x081 || currentByte == 0x082 || currentByte == 0x083 || currentByte == 0x084 || currentByte == 0x085 || currentByte == 0x086 || currentByte == 0x087 || currentByte == 0x088 ) { if ( MatchQueueData( queue ) ) { long position = reader.BaseStream.Position; string line = ReadCString( reader ); Entry e = new Entry( ) { position = position, opcode = currentByte, lastOpcode = lastOpcode, line = line }; writer.Write( utf16le.GetBytes( string.Format( "{0}{1}", e.ToString( ), Environment.NewLine ) ) ); } // if } // if // re-initialize lastOpcode = queue.Dequeue( ); queue.Enqueue( currentByte ); } // while } // using } // using } // Main static bool Equals( byte[ ] a, byte[ ] b ) { int len = a.Length; if ( len != b.Length ) return false; for ( int i = 0; i < len; i++ ) { if ( a[ i ] != b[ i ] ) return false; } return true; } static bool MatchQueueData( Queue<byte> queue ) { byte[ ] array = queue.ToArray( ); return Equals( zeros, array ); } static string ReadCString( BinaryReader reader ) { StringBuilder builder = new StringBuilder( ); char c = '\0'; while ( ( c = reader.ReadChar( ) ) != '\0' ) { builder.Append( c ); } return builder.ToString( ); } static readonly byte[ ] zeros = new byte[ ] { 0, 0, 0 }; } struct Entry { public long position; public string line; public byte opcode; public byte lastOpcode; public override string ToString( ) { return string.Format( "0x{0:X} 0x{1:X} 0x{2:X} {3}", this.position, this.lastOpcode, this.opcode, this.line ); } } }
这段代码本身没什么稀奇,只有第57行到62行的内容有点诡异:居然把变量赋值给自己了?
不不,再怎么说我也不可能犯这种错误。这其实是C# 3.0里的一个有趣语法,initializer。可以通过initializer,在使用new关键字构造新实例的时候指定其中一些字段的值;等号左边的是字段名,右边则是字面量或者变量名(或者表达式)。编译器能够正确识别出看似是同名字的token之间的区别,因而能够正确赋值。好吧我承认这不是好的编程习惯,大家看到了千万不要学,要引以为戒……
另外,那个if里一大堆对currentByte的判断后来也重构到外面一个单独的MatchOpcode()方法里去了。像上面这样写实在太恶心……也要引以为戒哦
虽然没什么稀奇,还是说下这个文件里的流程:
1、检查作为参数文件是否存在,并且是否后缀为tkn。检查不通过则退出程序。
2、获取一个Shift-JIS和一个UTF-16LE字符集的Encoding实例,并使用它们创建Shift-JIS的输入流和UTF-16LE的输出流。
3、校验脚本文件的特征码(signature)。这里假设头12个字节都是特征码。
4、校验成功后,给输出流写出一个字节序标记(BOM,Byte Order Mark)。这本来应该不需要手工做的,但我一直没弄清楚为什么我明明在创建utf16le时指定要BOM系统却不帮我自动做……
5、创建一个队列来记录最近的三个字节。使用一个变量(lastOpcode)来记录最近的第四个字节。
6、扫描文件直到遇到文件结束。如果遇到了连续的3个00,则读入其后的一个字节,并判断是否在[0x80, 0x88]的范围内;满足的话则读入一个C string并输出记录。
7、程序结束。
于是我得到了更新版的记录:
(格式与前面相同)
0x15 0xC 0x85 # 0x1C 0xC 0x81 base_path 0x2B 0xC 0x83 ../ 0x34 0x16 0x85 # 0x3B 0x16 0x81 include 0x48 0x16 0x83 Script/BaseInstruction.txt 0x68 0x20 0x81 motion 0x74 0x20 0x81 Main
于是我恍然大悟:那“奇怪的数字”居然是脚本源文件行号!而被认为是操作码或者类型的那个字节,则用于指定后面字符串的类型:可以是符号、十进制数字、十六进制数字、标识符、字符串、符号等。形象点看,如下图:
(红色的是行号,黄色的是类型,绿色的是字符串内容)
但位于脚本的0xC到0xF的那个数字(上图紫色部分)是什么意思还让我伤了下脑筋。观察了一下,发现从0a69b4afebd6d64527a21e3f1aa993f9.tkn提取出来的“东西”一共有1237个,而那意义不明的数字是0x876 = 2166,还差了不少。但总觉得它们应该有关系。突然想起我前面是用了个很糟糕的办法来提取记录,有连续的3个00字节才满足条件。但假如行号超过了0xFF = 255行的话这个条件就不成立了。赶紧把程序修改为第三版,按照新的理解去读入“行号”和“类型”两个数据,确认那个数字确实就是文件里总的token数。
然后我才理解了signature里那TOKENSET的含义……这看似是二进制的脚本其实根本没有编译过的二进制脚本之魂。
编译的前端至少有两部,scan和parse。Scan阶段处理词法分析,会把源文件切分成一个个token,而parse阶段处理文法分析,会根据上下文无关文法来尝试“理解”这些token,构造语法树(进而构造抽象语法树)。但这里我所看到的脚本只对脚本源文件做了scan,然后直接把scan的结果保存成“二进制脚本”了。真够OTL的。
简单点说,这个“二进制脚本”完整保留了脚本源文件的文本信息,而且还多加了些行号、类型等信息进去。缺少的是被去除了的注释。
那就很好办了不是么。于是把所谓的反编译程序写了出来:
ScriptDecompiler.cs
// ScriptDecompiler.cs, 2007/12/18 // by RednaxelaFX /* * Copyright (c) 2007 着作权由RednaxelaFX所有。着作权人保留一切权利。 * * 这份授权条款,在使用者符合以下三条件的情形下,授予使用者使用及再散播本 * 软件包装原始码及二进位可执行形式的权利,无论此包装是否经改作皆然: * * * 对于本软件源代码的再散播,必须保留上述的版权宣告、此三条件表列,以 * 及下述的免责声明。 * * 对于本套件二进位可执行形式的再散播,必须连带以文件以及/或者其他附 * 于散播包装中的媒介方式,重制上述之版权宣告、此三条件表列,以及下述 * 的免责声明。 * * 未获事前取得书面许可,不得使用RednaxelaFX之名称, * 来为本软件之衍生物做任何表示支持、认可或推广、促销之行为。 * * 免责声明:本软件是由RednaxelaFX以现状("as is")提供, * 本软件包装不负任何明示或默示之担保责任,包括但不限于就适售性以及特定目 * 的的适用性为默示性担保。RednaxelaFX无论任何条件、 * 无论成因或任何责任主义、无论此责任为因合约关系、无过失责任主义或因非违 * 约之侵权(包括过失或其他原因等)而起,对于任何因使用本软件包装所产生的 * 任何直接性、间接性、偶发性、特殊性、惩罚性或任何结果的损害(包括但不限 * 于替代商品或劳务之购用、使用损失、资料损失、利益损失、业务中断等等), * 不负任何责任,即在该种使用已获事前告知可能会造成此类损害的情形下亦然。 */ using System; using System.Collections.Generic; using System.IO; using System.Text; namespace FFDSystemAnalysis { enum TokenType { Decimal = 0x080, Identifier = 0x081, Hexadecimal = 0x082, String = 0x083, Operator = 0x085 } sealed class ScriptDecompiler { private static readonly byte[ ] SIGNATURE = { ( byte )0x54, ( byte )0x4F, ( byte )0x4B, ( byte )0x45, ( byte )0x4E, ( byte )0x53, ( byte )0x45, ( byte )0x54, ( byte )0x64, ( byte )0x0, ( byte )0x0, ( byte )0x0 }; static void Main( string[ ] args ) { if ( !args[ 0 ].EndsWith( ".tkn" ) ) return; if ( !File.Exists( args[ 0 ] ) ) return; string infile = args[ 0 ]; string outfile = Path.GetFileNameWithoutExtension( infile ) + ".txt"; Encoding utf16le = new UnicodeEncoding( false, true ); Encoding jis = Encoding.GetEncoding( 932 ); using ( BinaryReader reader = new BinaryReader( File.OpenRead( infile ), jis ) ) { using ( BinaryWriter writer = new BinaryWriter( File.Create( outfile ), utf16le ) ) { byte[ ] sig = reader.ReadBytes( SIGNATURE.Length ); if ( !Equals( sig, SIGNATURE ) ) { Console.WriteLine( "Wrong signature" ); return; } // write UTF-16LE BOM writer.Write( ( ushort ) 0xFEFF ); // process each token int lineNum = 1; int lastLineNum = 1; TokenType tokenType = TokenType.Operator; TokenType lastTokenType = TokenType.Operator; int tabCount = 0; int tokenCount = reader.ReadInt32( ); for ( int tokenNum = 0; tokenNum < tokenCount; ++tokenNum ) { // deal with line numbers, insert empty new lines if needed lineNum = reader.ReadInt32( ); if ( lastLineNum < lineNum ) { // should write on a newline // write empty lines for ( int i = lastLineNum; i < lineNum; ++i ) { writer.Write( utf16le.GetBytes( Environment.NewLine ) ); } // write tabs as indent for ( int tabs = 0; tabs < tabCount; ++tabs ) { writer.Write( utf16le.GetBytes( "\t" ) ); } // put a dummy value into tokenType lastTokenType = TokenType.Operator; } // get token tokenType tokenType = ( TokenType ) ( reader.ReadByte( ) & 0x0FF ); // get token value string tokenString = ReadCString( reader ); // deal with different token types if ( !( lastTokenType == TokenType.Operator || lastTokenType == TokenType.String || lastTokenType == TokenType.Decimal || lastTokenType == TokenType.Hexadecimal ) ) { writer.Write( utf16le.GetBytes( " " ) ); } switch ( tokenType ) { case TokenType.Decimal: case TokenType.Identifier: case TokenType.Hexadecimal: writer.Write( utf16le.GetBytes( tokenString ) ); break; case TokenType.String: writer.Write( utf16le.GetBytes( string.Format( "\"{0}\"", tokenString ) ) ); break; case TokenType.Operator: switch ( tokenString ) { case "#": case "%": case "-": case "@": writer.Write( utf16le.GetBytes( tokenString ) ); break; case "{": ++tabCount; writer.Write( utf16le.GetBytes( tokenString ) ); break; case "}": --tabCount; writer.BaseStream.Position -= 2; // delete the last tab writer.Write( utf16le.GetBytes( tokenString ) ); break; case "(": case ",": case ";": case "=": writer.Write( utf16le.GetBytes( string.Format( "{0} ", tokenString ) ) ); break; case ")": writer.Write( utf16le.GetBytes( string.Format( " {0}", tokenString ) ) ); break; } // switch tokenString break; default: Console.WriteLine( "Unexpected token type {0} at 0x{1}.", tokenType.ToString( "X" ), reader.BaseStream.Position.ToString( "X" ) ); return; } // switch tokenType // re-initialize lastLineNum = lineNum; lastTokenType = tokenType; } // for } } } static bool Equals( byte[ ] a, byte[ ] b ) { int len = a.Length; if ( len != b.Length ) return false; for ( int i = 0; i < len; i++ ) { if ( a[ i ] != b[ i ] ) return false; } return true; } static string ReadCString( BinaryReader reader ) { StringBuilder builder = new StringBuilder( ); char c = '\0'; while ( ( c = reader.ReadChar( ) ) != '\0' ) { builder.Append( c ); } return builder.ToString( ); } } }
中间有些代码是为了插入缩进的,忽略那部分吧……
得到的脚本看起来像是这样:
#base_path "../" #include "Script/BaseInstruction.txt" motion Main ( )
中间是有很多空行没错。那些原本应该是有注释的地方,或者本身就是空行(为了让代码好看)。这里我只是把原始脚本的状态尽量复原了出来而已。
==========================================================
暂时来说,这样就够用了。这个脚本处理已经让我们能做很多事。要进一步做的话,我可以把文法分析也做出来,方便对脚本更仔细的分析。但这两天肯定是没时间做那种事情咯……
Until then...
==========================================================
P.S. 上述代码皆以BSD许可证的形式发布。请有兴趣的人在遵循BSD许可证的前提下自由使用这些代码。
P.S.S. 其实上面代码值得吐槽的地方N多。例如说我完全没使用try-catch语句来处理可能出现的异常,又例如我在第一份代码里把一个Queue转变成数组再做相等性比较(极其恶心,本来自己写个循环数组就解决了)。……这些都是所谓的“原型代码”,目标是尽可能快的写出代码来验证自己的一些设想是否正确。偷懒不加异常处理、宁可别扭的使用标准库里的容器也不自己封装一个,都是出于同一原因。看倌们请多多包涵这些地方 XD
P.S.S.S. 唉,不过我偷懒也真是过分了。后一份代码里居然没把BinaryWriter改回用StreamWriter……
评论
3 楼
lwwin
2007-12-23
偶的意思是说,这些 天分 是有爱………………
2 楼
RednaxelaFX
2007-12-21
跟天分大概没什么关系,不过经验肯定是有关的 ^ ^
1 楼
lwwin
2007-12-20
经验啊经验,破解的东西果然还是靠天分的罢?
发表评论
-
IDA Pro Free笔记
2013-05-19 08:59 0IDA把数据都存哪里了? Windows 7 D:\tem ... -
加密算法收集
2011-01-23 16:10 0加密算法,OTP http://en.wikipedia.or ... -
BattleMoonWars 归档解压/压缩程序(砍掉重炼版)
2010-05-03 21:40 2286以前写过BattleMoonWars的 ... -
Quartett!文本插入程序
2008-06-21 20:38 1577年初写的Quartett!的文本 ... -
BattleMoonWars 归档解压/压缩程序 (Java)
2008-04-08 16:44 2465呼,这个也是一年多之 ... -
ケータイ少女 script.arc的解压缩程序 (Java)
2008-04-08 14:02 4662嗯,这个也是快一年前写的了。当时在澄空看到有人想要解手机少女的 ... -
桃華月憚体験版的解压缩程序 (Java)
2008-04-08 13:38 3120这是差不多一年前写的程序了……有人说想看于是发出来。 当时也是 ... -
Quartett!的文本提取程序
2008-03-05 23:31 1885诶,之前写了这个程序 ... -
Fortune Arterial Tools
2008-02-28 13:34 2450using System; using System.IO; ... -
さくらシュトラッセ literal record
2008-01-28 14:53 1819脚本在scenario.sc里。无 ... -
Borland的库的一个小特征?
2008-01-06 00:22 2512最近弄了下KAMIPANI相关 ... -
[脚本分析] 从Quartett!的脚本得到资源列表
2007-12-21 14:00 1780听汉公的说明,看来LittleWitch所使用的FFD Sys ...
相关推荐
"Quartett!文本插入程序"是一个用于在特定文件中插入文本的工具,它主要处理tkn格式的文件。这个程序可能对程序员或系统管理员非常有用,因为它允许他们自动化一些文本处理任务,例如批量添加注释、修改配置文件或者...
"quartett-app" 是一个基于Java开发的项目,它是一个响应式的网页游戏,灵感来源于经典的超级Trumpf游戏。这个游戏的特色在于它结合了蒂罗尔地区城市的文化元素,为玩家提供了一种新颖且富有地方特色的卡牌游戏体验...
MATLAB数字滤波器设计及其在语音信号去噪中的应用:源码详解与报告分享,MATLAB 数字滤波器设计 及其语音信号去噪应用。 (供学习交流)带源码,带注释。 有代码和报告。 ,核心关键词:MATLAB; 数字滤波器设计; 语音信号去噪应用; 源码; 注释; 代码与报告。,"MATLAB数字滤波器设计及其在语音信号去噪中的应用:带源码注释与报告"
COMSOL软件模拟三维电化学腐蚀过程的研究分析,comsol三维电化学腐蚀。 ,核心关键词:Comsol;三维电化学;腐蚀;模型模拟;电化学腐蚀过程。,"Comsol模拟:三维电化学腐蚀过程解析"
基于COMSOL的降雨入渗模型:边坡与渗流边界下的强度折减塑性形变研究,comsol降雨入渗模型,边坡降雨边界与渗流边界 强度折减塑性形变 ,comsol降雨入渗模型; 降雨边界; 渗流边界; 强度折减; 塑性形变,"COMSOL降雨入渗模型:边坡渗流与强度折减塑性形变分析"
2025员工安全意识培训试题及答案.docx
Python自动化办公源码-06在Word表格中将上下行相同内容的单元格自动合并
基于深度学习的神经网络技术在信息通信领域的应用研究.pdf
1.内容概要 通过KNN实现鸢尾花分类,即将新的数据点分配给已知类别中的某一类。该算法的核心思想是通过比较距离来确定最近邻的数据点,然后利用这些邻居的类别信息来决定待分类数据点的类别。 2.KNN算法的伪代码 对未知类别属性的数据集中的每个点依次执行以下操作: (1)计算已知类别数据集中的点与当前点之间的距离; (2)按照距离递增次序排序; (3)选取与当前点距离最小的k个点; (4)确定前k个点所在类别的出现频率; (5)返回前k个点出现频率最高的类别作为当前点的预测分类。 3.数据集说明 代码使用`pandas`库加载了一个名为`iris.arff.csv`的数据集 4.学习到的知识 通过鸢尾花分类学习了KNN算法,选择样本数据集中前k个最相似的数据,就是KNN算法中k的出处。k值过大,会出现分类结果模糊的情况;k值较小,那么预测的标签比较容易受到样本的影响。在实验过程中,不同的k值也会导致分类器的错误率不同。KNN算法精度高、无数据输入的假定,可以免去训练过程。但是对于数据量较多的训练样本,KNN必须保存全部数据集,可能会存在计算的时间复杂度、空间复杂度高的情况,存在维数灾难问
感应电机控制与矢量控制仿真:磁链闭环、转速闭环与电流闭环的综合应用研究,感应电机控制仿真,矢量控制,异步电机仿真,磁链闭环,转速闭环,电流闭环 ,核心关键词:感应电机控制仿真; 矢量控制; 异步电机仿真; 磁链闭环; 转速闭环; 电流闭环,"感应电机矢量控制仿真:磁链、转速、电流三闭环异步电机模拟"
威纶通TK6071IP触摸屏锁屏宏指令程序详解:注释清晰,便于理解与学习,威纶通触摸屏锁屏宏指令程序 ~ 威纶通触摸屏锁屏宏指令程序,TK6071IP触摸屏 利用宏指令程序来控制,宏指令注释清晰,方便理解程序。 具有很好的学习意义和借鉴价值。 ,关键词:威纶通触摸屏;锁屏宏指令程序;TK6071IP触摸屏;宏指令控制;注释清晰;学习借鉴。,威纶通触摸屏宏指令程序:清晰注释,学习借鉴之利器
2025输血相关法律法规试题考核试题及答案.docx
Python游戏编程源码-2048小游戏
2025最新康复医学概论考试题库(含答案).doc
Python自动化办公源码-09用Python批量往Word文档中指定位置添加图片
高品质车载充电器技术解决方案:含原理图、PCB图、C源代码及DSP控制器资料,附赠CDCDC模块资料,车载充电器 3Kw OBC 车载充电器 含原理图、PCB图、C源代码、变压器参数等生产资料。 附赠15kwdcdc模块资料 1、这款产品的方案采用的是dsp2803x系列。 2、原理图和Pcb采用AD绘制。 此方案仅供学习 ,车载充电器; 3Kw OBC; 原理图; PCB图; C源代码; 变压器参数; 生产资料; dsp2803x系列; AD绘制; 15kwdcdc模块资料,3Kw车载充电器方案:DSP2803x系列原理图、PCB图及C源学习包
2025最新康复医学考试题及答案.docx
内容概要:本文介绍了一种用于视频处理的新型卷积神经网络(CNN)加速器。主要创新点在于引入了混合精度计算、跨帧数据重用控制器及引擎,以及混合位宽差帧数据编码解码器。这些特性有效解决了视频帧间的时空相关性和稀疏性带来的挑战,提高了处理速度并降低了功耗和带宽需求。具体来说,通过对连续帧的数据相似度利用,可以在保持高精度的同时减少计算量和内存访问次数;通过多类型稀疏卷积聚类数组实现了对现代稀疏神经网络的支持;并通过混合位宽度编码减少了离芯片外的数据传输量,最高达到68%。 适用人群:从事深度学习硬件加速设计的研究人员和技术爱好者;关注AI边缘计算领域的从业者。 使用场景及目标:适用于自动驾驶汽车摄像头、监控系统等实时视频流应用场景。旨在为开发者提供高效的低能耗解决方案,在有限的时间和资源下完成大量的图像信号处理任务,如跟踪、分类等。 其他说明:文中还详细描述了芯片的设计细节,测试平台构建,以及不同模型(如MobileNet)在网络上的实际性能表现。
COMSOL电化学喷射腐蚀模拟与解析:技术原理及应用实践,comsol电化学喷射腐蚀 ,核心关键词:comsol; 电化学; 喷射腐蚀; 电化学腐蚀。,"电化学喷射腐蚀研究:comsol模拟与解析"
项目已获导师指导并通过的高分毕业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行!可以放心下载 技术组成 语言:java 开发环境:idea 数据库:MySql8.0 部署环境:Tomcat(建议用 7.x 或者 8.x 版本),maven 数据库工具:navicat