从本质上来说,QuickBMS只是一个通用解包器引擎,由Luigi Auriemma开发并维护,虽然QuickBMS简单而高效,技术组也经常使用QuickBMS来解包游戏的资源文件,但任何技术都存在一个入门门槛,而本篇以及后续教学笔记就试图为同学们拆除这个门槛。
那么,进入QuickBMS世界的门槛有多高呢?我将使用什么方法来拆除这个门槛呢?
第一节 QuickBMS使用方法
QuickBMS的使用方法没有任何难度,其步骤如下所示:
1、使用鼠标左键双击运行quickbms.exe程序。
2、选择一个解包脚本。
3、选择一个游戏资源文件(包文件)。
4、选择一个输出文件夹来存放解包出来的文件。
5、观察解包进度。
就这么几个步骤,是不是很简单啊?请同学们牢记,针对QuickBMS程序来说,无论在任何情况下,解包脚本、包文件和输出文件夹都是必须的,让我们来仔细的观察一下这三个必须存在的东西:包文件已经由等待汉化的游戏提供出来了,输出文件夹我们自己在硬盘上建立一个就行了,那解包脚本在哪里呢?答案是没有。这也是我们为什么把QuickBMS称为通用解包器引擎而不是通用解包器的原因,QuickBMS本身并不包含万能的解包脚本(因为根本不存在什么万能解包脚本),而是需要使用者自己针对特定的包文件结构编写出特定的解包脚本,这就是所谓的QuickBMS技术门槛,对猛犸技术组来说,这个门槛的高度是零,技术组的真正门槛是分析和汇总各种各样的包文件结构并最终整合,但对于同学们来说,在已知包文件结构的前提下,如何编写出解包脚本是现阶段必须跨过的门槛。
在讨论怎样编写解包脚本之前,先让我们简单了解一下QuickBMS程序的术语或适用范围,如下所示:
1、编程技术并不是必须的,但是会编程能够让你更好的理解解包脚本的逻辑性。
2、解包过程并不需要知道全部的包文件格式,只要知道了文件名称、数据偏移和文件大小就可以解包。
3、解包脚本中的命令和变量名称大小写不敏感。
4、任何32位数值都是有符号的(-2147483648到2147483647),因此QuickBMS不能处理大于2GB的文件,但是能够在小于4GB的文件上进行查找,也就是说只能处理最大4GB的包文件和最大2GB的包内文件。
5、被称为常量的字符串(依赖于命令的上下文)可以处理成C语言中的字符串标记法,例如: "\x12\x34\\hello\"bye\0",这里的关键是C语言的转义字符或转义序列。
6、支持由0x开头的16进制数值,例如1234和0x4d2是相同的。
7、任何大于8位的操作都要受控于全局大小序,默认是小序,否则需要使用Endian命令来指定。
8、注释能够使用C语言的//和/* */,以及BMS语法#。
9、QuickBMS中打开的每个文件都有一个关联号,所有的命令都使用这个号码来存取文件,默认为0。
10、内存文件是临时文件的一种特殊类型,驻留在内存中,其工作类似于普通文件。
11、临时文件可代替存储在物理硬盘上的相同文件。
了解了这么多,那么我们该如何编写解包脚本呢?其实所谓的解包脚本就是一个文本文件,其内容描述了特定包文件的逻辑结构关系,QuickBMS本身为我们提供了20种数据类型和40条命令,用来方便我们编写解包脚本,这就像炒菜一样,油盐酱醋、蔬菜和肉类都放在那里了,你只要按照一定的顺序依次下锅,就会色香味俱全了。
下面的两个小节就分别列出QuickBMS提供的数据类型和命令(截止于0.4.6a版本),同学们可以细细体会一番,而后续教学笔记将使用具体的游戏实例来讲解这些数据类型和命令的使用方法以及如何分析出特定的包文件结构,这就是前言里面提到的拆除门槛的方法,必须要经过多个游戏实例的练习才能够有所掌握,希望同学们能够下定决心并坚持到底。
第二节 QuickBMS的数据类型
序号
|
类型名称
|
类型描述
|
1
|
BYTE
|
8位数值,0到0xff
|
2
|
SHORT
|
16位数值,0到0xffff
|
3
|
THREEBYTE
|
24位数值,0到0xffffff
|
4
|
LONG
|
32位数值,0到0xffffffff
|
5
|
LONGLONG
|
假的64位,所以数值是0到0xffffffff,但是却读取8个字节
|
6
|
STRING
|
空结尾字符串(每个字符一个字节)
|
7
|
ASIZE
|
特殊类型,用于返回打开文件的大小,仅用于GET命令
|
8
|
FILENAME
|
特殊类型,用于返回打开文件的名称,如:"myfile.zip",仅用于GET命令
|
9
|
BASENAME
|
特殊类型,用于返回打开文件的基本名称,如:"myfile",仅用于GET命令
|
10
|
EXTENSION
|
特殊类型,用于返回打开文件的扩展名,如:"zip",仅用于GET命令
|
11
|
UNICODE
|
特殊类型,用于统一码utf16字符串,utf16的大小序与脚本里的Endian命令设置相同,也可用于转换统一码字符串到ascii,如:Set ASCII_STRING UNICODE UNICODE_STRING,很显然这是假的utf16,所以仅对英文字符串有效
|
12
|
BINARY
|
特殊类型,用于C语言中的二进制字符串标记法,如:"\xff\x00\x12\x34",主要用于常量
|
13
|
COMPRESSED
|
特殊类型,用于设置大的字符串和内存文件仅使用少量的文本,要使用这个类型,你必须获得原始文本或文件,使用zlib进行压缩,然后使用base64对输出文件进行编码,例如:set MEMORY_FILE compressed eNrtwbEJACAMBMBecIfvnMUxPuEJAe0UHN81LLzrbYKwDOjI96IN1cLveRfAGqYu
|
14
|
LINE
|
特殊类型,用于处理回车换行为界定符的字符串(所以任何字符串都要使用0x00、 0x0a或0x0d结尾)
|
15
|
FULLNAME
|
文件的完整路径,实际上就是输入文件的完整路径
|
16
|
CURRENT_FOLDER
|
启动QuickBMS的路径
|
17
|
BMS_FOLDER
|
定位脚本的文件夹
|
18
|
OUTPUT_FOLDER
|
输出文件夹
|
19
|
INPUT_FOLDER
|
输入文件夹
|
20
|
ALLOC
|
仅用于Set命令的特殊情况
|
第三节 QuickBMS的命令
序号
|
命令及描述
|
1
|
quickbmsver VERSION
检查QuickBMS的当前版本是否可以支持解包脚本,这个命令很少使用。
参数:VERSION QuickBMS的版本号
|
2
|
clog NAME OFFSET ZSIZE SIZE [FILENUM]
提取文件并实时解压,本操作不影响输入文件的当前位置计数器,解压算法由ComType命令指定,提取的文件内容能够自动使用Encryption命令进行解密。
参数:
NAME 输出文件的名称
OFFSET 定位文件在资源中的位置
ZSIZE 资源中压缩的数据大小
SIZE 解压后的文件大小
FILENUM 与资源关联的文件号
|
3
|
findloc VAR TYPE STRING [FILENUM] [ERR_VALUE]
从资源的当前位置查找首次出现的字符串或数值,这个命令通常用于未知资源格式或特殊文本文件,很少使用
参数:
VAR 存储找到的位置
TYPE 能够是字符串、统一码或数值
STRING C语言的字符串标记法,依赖于TYPE参数
FILENUM与资源关联的文件号
ERR_VALUE 如果没有找到字符串,默认时FindLoc将终止解包脚本,但是若ERR_VALUE指定了值,那么这个值将被分配给VAR参数,从而不会终止解包脚本
|
4
|
for [VAR1] [OP] [VALUE] [COND] [VAR2]
...
next [VAR]
经典的带有初始化、条件和步进的"for"循环。在任何时候可使用break指令中断循环 (注意:break指令在多个for循环中并不直观,因为某些情况下会产生问题),next用来界定循环,同时进行步进。实际步骤是:将VALUE赋值给VAR1 (或执行一个数学操作);执行循环直到VAR1大于VAR1的限定条件;执行for和next之间的全部操作;增量VAR1
参数:
VAR1 初始化变量
OP 操作符
VALUE 赋给初始化变量的值
COND 退出循环条件
VAR2 条件的第二部分
|
5
|
get VAR TYPE [FILENUM]
从打开的文件读取字符串或数值,这是经常使用的命令
参数:
VAR 存储读取数据的变量
TYPE 请查看前面解释的类型描述
FILENUM 与资源关联的文件号
|
6
|
getdstring VAR LENGTH [FILENUM]
从文件或变量中读取定长数据,用于指定长度的文件名或其它字符串
参数:
VAR 存储读取数据的变量
LENGTH 读取的字节数
FILENUM 与资源关联的文件号
|
7
|
goto OFFSET [FILENUM] [TYPE]
转到文件的绝对位置
参数:
OFFSET 到达的位置,如果是负数则从文件末尾向前推,能够处理的范围是(0到 0xffffffff)
FILENUM 与资源关联的文件号
TYPE SEEK_SET (默认),SEEK_CUR,SEEK_END
|
8
|
idstring [FILENUM] STRING
如果文件当前位置处的签名与提供的字符串不符则终止解包脚本。
参数:
FILENUM与资源关联的文件号
STRING C语言的字符串标记法
|
9
|
log NAME OFFSET SIZE [FILENUM]
提取文件,本操作不影响输入文件的当前位置计数器,提取的文件内容能够自动使用Encryption命令进行解密。
参数:
NAME 输出文件名称
OFFSET 定位文件在资源中的位置
SIZE 提取的数据大小
FILENUM 与资源关联的文件号
|
10
|
math VAR1 OP VAR2
执行两个变量的数学操作,并将结果放置在第一个变量里,注意:为了兼容性考虑,全部的操作都默认使用有符号32位数值
参数:
VAR1 担当输入和输出变量
OP + 相加
* 相乘
/ 相除
- 相减
^ 异或
& 与
| 或
% 取模
! 非(0成为1,其他值成为0)
~ 补码(类似于异或0xffffffff)
< 左移
> 右移
l 左转
r 右转
s 字节交换
w 位交换
= 将VAR2赋值给VAR1
n 负数
a 绝对值
v 根
p 乘方
x 对齐,例如:var1=1, var2=16, result=16
var1=16, var2=16, result=16
var1=17, var2=16, result=32
z 公共位交换:var1=0xab, var2=4, result=0xba
var1=0xabcd, var2=4, result=0xdc
var1=0xabcd, var2=8, result=0xcdab
在OP的前面或后面添加u可以强制使用无符号操作
VAR2 其它的输入变量
|
11
|
open FOLDER NAME [FILENUM] [EXISTS]
打开文件以便进行读取
参数:
FOLDER 文件夹名称,FDDE表示NAME参数是输入文件夹里要打开的文件的扩展名,FDSE将认为NAME参数就是输入文件夹里要打开的文件名,"."表示当前输出文件夹
NAME 请看上面解释,NAME能够是?,这样QuickBMS将告之用户插入文件并手动打开,若NAME是"",将执行清空操作
FILENUM 与资源关联的文件号
EXISTS 若文件不存在,此变量将设置为0,否则为1,若文件不存在,QuickBMS默认使用一个错误进行终止
|
12
|
savepos VAR [FILENUM]
返回文件的当前位置
参数:
VAR 存储位置的变量
FILENUM 与资源关联的文件号
|
13
|
set VAR1 [TYPE] VAR2
将常量或变量赋值给其它变量,也可以在赋值时改变类型
参数:
VAR1 输出变量或内存文件
TYPE 没什么作用
VAR2 等待赋值的变量或常量
|
14
|
do
...
while VAR1 COND VAR2
一个不经常使用的循环类型,在循环的末尾执行条件检查,实际上很少使用。
参数:
VAR1 条件的第一部分
COND 条件
VAR2 条件的第二部分
|
15
|
string VAR1 OP VAR2
相当于math命令的字符串形式
参数:
VAR1 输入和输出变量
OP = 直接复制,如果VAR2是数值,则作为原始字符串,例子:var2="0x44434241", result="ABCD"
+ 将VAR2附加到VAR1的末尾
- 如果VAR2是正数,那么VAR1将从末尾截断指定的字节数;如果VAR2是负数,那么VAR1将从开头截断指定的字节数,否则将从VAR1中移除全部的VAR2
^ 使用VAR2异或VAR1(若较短则循环异或)
< 从VAR1的前面截断VAR2指定的字符数量,var1="thisisastring",var2="4", result="isastring"
% 截断头尾字符
& var1="thisisastring", var2="isa", result="isastring"
| var1="thisisastring", var2="isa", result="string"
$
!
> var1="thisisastring", var2="4", result="thisisast";var1="thisisastring", var2="isa", result="this"
b 字节转16进制数,var2="abc", result="616263"
B 字节转16进制数,但VAR2是一个空结尾字符串
h 16进制数转字节,var2="616263", result="abc"
e 基于Encryption命令的加密
E 基于Encryption命令的加密,但VAR2是一个空结尾字符串
c 基于ComType命令的压缩
C基于ComType命令的压缩,但VAR2是一个空结尾字符串
u var2="hello", result="HELLO"
l var2="HELLO", result="hello"
p
s
r 反转字符串
R 替换字符
VAR2 第二个变量或字符串
|
16
|
cleanexit
终止解包脚本
|
17
|
if VAR1 COND VAR2
...
[elif VAR1 COND VAR2]
...
[else]
...
endif
检查条件并当条件满足时执行必要的操作:
- If 是第一个条件
- Elif 是其它条件,能够是多个
- Else 没有条件匹配时执行的操作,一般放在最后
- EndIf 定界标记
参数:
VAR1 条件的第一部分
COND 可以是字符串和数值
< 小于
> 大于
!= 不等
== 相等
>= 大于等于
<= 小于等于
& 字符串:VAR1包含VAR2;数值:与
^ 字符串:等于;数值:异或
| 或
% 取模
/ 相除
<< 左移
>> 右移
! 取反
!! 若VAR2为非零值则为真
~ 补码
VAR2 条件的第二部分
|
18
|
getct VAR TYPE CHAR [FILENUM]
读取字符串直到CHAR表示的定界符
参数:
VAR 输出变量
TYPE 忽略了
CHAR 8位数值的定界字符
FILENUM 与资源关联的文件号
|
19
|
comtype ALGO [DICT]
指定clog命令使用的压缩算法
参数:
ALGO 各种压缩算法名称
DICT 可选的字典或与算法相关的参数
|
20
|
reverselong VAR
交换32位变量,例如:0x44332211交换为0x11223344
参数:
VAR 交换的变量
|
21
|
reverseshort VAR
交换16位变量,例如:0x2211交换为0x1122
参数:
VAR 交换的变量
|
22
|
endian TYPE
改变读写数据的全局大小序,默认为小序
参数:
TYPE 小序的0x11223344存储为44 33 22 11;大序的0x11223344存储为11 22 33 44
|
23
|
filexor SEQ
任何读取操作(get, *log 等等)都将执行异或
参数:
SEQ 8位数值序列,可以是:如0x12或由空格分开的字节序列如"0x12 0x34 0x56"或16进制字符串如"\x12\x34\x56";设置为0或""将禁止异或
|
24
|
filerot SEQ
与filexor命令类似,但执行的是求和操作,若SEQ为0x01且文件包含"hello",则变成 "ifmmp"
|
25
|
strlen VAR1 VAR2
计算VAR2的长度并存储在VAR1中
参数:
VAR1 存储长度的变量
VAR2 需要计算长度的变量
|
26
|
getvarchr VAR1 VAR2 OFFSET [TYPE]
定制字符串以便包含不同信息
参数:
VAR1 目的变量,包含读取的元素
VAR2 想要获取元素的变量或内存文件
OFFSET 指定VAR2中获取元素的位置
TYPE 默认为字节
|
27
|
putvarchr VAR1 OFFSET VAR2 [TYPE]
允许使用自定义规则执行写入的复杂操作
参数:
VAR1 想要放置元素的变量或内存文件
OFFSET 在指定位置放置元素
VAR2 包含想要写入的元素的变量
TYPE 默认为字节
|
28
|
debug
用于调试
|
29
|
padding VAR [FILENUM]
执行此命令将自动跳转到对齐后的数据位置,如果文件使用4字节对齐,当前位置是0x39,那么在使用padding 4之后,其位置将自动变为0x3c
参数:
VAR 对齐大小
FILENUM 与资源关联的文件号
|
30
|
append
在*log命令中使能附加模式,因此当输出文件名已经存在时将不会覆盖而是添加
|
31
|
encryption ALGO KEY [IVEC] [MODE] [KEYLEN]
为文件的读取操作设置一个解密算法,此命令仅工作于log 和clog命令。
参数:
ALGO 各种解密算法
KEY 类似于"\x11\x22\x33\x44"的键值
IVEC 用于增加解密算法的安全性
MODE 默认为0,表示解密,否则为1,表示加密
KEYLEN 强制键值的长度,主要是避免使用变量作为键值时出现问题(因为可能包含0)
|
32
|
print MESSAGE
打印字符串,若变量在两个%字符之间则打印变量的值
参数:
MESSAGE 字符串
|
33、34
|
getarray VAR1 ARRAY VAR2
putarray ARRAY VAR1 VAR2
使用动态数组来存储数据,类似于临时区或堆栈
|
35
|
callfunction NAME [KEEP_VAR]
startfunction NAME
...
endfunction
声明一个函数,请牢记规则:若KEEP_VAR为1,则函数结束时会保存变量的值,通常在解包脚本的后面放置函数
参数:
NAME 函数名称
KEEP_VAR 0 = 递归函数(默认)
1 = 正常函数,会改变变量
|
36
|
scandir PATH NAME SIZE [FILTER]
扫描文件,没什么用处,可以忽略
参数:
PATH 必须是".",代表当前文件夹
NAME 接收文件名称,可以是""
SIZE 接收文件大小,可以是-1
FILTER 过滤器,仅当指定-F参数时才有效
|
37
|
calldll DLLNAME FUNC/OFF CONV RET [ARG1] [ARG2] ... [ARGn]
本命令允许使用QuickBMS内部的插件,可用于存储在可执行文件或动态链接库中的自定义解压或解密函数
参数:
DLLNAME 动态链接库或可执行文件名称
FUNC/OFF 函数名称或相对位置偏移,请牢记相对地址不是绝对地址,而是与基地址有关
CONV 函数调用惯例:stdcall,cdecl,fastcall,borland,watcom,pascal,safecall,syscall,optlink,carion,thiscall
RET 函数返回值,""表示没有返回值
[ARGS] 函数的所有参数
|
38、39、40
|
put VAR TYPE [FILENUM]
putdstring VAR LENGTH [FILENUM]
putct VAR TYPE CHAR [FILENUM]
类似于get*命令,但执行的是写入操作
|