- 浏览: 3056673 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (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 1578年初写的Quartett!的文本 ... -
BattleMoonWars 归档解压/压缩程序 (Java)
2008-04-08 16:44 2466呼,这个也是一年多之 ... -
ケータイ少女 script.arc的解压缩程序 (Java)
2008-04-08 14:02 4663嗯,这个也是快一年前写的了。当时在澄空看到有人想要解手机少女的 ... -
桃華月憚体験版的解压缩程序 (Java)
2008-04-08 13:38 3121这是差不多一年前写的程序了……有人说想看于是发出来。 当时也是 ... -
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 1781听汉公的说明,看来LittleWitch所使用的FFD Sys ...
相关推荐
"Quartett!文本插入程序"是一个用于在特定文件中插入文本的工具,它主要处理tkn格式的文件。这个程序可能对程序员或系统管理员非常有用,因为它允许他们自动化一些文本处理任务,例如批量添加注释、修改配置文件或者...
"quartett-app" 是一个基于Java开发的项目,它是一个响应式的网页游戏,灵感来源于经典的超级Trumpf游戏。这个游戏的特色在于它结合了蒂罗尔地区城市的文化元素,为玩家提供了一种新颖且富有地方特色的卡牌游戏体验...
2025职业教育知识竞赛题库(含答案).pptx
"SOA海鸥算法优化下的KELM核极限学习机分类MATLAB代码详解:传感器故障诊断数据集应用与本地EXCEL数据读取功能",(SOA-KELM)海鸥算法SOA优化KELM核极限学习机分类MATLAB代码 代码注释清楚。 main为运行主程序,可以读取本地EXCEL数据。 很方便,容易上手。 (以传感器故障诊断数据集为例) ,核心关键词:SOA-KELM;海鸥算法优化;核极限学习机分类;MATLAB代码;代码注释清楚;main程序;读取本地EXCEL数据;传感器故障诊断数据集。,SOA-KELM分类算法MATLAB代码:海鸥优化核极限学习机,轻松上手,读取EXCEL数据集进行传感器故障诊断
内容概要:本文由世界经济论坛与Capgemini联合发布,主要阐述了AI代理从简单程序演变为复杂自主系统的进程,强调了它们在现代各行业如医疗保健、教育及金融服务等方面所发挥的作用,并讨论了其潜在收益以及伴随的风险和挑战。文中详细介绍了AI代理的发展历程、核心技术趋势(深度学习、强化学习)、多种类型的AI代理及其系统架构,同时对未来的发展方向——多智能体系统进行了展望,探讨了提高生产力、优化资源配置的新机会。 适合人群:对人工智能感兴趣的各界人士,尤其是关注技术创新对企业和社会长远影响的决策者和技术领导者,如商业领袖、政府官员及其他利益相关方。 使用场景及目标:①帮助政策制定者理解AI代理的功能和应用场景;②为企业管理者提供关于部署和管理AI系统的指导;③为研究者指明未来科研方向并探讨伦理和社会责任等问题;④为技术人员揭示当前最先进技术和最佳实践案例。 其他说明:文中还提到了随着更加先进的AI代理不断涌现,确保安全性和有效监管将是未来发展的重要议题之一。此外,跨行业的共识对于将AI代理顺利整合到各个部门至关重要。文章指出需要建立稳健治理机制来保障AI技术健康发展并服务于公共利益最大化的目标。
2025网络安全理论知识考试题(含答案).pptx
项目已获导师指导并通过的高分毕业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行!可以放心下载 技术组成 语言:java 开发环境:idea 数据库:MySql8.0 部署环境:Tomcat(建议用 7.x 或者 8.x 版本),maven 数据库工具:navicat
基于FATFS系统的STM32F407 SD卡升级Bootloader程序:自动检测与升级流程,stm32f407 SD卡升级 bootloader程序 基于sdio fatfs系统的stm32 bootloader程序 功能简介: 本程序使用fatfs系统读取bin文件。 开机后会自动检测sd卡,检测到sd卡后,再读取固定名称的bin文件,之后会对bin文件进行首包校验,判断该升级包的起始地址是否正确,正确的话,就循环读取bin文件并写入到flash中。 完成升级。 详细流程请看流程图 ,stm32f407; SD卡升级; bootloader程序; fatfs系统读取bin文件; 检测SD卡; 首包校验; 循环写入flash。,STM32F407 SD卡升级Bootloader程序:基于SDIO FATFS系统实现自动升级功能
2025网络与信息安全技术题库及答案.doc
C# WinForm通用软件开发框架源码,基于VS2019 .NET与DevExpress 21,WebApi连接SQLServer2014数据库,互联网化数据访问模式,C# 源码 WinForm?通用软件开发框架平台源码 基于:C#Winform+ WebApi +SQLServer2014数据库 基于:VS2019.NET? DevExpress 21.2.6控件 基于:SQLServer2014?数据库 客户端通过Http访问WebApi获得json数据的模式,本系统走互联网,只需要把WebApi发布在公网即可。 说明:此框架源码除系统管理功能外,其它无源码 ,C#源码; WinForm; WebApi; SQLServer2014; VS2019.NET; DevExpress控件; 互联网模式; 系统管理功能; 发布。,C# WinForm开发框架:基于DevExpress与WebApi的通用软件平台源码
项目已获导师指导并通过的高分毕业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行!可以放心下载 技术组成 语言:java 开发环境:idea 数据库:MySql8.0 部署环境:Tomcat(建议用 7.x 或者 8.x 版本),maven 数据库工具:navicat
基于SqueezeNet迁移学习算法的滚动轴承故障诊断方法研究——在MATLAB r2021b环境下的应用与拓展至多元信号领域的研究,MATLAB环境下一种基于sqeezenet网络迁移学习的滚动轴承故障诊断方法。 算法运行环境为MATLAB r2021b,该代码展示了如何使用深度学习(迁移学习)方法对滚动轴承进行故障诊断,演示了如何将一维轴承振动信号转为二维尺度图图像并使用预训练网络应用迁移学习对轴承故障进行分类。 迁移学习显著减少了传统轴承诊断方法特征提取和特征选择所花费的时间,并在小型数据集中获得了良好的准确性。 算法可迁移至金融时间序列,地震 微震信号,机械振动信号,声发射信号,电压 电流信号,语音信号,声信号,生理信号(ECG,EEG,EMG)等信号。 ,MATLAB环境; SqueezeNet网络; 迁移学习; 滚动轴承故障诊断; 算法运行环境; 一维轴承振动信号转换; 二维尺度图图像; 特征提取和选择; 信号分析;迁移至其他类型信号 (以分号隔开),基于SqueezeNet迁移学习在MATLAB的滚动轴承故障诊断算法优化
基于弱形式PDE建模的COMSOL不相溶两相流渗流水驱油模拟研究,comsol不相溶两相流渗流模拟,水驱油,基于弱形式PDE建模,模型已验证。 ,核心关键词:comsol; 不相溶两相流; 渗流模拟; 水驱油; 弱形式PDE建模; 模型验证。,"基于弱形式PDE建模的COMSOL两相流渗流模拟:验证水驱油模型"
Tiled for Mac是一款功能强大的开源地图编辑器,适用于macOS系统。它支持正交、等距和六边形地图类型,可创建无限大小的地图,并支持多图层编辑。用户可以通过直观的界面快速添加、修改地图元素,使用像素精度放置对象,并支持图块动画和碰撞编辑。Tiled的TMX格式易于理解,支持多种插件扩展,兼容多种游戏引擎,如RPG和平台游戏。它还提供撤销/重做功能,方便用户调整和优化地图。
太阳能光伏MPPT控制蓄电池三阶段充电模型仿真说明文档(附扰动观测法仿真模型,R2015b版),充电控制器,太阳能光伏MPPT控制蓄电池充电模型。 其中,光伏MPPT控制采用扰动观测法(P&O法),蓄电池充电采用三阶段充电控制。 仿真模型附加一份仿真说明文档,便于理解和修改参数。 版本: R2015b ,充电控制器; 光伏MPPT控制; 扰动观测法(P&O法); 蓄电池充电控制; 三阶段充电控制; 仿真模型; 仿真说明文档; 版本:R2015b,"R2015b版:太阳能光伏MPPT三阶段充电控制仿真模型及说明"
项目已获导师指导并通过的高分毕业设计项目,可作为课程设计和期末大作业,下载即用无需修改,项目完整确保可以运行。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 项目都经过严格调试,确保可以运行!可以放心下载 技术组成 语言:java 开发环境:idea 数据库:MySql8.0 部署环境:Tomcat(建议用 7.x 或者 8.x 版本),maven 数据库工具:navicat
2025医院收费员考试题题库(含答案).docx
"欧姆龙PLC编程新手宝典:标准程序案例集,包括CP1H脉冲编程与触摸屏实战应用",欧姆龙PLC程序欧姆龙案例欧姆龙标准程序 本产品适用于新手或者在校生 本程序包括有欧姆龙CP1H脉冲程序案例,威纶通触摸屏程序,电子版讲义 程序涉及方面广,适合新手入门学习,掌握了这些以后欧姆龙脉冲程序基本通吃,编程起来无压力 本程序设计到CP1H各个轴的程序编写具体用了ACC PLS2 INI等众多指令, 每个轴的程序都是单独的,包括触摸屏在内,您可以直接调用程序套到直接的程序上,只需要把地址稍微改动即可。 本程序适用于新手、自动化专业在校生学习和提高,另外额外赠送主流的CAD电气原理图纸,包含各种主流的PLC接线原理图,各种成功案例,是每个电气工程师学习和提高最必不可少的资料 ,欧姆龙PLC程序; 欧姆龙案例; 欧姆龙标准程序; 新手学习; 在校生; CP1H脉冲程序案例; 威纶通触摸屏程序; 电子版讲义; 编程指令; 程序设计; PLC接线原理图; 成功案例。,欧姆龙PLC入门宝典:从新手到专业工程师的实用指南
"基于Simulink的锂电池SOC估计模型研究:卡尔曼滤波算法的参数辨识与模型优化",锂电池SOC估计模型 simulink SOC估算卡尔曼滤波估算 SOC电池参数辨识模型10个; 卡尔曼滤波算法锂电池SOC估算估算模型15个; 卡尔曼滤波31个; ,锂电池SOC估计模型; Simulink; SOC估算; 卡尔曼滤波估算; 电池参数辨识模型; 锂电池SOC卡尔曼滤波估算模型; 卡尔曼滤波,基于Simulink的锂电池SOC估计与卡尔曼滤波算法研究
苍鹰算法优化BP神经网络参数:多输入单输出预测建模及效果展示 注:此程序为matlab编写,可直接运行出多种预测结果图与评价指标。效果图为测试数据展示,具体预测效果以个人数据为准。,苍鹰优化算法NGO优化BP神经网络的软值和阈值参数做多输入单输出的拟合预测建模。 程序内注释详细直接替数据就可以使用。 程序语言为matlab。 程序直接运行可以出拟合预测图,迭代优化图,线性拟合预测图,多个预测评价指标。 PS:以下效果图为测试数据的效果图,主要目的是为了显示程序运行可以出的结果图,具体预测效果以个人的具体数据为准。 2.由于每个人的数据都是独一无二的,因此无法做到可以任何人的数据直接替就可以得到自己满意的效果。 ,核心关键词: 苍鹰优化算法; NGO优化; BP神经网络; 软值和阈值参数; 多输入单输出拟合预测建模; 程序内注释; MATLAB程序语言; 拟合预测图; 迭代优化图; 线性拟合预测图; 预测评价指标。,基于苍鹰优化算法的NGO-BP神经网络模型:多输入单输出拟合预测建模与评估