首页
社区
课程
招聘
Galgame汉化中的逆向(七):动态汉化分析2_以AZsystem引擎为例
发表于: 2023-1-21 10:56 21850

Galgame汉化中的逆向(七):动态汉化分析2_以AZsystem引擎为例

2023-1-21 10:56
21850

好久没发帖了,不知不觉又到了除夕,祝大家新年快乐~

by devseed, 本贴论坛和我的博客同时发布
本贴代码开源详见我的github: GalgameReverse, ReverseUtil

上篇链接:Galgame汉化中的逆向(六):动态汉化分析_以MAJIROv3引擎为例

上节 Galgame汉化中的逆向(六):动态汉化分析_以MAJIROv3引擎为例,我们介绍了动态汉化。动态汉化不用分析封包结构,不用分析opcode,看上去很方便,但是动态汉化解决同步问题会很麻烦,比如说改完文本后backlog文本仍是日文、返回主界面再载入文本没有变动等问题。动态汉化也有可能出现莫名其妙的崩溃bug,且这些bug不容易被调试。

针对动态汉化的上述缺点,本节我们将介绍一种这种半动态汉化的方案。与上节的方法不同,本节不进行文本级替换,而是文件级别的替换。即去hook相关函数,动态将解密后的缓冲区替换为我们汉化后的文件。适合于那种封包与加密特别麻烦或复杂的游戏。

本文将以azsystem为例,来分析:

lamune_chstest2

和上节相同,第一步先分析文件,无论静态分析算法还是动态dump缓冲区,先把文件提取出来。

由于方法差不多,这里不再详细展开了。

这个游戏封包为.arc文件,用文件长度哈希值来作为加密密钥,里面有若干个.asb脚本文件。IDA里面直接搜.asb字符串就能找到相关函数了,读取脚本文件函数如下:

lamune_loadscript

简单分析后,我们可以得到asb的文件头结构、校验文本函数、解压函数以下结论,具体如下:

提取只需要hooksub_40AB65,frida代码如下:

用其他工具如arc unpack可以得到arc封包的文件名,把文件名录入frida脚本,即可dump出全部asb脚本。

lamune_dumpasb

结合上面文件分析,我们可以在004311E1| E8 7F99FDFF| call lamune.40AB65| decompress进行inlinehook,在此直接加载我们已经解密并汉化的asb文件。解密的缓冲区是前面new出来的,我们还需要修改缓冲区大小。另外还要nop掉缓冲区crc校验的函数。

上节我们用了detours,这期我们来手动inlinehook,步骤如下:

在需要hook的位置用5字节call(E9)jmp(E8) 进行相对跳转到我们的函数上,

机器码为E8 XXXXXXXX, E9 XXXXXXXX

XXXXXXXX为相对于下一条指令的偏移,即targetva - (va + 5)

执行完后hook的函数后,结尾手动修复一下被我们修改5字节破坏的代码,跳转到下个指令处。

动态替换解密后的缓冲区脚本代码如下:

上面代码中load_rawasb即为我们读取对应解密文件的代码,这里为了减少零碎文件,我采取了从zip文件中读取的方法。

此处不再赘述,详见我的github

导入中文文本后,经测试发现一大堆半角乱码。

这是因为有sjis首字节字符编码范围检测,不在sjis范围内的字符将被解析为单字节文本。

lamune_sjis_error

与其他游戏不同,此游戏不是用cmp ax, 0x81等指令来检测sjis字符,而且位置过多过于分散,修改起来很麻烦。

这部分定位我们可以在TextOutA下断点,往上慢慢找,可以看到下图位置:

lamune_sjischeck

这里非常巧妙,用一条c^0x20 + 0x5f > 0x3B就可以判断是否为sjis首字符了,具体分析如下:

修改方法也很简单,把上面xoraddnoppatch,编码检测改为cmp dl, 0x80即可。

修改完后,虽然文本框正确了,但我们发现backlog中文本还有乱码。

这时候就要在搜索其他地方的检测字符函数了,可以试着搜cmp al|bl|cl|dl, 0x3b,逐个下断点,启动backlog看哪里断下。

lamune_backlog_error

lamune_checksjis_search

0nana.asb为例,这个opcode是对齐的,很工整,如下图:

lamune_script_decrypt

总结起来就是optype 4, oplengh 4, payload n结构,超长文本只需要修正一下oplenghjmp相关的指令就行了,如下:

将测试文本导入后,我们可以完成超长文本的汉化测试了~

lamune_chstest

这个游戏是通过Windows compatible DC进行绘图的,我们可以在CreateDIBinfo下断点,然后一层层往上跟,找到在缓冲区填充像素的函数,之后bitblt到帧缓冲位置。这里有个麻烦事,这游戏有很多虚函数通过虚表来寻址,如v3=(*(**v7+12))(*v7, v5, v10,a3这种。静态跟起来很费劲,可以尝试动态来看虚表。由于跟踪过于繁琐了,具体流程从略了,callback和具体调用流程如下:

上面我们来讲了一下定位方法,和整体加载流程。在这节我们来分析一下cpb文件如何读取和加载渲染到屏幕上的。

cpb中像素是分通道存储的,数据结构如下:

在渲染图片之前,游戏引擎先进行DC的初始化。

这部分是读取cpb到内存里,并检验文件头等信息

加载后,会根据通道数不同调用不同的解压缩函数。

这个游戏有多个cpb解压函数,对应着不同通道数的文件,这里以32位图为例分析。

注意这里vv1 = (*(*obja + 0xC))(obja)中的vv1值为prepare dc中的v5 = CreateDIBSection(0, &pbmi, 0, this + 0x28, 0, 0) 此句的DIB缓冲区。

我们可以替换decompress_channel_40AA38后的缓冲区为汉化后的图片,然后让游戏引擎帮我们复制到DIB缓冲区内。

解压各通道算法,看起来有点像lzss改版?

最后再通过bitblt到屏幕帧缓存中,至此整个游戏图片渲染分析完毕。

为了搞明白这个游戏游戏引擎图像如何渲染的,我把很多的虚函数都跟了一遍。

其实汉化图片只需要逆向到如何解压cpb文件那里就足够了。这个游戏麻烦地方在于不同通道对应的不同处理函数,要依次来hook替换缓冲区。另外在读取文件适合要记录一些文件名,用于缓冲区动态替换我们汉化的图片。

以24位图片代码替换为例,代码如下:

这里采取的是png格式存储的汉化图片,为了方便用了stb进行加载。

加载后遇到渲染bug,我们把对应缓冲区dump出来放到ct2中进行查看,确定原因。

lamune_frame_error

lamune_texture

这里发现原来是stbi_load_from_memory函数对于tga格式有些问题,换成png格式最后参数为0,问题解决。

lamune_picture_chs

至此,图片汉化问题全部解决。

这个游戏我逆向了一周多把引擎的加载方式搞明白了,之后又测试导入翻译断断续续修复bug一个月,基本上汉化完美了。这里有个坑,通关后没法打开gallary。这是官方的bug,下载了升级补丁可以修复。但是之前给我的文件是初版游戏,我说基于这个版本分析的。还得把旧版搬到新版上,非常麻烦。这个故事告诉我们,以后汉化要第一时间检查更新补丁。

整体来讲,这游戏有三大难点。难点之一在封包上,有加密和校验非常麻烦,因此我们采取了动态替换解密后的缓冲区;其二,图像缓冲区不好找,里面有大量虚函数,需要一点点跟;其三,sjis字符检测过于分散,需要手动一个个调整,而且也是用非主流方式判断的。因此,我认为此游戏比较适合半动态汉化。这种基于文件的替换方式可以免去复杂的封包,同时相比文本层面上的全动态汉化,可以更方便调试,少引发一些文本同步之类的问题。

另外我用stb加载图片,这里遇到了问题,xp上运行会崩溃。lamune_xp_error

调试定位在了mov eax, large fs:2Ch上,这是因为这个库用了__declspec(thread),在win xpLoadLibrary遇到tls就会崩,定义宏#define STBI_NO_THREAD_LOCALS即可解决。

然后进行了若干测试,我这个汉化兼容补丁性还不错~ win xp, win7, win8 , win10甚至连linux wineexagear都测试了,可以说是全平台兼容了~ 完结撒花~

lamune_exager

lamune_xp_iat

lamune_wine

 
 
 
 
 
 
int __thiscall sub_43112A(_DWORD *this, char *script_name)
{
  char *raw_data; // edi
  int v4; // eax
  unsigned int v5; // ecx
  _DWORD *v7[4]; // [esp+8h] [ebp-34h] BYREF
  int v8; // [esp+18h] [ebp-24h] BYREF
  unsigned int compressed_size; // [esp+1Ch] [ebp-20h]
  unsigned int raw_size; // [esp+20h] [ebp-1Ch]
  int v11; // [esp+24h] [ebp-18h]
  int (__thiscall **v12)(void *, char); // [esp+28h] [ebp-14h]
  char *compressed_data; // [esp+2Ch] [ebp-10h]
  int v14; // [esp+38h] [ebp-4h]
 
  v7[0] = off_460A6C;
  sub_40BD95(v7);
  v14 = 1;
  v12 = &off_462CDC;
  v11 = 0;
  sub_430FC9((int)this);
  if ( fopen_40C102(v7, script_name, 0x80000000) != 1 )
  {
    logprintf_407C41("CScript::Create", byte_4679CC, script_name);
    goto LABEL_13;
  }
  readfile_40C03E(v7, (char *)&v8, 0xC);
  if ( v8 == 0x1A425341 ) // asb\x1a
  {
    compressed_data = (char *)operator new(compressed_size);
    raw_data = (char *)operator new(raw_size);
    readfile_40C03E(v7, compressed_data, compressed_size);
    if ( sub_430F6A(compressed_data, compressed_size, raw_size) )
    {
      v4 = decompress_40AB65(compressed_data, compressed_size, raw_data, raw_size);// decompress
      v5 = raw_size;
      if ( v4 == raw_size )
      {
        this[4] = 0;
        this[1] = raw_data;
        this[2] = v5;
        this[3] = raw_data;
        this[5] = raw_data;
        v11 = 1;
LABEL_10:
        if ( compressed_data )
          j__free(compressed_data);
        goto LABEL_13;
      }
      logprintf_407C41("CScript::Create", byte_467A38, script_name);
    }
    else
    {
      logprintf_407C41("CScript::Create", byte_467A0C, script_name);// error
    }
    if ( raw_data )
      j__free(raw_data);
    goto LABEL_10;
  }
LABEL_13:
  v14 = -1;
  v12 = &off_462CDC;
  v7[0] = off_460A6C;
  sub_40BFDD(v7);
  return v11;
}
int __thiscall sub_43112A(_DWORD *this, char *script_name)
{
  char *raw_data; // edi
  int v4; // eax
  unsigned int v5; // ecx
  _DWORD *v7[4]; // [esp+8h] [ebp-34h] BYREF
  int v8; // [esp+18h] [ebp-24h] BYREF
  unsigned int compressed_size; // [esp+1Ch] [ebp-20h]
  unsigned int raw_size; // [esp+20h] [ebp-1Ch]
  int v11; // [esp+24h] [ebp-18h]
  int (__thiscall **v12)(void *, char); // [esp+28h] [ebp-14h]
  char *compressed_data; // [esp+2Ch] [ebp-10h]
  int v14; // [esp+38h] [ebp-4h]
 
  v7[0] = off_460A6C;
  sub_40BD95(v7);
  v14 = 1;
  v12 = &off_462CDC;
  v11 = 0;
  sub_430FC9((int)this);
  if ( fopen_40C102(v7, script_name, 0x80000000) != 1 )
  {
    logprintf_407C41("CScript::Create", byte_4679CC, script_name);
    goto LABEL_13;
  }
  readfile_40C03E(v7, (char *)&v8, 0xC);
  if ( v8 == 0x1A425341 ) // asb\x1a
  {
    compressed_data = (char *)operator new(compressed_size);
    raw_data = (char *)operator new(raw_size);
    readfile_40C03E(v7, compressed_data, compressed_size);
    if ( sub_430F6A(compressed_data, compressed_size, raw_size) )
    {
      v4 = decompress_40AB65(compressed_data, compressed_size, raw_data, raw_size);// decompress
      v5 = raw_size;
      if ( v4 == raw_size )
      {
        this[4] = 0;
        this[1] = raw_data;
        this[2] = v5;
        this[3] = raw_data;
        this[5] = raw_data;
        v11 = 1;
LABEL_10:
        if ( compressed_data )
          j__free(compressed_data);
        goto LABEL_13;
      }
      logprintf_407C41("CScript::Create", byte_467A38, script_name);
    }
    else
    {
      logprintf_407C41("CScript::Create", byte_467A0C, script_name);// error
    }
    if ( raw_data )
      j__free(raw_data);
    goto LABEL_10;
  }
LABEL_13:
  v14 = -1;
  v12 = &off_462CDC;
  v7[0] = off_460A6C;
  sub_40BFDD(v7);
  return v11;
}
 
typedef struct {
        s8 magic[4];                        /* "ASB" */
        u32 comprlen;
        u32 uncomprlen;
        u32 unknown;
} asb_header_t;
 
typedef struct {
        s8 magic[4];                        /* "ASB\x1a" 通过此magic来定位*/
        u32 comprlen;
        u32 uncomprlen;
} asb1a_header_t;
 
// CScript.constructor, 这里不再自己构造了,在游戏调用的时候记录下this指针
void *__thiscall sub_43277F(_DWORD *this)
 
// check_valid
BOOL __stdcall sub_430F6A(char *compressed_data, int compressed_size, int raw_size)
 
// decompress
sub_40AB65(char *compressed_data, int compressed_len, char *raw_data, int raw_len)
 
0043112A    | B8 9EE54500  | mov eax,lamune.45E59E |load_script(char* name)
 
004311D4    | FF75 E4  | push dword ptr ss:[ebp-1C]  | raw_len
004311D7    | 8D4D EC  | lea ecx,dword ptr ss:[ebp-14]
004311DA    | 57         | push edi  | raw_data
004311DB    | FF75 E0    | push dword ptr ss:[ebp-20] | compressed_len
004311DE    | FF75 F0    | push dword ptr ss:[ebp-10] | compressed_data
004311E1    | E8 7F99FDFF| call lamune.40AB65| decompress
typedef struct {
        s8 magic[4];                        /* "ASB" */
        u32 comprlen;
        u32 uncomprlen;
        u32 unknown;
} asb_header_t;
 
typedef struct {
        s8 magic[4];                        /* "ASB\x1a" 通过此magic来定位*/
        u32 comprlen;
        u32 uncomprlen;
} asb1a_header_t;
 
// CScript.constructor, 这里不再自己构造了,在游戏调用的时候记录下this指针
void *__thiscall sub_43277F(_DWORD *this)
 
// check_valid
BOOL __stdcall sub_430F6A(char *compressed_data, int compressed_size, int raw_size)
 
// decompress
sub_40AB65(char *compressed_data, int compressed_len, char *raw_data, int raw_len)
 
0043112A    | B8 9EE54500  | mov eax,lamune.45E59E |load_script(char* name)
 
004311D4    | FF75 E4  | push dword ptr ss:[ebp-1C]  | raw_len
004311D7    | 8D4D EC  | lea ecx,dword ptr ss:[ebp-14]
004311DA    | 57         | push edi  | raw_data
004311DB    | FF75 E0    | push dword ptr ss:[ebp-20] | compressed_len
004311DE    | FF75 F0    | push dword ptr ss:[ebp-10] | compressed_data
004311E1    | E8 7F99FDFF| call lamune.40AB65| decompress
/*
        for lamune.exe v1.0
        open the game to title, then
        frida -l lamune_hook.js -n lamune.exe
        next go to the prologue to dump all asbs
*/
function install_decompress_hook(outdir='./dump')
{
       // hook decompress function to dump
       const addr_decompress = ptr(0x40AB65);
       var raw_asbname = "";
       var raw_asbdata = ptr(0);
       var raw_asbsize = 0;
       Interceptor.attach(addr_decompress, {
           onEnter: function(args)
           {
               raw_asbdata = ptr(args[2]);
               raw_asbsize = args[3].toUInt32();
               raw_asbname = ptr(this.context.ebp).add(8).
                               readPointer().readAnsiString();
           },
           onLeave: function(retval)
           {
               //var asbname = asbname_buf.readAnsiString();
               var asbname = raw_asbname;
               console.log(asbname,
                   ", raw_asbdata addr at", raw_asbdata,
                   ", raw_asbsize ", raw_asbsize)
               try{
                   var fp = new File(outdir+"/"+asbname, 'wb');
                   fp.write(raw_asbdata.readByteArray(raw_asbsize));
                   fp.close();
               }
               catch(e)
               {
                   console.log("file error!", e);
               }
 
           }
       })
}
 
function dump_asbs(names, outdir="./dump")
{
    const addr_loadscript = ptr(0x43112A);
    const load_script = new NativeFunction(addr_loadscript, 
        'void', ['pointer', "pointer"], 'thiscall');
    console.log("load_script at:", load_script)
 
    // use this to store c++ context
    var pthis = ptr(0)
    Interceptor.attach(addr_loadscript, {
        onEnter: function(args)
        {
            pthis = ptr(this.context.ecx)
        }
    })
    install_decompress_hook(outdir)
 
    // wait for c++ context
    while(!pthis.toInt32())
    {
        Thread.sleep(0.2);
    }
 
    // dump all scripts
    var name_buf = Memory.alloc(0x100);
    for(var i=0;i<names.length;i++)
    {
        console.log("try to dump", names[i], ", this=",pthis);
        name_buf.writeAnsiString(names[i]);
        load_script(pthis, name_buf);
    }
    console.log("dump asbs finished!\n");
}
 
function dump_scenario()
{
    var names_v103 = ["00suzuk.asb"]
    dump_asbs(names_v103)
}
/*
        for lamune.exe v1.0
        open the game to title, then
        frida -l lamune_hook.js -n lamune.exe
        next go to the prologue to dump all asbs
*/
function install_decompress_hook(outdir='./dump')
{
       // hook decompress function to dump
       const addr_decompress = ptr(0x40AB65);
       var raw_asbname = "";
       var raw_asbdata = ptr(0);
       var raw_asbsize = 0;
       Interceptor.attach(addr_decompress, {
           onEnter: function(args)
           {
               raw_asbdata = ptr(args[2]);
               raw_asbsize = args[3].toUInt32();
               raw_asbname = ptr(this.context.ebp).add(8).
                               readPointer().readAnsiString();
           },
           onLeave: function(retval)
           {
               //var asbname = asbname_buf.readAnsiString();
               var asbname = raw_asbname;
               console.log(asbname,
                   ", raw_asbdata addr at", raw_asbdata,
                   ", raw_asbsize ", raw_asbsize)
               try{
                   var fp = new File(outdir+"/"+asbname, 'wb');
                   fp.write(raw_asbdata.readByteArray(raw_asbsize));
                   fp.close();
               }
               catch(e)
               {
                   console.log("file error!", e);
               }
 
           }
       })
}
 
function dump_asbs(names, outdir="./dump")
{
    const addr_loadscript = ptr(0x43112A);
    const load_script = new NativeFunction(addr_loadscript, 
        'void', ['pointer', "pointer"], 'thiscall');
    console.log("load_script at:", load_script)
 
    // use this to store c++ context
    var pthis = ptr(0)
    Interceptor.attach(addr_loadscript, {
        onEnter: function(args)
        {
            pthis = ptr(this.context.ecx)
        }
    })
    install_decompress_hook(outdir)
 
    // wait for c++ context
    while(!pthis.toInt32())
    {
        Thread.sleep(0.2);
    }
 
    // dump all scripts
    var name_buf = Memory.alloc(0x100);
    for(var i=0;i<names.length;i++)
    {
        console.log("try to dump", names[i], ", this=",pthis);
        name_buf.writeAnsiString(names[i]);
        load_script(pthis, name_buf);
    }
    console.log("dump asbs finished!\n");
}
 
function dump_scenario()
{
    var names_v103 = ["00suzuk.asb"]
    dump_asbs(names_v103)
}
 
 
/* for hook new decompressed buffer
0043119A   | FF75 E0   | push dword ptr ss:[ebp-20]
0043119D   | E8 A1510000  | call lamune.436343 | new
004311A2   | FF75 E4    | push dword ptr ss:[ebp-1C]  | [ebp-1c] raw_size
004311A5   | 8945 F0  | mov dword ptr ss:[ebp-10],eax
004311A8   | E8 96510000         | call lamune.436343  | new raw_buf
*/
const DWORD g_newrawbufi_4311A2 = 0x4311A2;
const DWORD g_newrawbufo_4311A8 = 0x4311A8;
 
/* for hook decompress asb
.text:004311D4 FF 75 E4          push    [ebp+raw_size]  ; raw_len
.text:004311D7 8D 4D EC          lea     ecx, [ebp+var_14]
.text:004311DA 57                push    edi             ; raw_data
.text:004311DB FF 75 E0    push [ebp+compressed_size] ; compressed_len
.text:004311DE FF 75 F0    push [ebp+compressed_data] ; compressed_data
.text:004311E1 E8 7F 99 FD FF    call    decompress_40AB65
*/
const DWORD g_decompressasbi_4311E1 = 0x4311E1;
const DWORD g_decompressasbo_40AB65 = 0x40AB65;
 
// inlinehook stubs
void __declspec(naked) newrawbuf_hook_4311A2()
{
    __asm{
        pushad;
        xor eax, eax;
        // size_t __stdcall load_rawasb(char *name, PBYTE buf)
        push eax;
        push [ebp+8];
        call load_rawasb;
        test eax, eax;
        je newrawbuf_hook_end;
        mov [ebp-0x1c], eax; // change raw buf size
        newrawbuf_hook_end:
        popad;
 
        // fix origin code
        push dword ptr [ebp-0x1c];
        mov dword ptr [ebp-0x10], eax;
        jmp dword ptr ds:[g_newrawbufo_4311A8];
    }
}
 
void __declspec(naked) decompressasb_hook_4311E1()
{
    //sub_40AB65(char *compressed_data, int compressed_len, char *raw_data, int raw_len)
    __asm {
        push [esp+0xc]; // after push ret addr, above, raw_buf
        push [ebp+0x8];  // asbname
        call load_rawasb;
        test eax, eax;
        je decompress_origin;
        ret 0x10;
        decompress_origin:
        mov eax, 0x99E15CB4; // this is the original corrent crc value
        mov dword ptr ds:[0x0047E718], eax; // this is not worked...
        jmp dword ptr ds:[g_decompressasbo_40AB65];
    }
}
 
// hook install functions
void install_asbhook()
{
    /* inlinehook check_valid
    .text:0040AB8A 6A 00             push    0
    .text:0040AB8C 8D 43 FC          lea     eax, [ebx-4]
    .text:0040AB8F 50                push    eax
    .text:0040AB90 8D 77 04          lea     esi, [edi+4]
    .text:0040AB93 56                push    esi
    .text:0040AB94 E8 27 D9 FF FF    call    makecrc_4084C0
    .text:0040AB99 83 C4 0C          add     esp, 0Ch
    .text:0040AB9C 39 07             cmp     [edi], eax
    .text:0040AB9E 75 64             jnz     short loc_40AC04
    */
    BYTE nop2[0x2]={0x90, 0x90};
    winhook_patchmemory((LPVOID)0x4311d2,
        nop2, sizeof(nop2));
    winhook_patchmemory((LPVOID)0x40AB9E,
        nop2, sizeof(nop2));
 
    // inlinehook newrawdata
    BYTE jmpE8buf[0x5]={0xE9}; // jmp relative
    *(DWORD*)(jmpE8buf+1) = (DWORD)newrawbuf_hook_4311A2- 
        ((DWORD)g_newrawbufi_4311A2 + sizeof(jmpE8buf));
    winhook_patchmemory((LPVOID)g_newrawbufi_4311A2,
        jmpE8buf, sizeof(jmpE8buf));
 
    // inlinehook decompress
    BYTE callE9buf[0x5]={0xE8}; // call relative
    *(DWORD*)(callE9buf+1) =(DWORD)decompressasb_hook_4311E1- 
        ((DWORD)g_decompressasbi_4311E1 + sizeof(jmpE8buf));
    winhook_patchmemory((LPVOID)g_decompressasbi_4311E1,
        callE9buf, sizeof(callE9buf));
}
/* for hook new decompressed buffer
0043119A   | FF75 E0   | push dword ptr ss:[ebp-20]
0043119D   | E8 A1510000  | call lamune.436343 | new
004311A2   | FF75 E4    | push dword ptr ss:[ebp-1C]  | [ebp-1c] raw_size
004311A5   | 8945 F0  | mov dword ptr ss:[ebp-10],eax
004311A8   | E8 96510000         | call lamune.436343  | new raw_buf
*/
const DWORD g_newrawbufi_4311A2 = 0x4311A2;
const DWORD g_newrawbufo_4311A8 = 0x4311A8;
 
/* for hook decompress asb
.text:004311D4 FF 75 E4          push    [ebp+raw_size]  ; raw_len
.text:004311D7 8D 4D EC          lea     ecx, [ebp+var_14]
.text:004311DA 57                push    edi             ; raw_data
.text:004311DB FF 75 E0    push [ebp+compressed_size] ; compressed_len
.text:004311DE FF 75 F0    push [ebp+compressed_data] ; compressed_data
.text:004311E1 E8 7F 99 FD FF    call    decompress_40AB65
*/
const DWORD g_decompressasbi_4311E1 = 0x4311E1;
const DWORD g_decompressasbo_40AB65 = 0x40AB65;
 
// inlinehook stubs
void __declspec(naked) newrawbuf_hook_4311A2()
{
    __asm{
        pushad;
        xor eax, eax;
        // size_t __stdcall load_rawasb(char *name, PBYTE buf)
        push eax;
        push [ebp+8];
        call load_rawasb;
        test eax, eax;
        je newrawbuf_hook_end;
        mov [ebp-0x1c], eax; // change raw buf size
        newrawbuf_hook_end:
        popad;
 
        // fix origin code
        push dword ptr [ebp-0x1c];
        mov dword ptr [ebp-0x10], eax;
        jmp dword ptr ds:[g_newrawbufo_4311A8];
    }
}
 
void __declspec(naked) decompressasb_hook_4311E1()
{
    //sub_40AB65(char *compressed_data, int compressed_len, char *raw_data, int raw_len)
    __asm {
        push [esp+0xc]; // after push ret addr, above, raw_buf
        push [ebp+0x8];  // asbname
        call load_rawasb;
        test eax, eax;
        je decompress_origin;
        ret 0x10;
        decompress_origin:
        mov eax, 0x99E15CB4; // this is the original corrent crc value
        mov dword ptr ds:[0x0047E718], eax; // this is not worked...
        jmp dword ptr ds:[g_decompressasbo_40AB65];
    }
}
 
// hook install functions
void install_asbhook()
{
    /* inlinehook check_valid
    .text:0040AB8A 6A 00             push    0
    .text:0040AB8C 8D 43 FC          lea     eax, [ebx-4]
    .text:0040AB8F 50                push    eax
    .text:0040AB90 8D 77 04          lea     esi, [edi+4]
    .text:0040AB93 56                push    esi
    .text:0040AB94 E8 27 D9 FF FF    call    makecrc_4084C0
    .text:0040AB99 83 C4 0C          add     esp, 0Ch
    .text:0040AB9C 39 07             cmp     [edi], eax
    .text:0040AB9E 75 64             jnz     short loc_40AC04
    */
    BYTE nop2[0x2]={0x90, 0x90};
    winhook_patchmemory((LPVOID)0x4311d2,
        nop2, sizeof(nop2));
    winhook_patchmemory((LPVOID)0x40AB9E,
        nop2, sizeof(nop2));
 
    // inlinehook newrawdata
    BYTE jmpE8buf[0x5]={0xE9}; // jmp relative
    *(DWORD*)(jmpE8buf+1) = (DWORD)newrawbuf_hook_4311A2- 
        ((DWORD)g_newrawbufi_4311A2 + sizeof(jmpE8buf));
    winhook_patchmemory((LPVOID)g_newrawbufi_4311A2,
        jmpE8buf, sizeof(jmpE8buf));
 
    // inlinehook decompress
    BYTE callE9buf[0x5]={0xE8}; // call relative
    *(DWORD*)(callE9buf+1) =(DWORD)decompressasb_hook_4311E1- 
        ((DWORD)g_decompressasbi_4311E1 + sizeof(jmpE8buf));
    winhook_patchmemory((LPVOID)g_decompressasbi_4311E1,
        callE9buf, sizeof(callE9buf));
}
 
 
 
 
 
 
 
.text:004340F6 loc_4340F6:
.text:004340F6 mov     ecx, [ebp+74h+var_4]
.text:004340F9 mov     cl, [ecx]
.text:004340FB mov     dl, cl
.text:004340FD xor     dl, 20h
.text:00434100 add     dl, 5Fh ; '_'
.text:00434103 cmp     dl, 3Bh ; ';'
.text:00434106 ja      loc_434215
.text:004340F6 loc_4340F6:
.text:004340F6 mov     ecx, [ebp+74h+var_4]
.text:004340F9 mov     cl, [ecx]
.text:004340FB mov     dl, cl
.text:004340FD xor     dl, 20h
.text:00434100 add     dl, 5Fh ; '_'
.text:00434103 cmp     dl, 3Bh ; ';'
.text:00434106 ja      loc_434215
 
 
 
 
 
 
// from the file start, there are several opcodes entries
optype 4, oplengh 4, payload n
 
[26|27 00 00 00], oplengh 4, [00]*0x10, optext // 26 music, 27 text
[0d 00 00 00], oplengh 4, [00]*4, option_num 4, [00] * 8, text1, 00, text2 ... // option
[0a 00 00 00], [18 00 00 00] , addr 4, [00]*4, unknow1 4, unknow2 4 // jmp
[0b 00 00 00], [1c 00 00 00] , addr 4, [00]*4, unknow1 4, unknow2 4 // option jmp
 
00 00 00 00 FF FF FF FF FF FF FF FF 00 00 00 00
00 00 00 00 00 00 00 00 // end with that
// from the file start, there are several opcodes entries
optype 4, oplengh 4, payload n
 
[26|27 00 00 00], oplengh 4, [00]*0x10, optext // 26 music, 27 text
[0d 00 00 00], oplengh 4, [00]*4, option_num 4, [00] * 8, text1, 00, text2 ... // option
[0a 00 00 00], [18 00 00 00] , addr 4, [00]*4, unknow1 4, unknow2 4 // jmp
[0b 00 00 00], [1c 00 00 00] , addr 4, [00]*4, unknow1 4, unknow2 4 // option jmp
 
00 00 00 00 FF FF FF FF FF FF FF FF 00 00 00 00
00 00 00 00 00 00 00 00 // end with that
 
0019FD80  0040EF75            50        return to lamune.sub_40EE6F+106 from ???           User // CreateDIBinfo
0019FDD0  00401E77  0040EE6F  34        return to lamune.sub_401D0F+168 from lamune.sub_40 User
0019FE04  0040955D            24        return to lamune.sub_40951B+42 from ???            User
0019FE28  0040686C  0040951B  24        return to lamune.sub_406813+59 from lamune.sub_409 User
0019FE4C  004383EA  00406813  A4        return to lamune.EntryPoint+184 from lamune.sub_40 User
0019FEF0  0043827E  0043F210  84        return to lamune.EntryPoint+18 from lamune.sub_43F User
 
DWORD __thiscall sub_42A199(int *this) // neko_logo.cpb
| loadimg_419E03(off_473088, "neko_logo.cpb", this + 0x214);
  | readcpb_40C03E((_DWORD **)this + 1, cpb_header, 0x10);
  | (*(_DWORD *)*v7+4))(*v7, cpb_header) //check magic cpb\x1a
  | (*(v8 + 0x3C))(v10[4], v10[5])   // 0041D3FB, read full
  | v3=(*(**v7+12))(*v7, v5, v10,a3);// 0041D453, check depth
    |return (*(*v6 + 0x10))(v6, a2, a4);// 0041E36F, 0041ddb8,decompress
          |decompress2_40AA38(char *compressed_buf, size_t |compressed_size, char *raw_buf, size_t raw_len) // lzss?
| sub_40C9C1(DWORD *this, int a2, int a3, int *a4, DWORD *a5)
  |sub_4101EB(v9 + 2, a2, a3, a4, *a5, a5[1], a5[2], a5[3], 0);// bltalpha
  |(*(this[2] + 0x48))(this + 2, a2, a3, a4, *a5, a5[1], a5[2], a5[3], 0xCC0020);// 004123E1, to bitblt
0019FD80  0040EF75            50        return to lamune.sub_40EE6F+106 from ???           User // CreateDIBinfo
0019FDD0  00401E77  0040EE6F  34        return to lamune.sub_401D0F+168 from lamune.sub_40 User
0019FE04  0040955D            24        return to lamune.sub_40951B+42 from ???            User
0019FE28  0040686C  0040951B  24        return to lamune.sub_406813+59 from lamune.sub_409 User
0019FE4C  004383EA  00406813  A4        return to lamune.EntryPoint+184 from lamune.sub_40 User
0019FEF0  0043827E  0043F210  84        return to lamune.EntryPoint+18 from lamune.sub_43F User
 
DWORD __thiscall sub_42A199(int *this) // neko_logo.cpb
| loadimg_419E03(off_473088, "neko_logo.cpb", this + 0x214);
  | readcpb_40C03E((_DWORD **)this + 1, cpb_header, 0x10);
  | (*(_DWORD *)*v7+4))(*v7, cpb_header) //check magic cpb\x1a
  | (*(v8 + 0x3C))(v10[4], v10[5])   // 0041D3FB, read full
  | v3=(*(**v7+12))(*v7, v5, v10,a3);// 0041D453, check depth
    |return (*(*v6 + 0x10))(v6, a2, a4);// 0041E36F, 0041ddb8,decompress
          |decompress2_40AA38(char *compressed_buf, size_t |compressed_size, char *raw_buf, size_t raw_len) // lzss?
| sub_40C9C1(DWORD *this, int a2, int a3, int *a4, DWORD *a5)
  |sub_4101EB(v9 + 2, a2, a3, a4, *a5, a5[1], a5[2], a5[3], 0);// bltalpha
  |(*(this[2] + 0x48))(this + 2, a2, a3, a4, *a5, a5[1], a5[2], a5[3], 0xCC0020);// 004123E1, to bitblt
00000000 cpb1a_header_t  struc ; (sizeof=0x20, mappedto_128)
00000000 ; XREF: decompresscpb_41E36F/r
00000000 magic           db 4 dup(?) ; string(C)
00000004 unknow1         db ?
00000005 color_depth     db ?
00000006 unknow2         db ?
00000007 version         db ?
00000008 width           dw ? ; XREF: decompresscpb_41E36F+39/r
0000000A height          dw ? ; XREF: decompresscpb_41E36F+3E/r
0000000C max_comprlen    dd ? ; XREF: decompresscpb_41E36F+56/r
00000010 comprlen dd 4 dup(?); XREF: decompresscpb_41E36F+93/r
00000010  ; decompresscpb_41E36F+B7/r ...
00000020 cpb1a_header_t  ends
00000000 cpb1a_header_t  struc ; (sizeof=0x20, mappedto_128)
00000000 ; XREF: decompresscpb_41E36F/r
00000000 magic           db 4 dup(?) ; string(C)
00000004 unknow1         db ?
00000005 color_depth     db ?
00000006 unknow2         db ?
00000007 version         db ?
00000008 width           dw ? ; XREF: decompresscpb_41E36F+39/r
0000000A height          dw ? ; XREF: decompresscpb_41E36F+3E/r
0000000C max_comprlen    dd ? ; XREF: decompresscpb_41E36F+56/r
00000010 comprlen dd 4 dup(?); XREF: decompresscpb_41E36F+93/r
00000010  ; decompresscpb_41E36F+B7/r ...
00000020 cpb1a_header_t  ends
void *__thiscall sub_40FDC2(void **this, LONG width, int height)
{
  void *result; // eax
  HBITMAP v5; // eax
  HDC dc; // eax
  int (__thiscall **v7)(void **, _DWORD); // eax
  BITMAPINFO pbmi; // [esp+8h] [ebp-2Ch] BYREF
 
  if ( (void *)width == this[41] && (void *)height == this[42] )
    return (void *)(*((int (__thiscall **)(void **, _DWORD))*this + 26))(this, 0);
  (*((void (__thiscall **)(void **))*this + 13))(this);
  if ( width > 0 && height > 0 )
  {
    memset(&pbmi, 0, sizeof(pbmi));
    pbmi.bmiHeader.biHeight = -height;
    pbmi.bmiHeader.biSize = 0x28;// struct size
    pbmi.bmiHeader.biWidth = width;
    pbmi.bmiHeader.biPlanes = 1// must be 1
    pbmi.bmiHeader.biBitCount = 32;    // rgba
    pbmi.bmiHeader.biCompression = 0;
    v5 = CreateDIBSection(0, &pbmi, 0, this + 0x28, 0, 0); // this+0x28
    this[37] = v5;
    if ( v5 )
    {
      dc = CreateCompatibleDC(0);
      this[0x27] = dc;
      if ( dc )
      {
        this[0x26] = SelectObject(dc, this[0x25]);
        this[0x29] = (void *)width;
        this[0x2A] = (void *)height;
        this[0x2E] = (void *)(height - 1);
        v7 = (int (__thiscall **)(void **, _DWORD))*this;
        this[0x2B] = 0;
        this[0x2C] = 0;
        this[0x2D] = (void *)(width - 1);
        result = (void *)v7[0x1A](this, 0); // 0041295A, FillRect
        this[0x52] = result;
        return result;
      }
    }
    (*((void (__thiscall **)(void **))*this + 0xD))(this);
  }
  return 0;
}
void *__thiscall sub_40FDC2(void **this, LONG width, int height)
{
  void *result; // eax
  HBITMAP v5; // eax
  HDC dc; // eax
  int (__thiscall **v7)(void **, _DWORD); // eax
  BITMAPINFO pbmi; // [esp+8h] [ebp-2Ch] BYREF
 
  if ( (void *)width == this[41] && (void *)height == this[42] )
    return (void *)(*((int (__thiscall **)(void **, _DWORD))*this + 26))(this, 0);
  (*((void (__thiscall **)(void **))*this + 13))(this);
  if ( width > 0 && height > 0 )
  {
    memset(&pbmi, 0, sizeof(pbmi));
    pbmi.bmiHeader.biHeight = -height;
    pbmi.bmiHeader.biSize = 0x28;// struct size
    pbmi.bmiHeader.biWidth = width;
    pbmi.bmiHeader.biPlanes = 1// must be 1
    pbmi.bmiHeader.biBitCount = 32;    // rgba
    pbmi.bmiHeader.biCompression = 0;
    v5 = CreateDIBSection(0, &pbmi, 0, this + 0x28, 0, 0); // this+0x28
    this[37] = v5;
    if ( v5 )
    {
      dc = CreateCompatibleDC(0);
      this[0x27] = dc;
      if ( dc )
      {
        this[0x26] = SelectObject(dc, this[0x25]);
        this[0x29] = (void *)width;
        this[0x2A] = (void *)height;
        this[0x2E] = (void *)(height - 1);
        v7 = (int (__thiscall **)(void **, _DWORD))*this;
        this[0x2B] = 0;
        this[0x2C] = 0;
        this[0x2D] = (void *)(width - 1);
        result = (void *)v7[0x1A](this, 0); // 0041295A, FillRect

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 8
支持
分享
最新回复 (4)
雪    币: 562
活跃值: (4200)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
什么? 这年头还有xp
2023-1-21 15:56
0
雪    币: 6296
活跃值: (4962)
能力值: ( LV12,RANK:250 )
在线值:
发帖
回帖
粉丝
3
DirtyAngle 什么? 这年头还有xp
大家都喜欢用xp来玩galgame呀~
2023-1-25 17:48
0
雪    币: 5029
活跃值: (4798)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
4
感谢分享!
2023-1-28 10:20
0
雪    币: 268
活跃值: (2367)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
还得是你们二次元啊
2023-3-16 11:55
0
游客
登录 | 注册 方可回帖
返回
//