- 浏览: 3047783 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (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 2276以前写过BattleMoonWars的 ... -
Quartett!文本插入程序
2008-06-21 20:38 1568年初写的Quartett!的文本 ... -
BattleMoonWars 归档解压/压缩程序 (Java)
2008-04-08 16:44 2441呼,这个也是一年多之 ... -
ケータイ少女 script.arc的解压缩程序 (Java)
2008-04-08 14:02 4645嗯,这个也是快一年前写的了。当时在澄空看到有人想要解手机少女的 ... -
桃華月憚体験版的解压缩程序 (Java)
2008-04-08 13:38 3107这是差不多一年前写的程序了……有人说想看于是发出来。 当时也是 ... -
Quartett!的文本提取程序
2008-03-05 23:31 1870诶,之前写了这个程序 ... -
Fortune Arterial Tools
2008-02-28 13:34 2442using System; using System.IO; ... -
さくらシュトラッセ literal record
2008-01-28 14:53 1800脚本在scenario.sc里。无 ... -
Borland的库的一个小特征?
2008-01-06 00:22 2502最近弄了下KAMIPANI相关 ... -
[脚本分析] 从Quartett!的脚本得到资源列表
2007-12-21 14:00 1766听汉公的说明,看来LittleWitch所使用的FFD Sys ...
相关推荐
"Quartett!文本插入程序"是一个用于在特定文件中插入文本的工具,它主要处理tkn格式的文件。这个程序可能对程序员或系统管理员非常有用,因为它允许他们自动化一些文本处理任务,例如批量添加注释、修改配置文件或者...
"quartett-app" 是一个基于Java开发的项目,它是一个响应式的网页游戏,灵感来源于经典的超级Trumpf游戏。这个游戏的特色在于它结合了蒂罗尔地区城市的文化元素,为玩家提供了一种新颖且富有地方特色的卡牌游戏体验...
Jupyter-Notebook
Jupyter-Notebook
高效甘特图模板下载-精心整理.zip
lstm Summary Framework: z = U>x, x u Uz Criteria for choosing U: • PCA: maximize projected variance • CCA: maximize projected correlation • FDA: maximize projected intraclass variance
OpenGL调试工具,适合图形开发者,包括视频开发,播放器开始以及游戏开发者。
全国行政区划shp最新图.zip
全国研究生招生与在校数据+国家线-最新.zip
Jupyter-Notebook
直播电商交流平台 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B
《林黛玉进贾府》课本剧剧本
2000-2020年沪深A股上市公司融资约束程度SA指数-最新数据发布.zip
PPT模版资料,PPT模版资料
CPA注会考试最新教材资料-最新发布.zip
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
内容概要:本文提供了一个完整的职工管理系统的C++源代码。通过面向对象的编程方法,实现了包括创建新职工、查询、增加、修改、删除、排序、统计以及存储和恢复职工数据在内的多个基本操作功能。该系统支持不同的用户角色(如管理员与老板),并通过菜单驱动方式让用户方便地进行相关操作。此外,还包括了错误检测机制,确保操作过程中的异常得到及时处理。 适合人群:有一定C++语言基础,特别是面向对象编程经验的程序员;企业管理人员和技术开发人员。 使用场景及目标:适用于中小型企业内部的人力资源管理部门或IT部门,用于维护员工基本信息数据库,提高工作效率。通过本项目的学习可以加深对链表、类和对象的理解。 阅读建议:建议先熟悉C++的基本语法和面向对象概念,再深入学习代码的具体实现细节。对于关键函数,比如exchange、creatilist等,应当重点关注并动手实践以加强理解。
Jupyter-Notebook
考研公共课历年真题集-最新发布.zip
Huawei-HKUST Joint Workshop on Theory for Future Wireless 15-16 September 2022 华为-香港科技大学未来无线理论联合研讨会 Speaker:Jingwen Tong