[YU-RIS] 编码范围

[YU-RIS] 编码范围

0x00 背景

对于yuris这个引擎,编码的检验范围和这种经典的范围校验是不一样的

00406896  | 85C9                  | test ecx,ecx
00406898  | 74 1D                 | je ハナヒメ*アブソリュート!.4068B7
0040689A  | 8B10                  | mov edx,dword ptr ds:[eax]
0040689C  | 8A4411 FF             | mov al,byte ptr ds:[ecx+edx-1]
004068A0  | 3C 81                 | cmp al,81
004068A2  | 72 04                 | jb ハナヒメ*アブソリュート!.4068A8
004068A4  | 3C 9F                 | cmp al,9F
004068A6  | 76 08                 | jbe ハナヒメ*アブソリュート!.4068B0
004068A8  | 3C E0                 | cmp al,E0
004068AA  | 72 0B                 | jb ハナヒメ*アブソリュート!.4068B7
004068AC  | 3C EF                 | cmp al,EF
004068AE  | 77 07                 | ja ハナヒメ*アブソリュート!.4068B7
004068B0  | BB 5C000000           | mov ebx,5C
004068B5  | EB 7F                 | jmp ハナヒメ*アブソリュート!.406936
004068B7  | 8B46 38               | mov eax,dword ptr ds:[esi+38]
004068BA  | 8B08                  | mov ecx,dword ptr ds:[eax]
004068BC  | 8D51 FF               | lea edx,dword ptr ds:[ecx-1]
004068BF  | 8910                  | mov dword ptr ds:[eax],edx
004068C1  | 85C9                  | test ecx,ecx

尽管在yuris的游戏中你可以搜索到上述几乎相同的校验代码

但那个并不是拿来校验游戏里显示的文本的。

0x01 理解校验原理

0x81,0x9F,0xE0,0xEF如果你搜索SJIS编码范围

不难发现这就是SJIS第一个字节的编码范围(自定义区就不去管他了)

也就是说其实这个校验范围只是验证了第一个字节。

SJIS:第一个字节的范围 0×81-0×9F、0xE0-0xEF
GBK:第一个字节的范围 0×81-0xFE

这边就取常见的范围,一般游戏趋向于取这种范围
其实还有自定义字符的范围,SJIS还能再大一些
有些游戏SJIS可能是0x80-0xA0、0xE0-0xFC
反正大致是这个范围,我们不去纠结这多少一点。

如果你有仔细阅读我之前的文章
应该可以知道,ASCII和SJIS的关系
为什么第一个字节不是从0x00开始的原因

这个范围的作用也很明显,比如一堆日文里夹杂了一个英文字符
大家肯定知道,日文字符用的是两个字节编码的,而英文字符是一个(讨论窄字节)
有了这个范围可以正常解析,如果是机械的两个两个字节解析成一个字符
那到了那个英文字符后面肯定就全部错位了

0x83, 0x6E, 0x83, 0x69, 0x83, 0x71, 0x83, 0x81 //SJIS
ハナヒメ
0x83, 0x6E, 0x83, 0x69, 0x41, 0x83, 0x71, 0x83, 0x81 //SJIS
ハナAヒメ

还有些时候是用来过滤一些显示不出来的字符
可以看到SJIS在9F到E0断了一大截,
如果你写的中文字符恰好在这个断掉的范围内,自然是会被过滤掉的
在一些引擎里表现为只能显示出几个汉字,或干脆直接没字了。

0x02 另一种校验方式的实现

除了上面那种一堆cmp的实现方法,还有另一种校验方式的实现

不管你能不能看懂最开头那段汇编代码
究其本质就是在字符串中一个个字节取出来比对
判断取出来的这个字节是不是在0×81-0×9F、0xE0-0xEF的范围内

假设我有一个函数

bool is_SJIS(unsigned char X)
{
	//这里就不写了,不重要
}

如果输入的 X 是在 0×81-0×9F、0xE0-0xEF的范围内 那么返回true,反之。

我们知道,一个字节的范围是 0x00-0xFF
那么也就是说这个函数的输入值,也就是 X 只可能是 0x00-0xFF
如果SJIS的范围是确定的,那么 X 的每一个输入的值都可以是确定的。

说白了,如果都是确定的,那么我们可以直接计算出结果来
然后保存成一张表,下次直接查表即可,不用再次计算了。

就像是99乘法表,AxB=C
A{0 - 9}, B{0 - 9} 即A和B的值是有限且确定的,所以C的值也是有限且确定的
我们背了99乘法表,就是把所有可能结果都记住了,下次就不用去算了。

对此,我们可以引入一张is_SJIS的表

unsigned char is_SJIS[] = 
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00
};

如果以0为基准,你从左往右数0x82个字节,可以发现是0x1
即图中0ffset0x80那一行的第二。
而且从这里开始一排都是01,数到这一堆01结束的地方是0x9F
以此类推,下面那一块01是E0开始FC结束

如果以数组的下标来访问

is_SJIS[0x82] -> 01 -> true -> SJIS
is_SJIS[0x87] -> 01 -> true -> SJIS
is_SJIS[0xC6] -> 00 -> false -> No SJIS
is_SJIS[0x07] -> 00 -> false -> No SJIS

那么这时候就相当于把一堆cmp转换成了查表操作
也就是手算 一位数x一位数 乘法变为了背诵乘法表
结果都是一样的,速度反而更快。(没有人会再手算一位数乘法吧?)

0x03 制作一张GBK范围校验表

明白了上述的原理,我们也可以来制作一张GBK范围的校验表

先来复习一下GBK的范围

0×81-0xFE

那么也就是说,我们的表,大小为0x100
且从第0x81个字节开始,一直写01直到第0xFE个字节

为了偷懒,我们直接在原来的SJIS表上改改

unsigned char is_GBK[] = 
{
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
	0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00
};

0x04 丧心病狂

其实如果你写过C语言,并且有一定的基础和了解
if( ?) -> 这个?只要不是0其实都相当于true

所以这样写也是完全OK的

当然如果是这样的当我没说 if(charX == 1)

0x05 修改游戏程序

这里就不用说了吧

直接在游戏exe搜索那张表就行了。

0x06 一点拓展

其实在编码转换上也是可以有同样的道理的

比如GBK->UTF16

每一个GBK编码的字符都有唯一确定的UTF16编码的字符(其实不是,但大部分是)
为此我们可以生成一张表,通过查表来进行编码的转换

SJIS->UTF16同理

posted @ 2022-11-17 23:46  Dir-A  阅读(292)  评论(0编辑  收藏  举报