红楼梦十二金钗游戏资源分析(十一)

终于,今天我们来到了读取的循环

目录

1-8 是之前的分析,已告一段落,从 9 开始(算)是重置:

  1. 背景、简单分析
  2. 显存位置
  3. 事件图保存算法: LZW
  4. 调色板
  5. MGP2
  6. 结局图
  7. 事件图中的眼睛
  8. 音频文件
  9. 按位读取
  10. 循环之前
  11. 读取循环
  12. 重现 LZW
  13. PAT 的图形格式

循环

while (local_6 != END_MARK_101) {
    FUN_1048_0e02();
    iVar3 = FUN_1048_0e3f();
    if (iVar3 != local_12) {
      (*DAT_1050_25e6)(0x1048,iVar3);
    }
    if (local_6 == RESET_MARK_100) {
      init_dict();
      local_4 = load_bits_by_len();
      convert_to_pix(local_4);
    }
    else {
      if (LAST_DICT_INDEX < local_6) {
        uVar1 = FUN_1018_3be7(local_4);
      }
      else {
        uVar1 = FUN_1018_3be7(local_6);
      }
      FUN_1018_3cdc(local_4,uVar1);
      convert_to_pix(local_6);
      local_4 = local_6;
      cVar2 = FUN_1018_3bc3();
      if (cVar2 != '\0') {
        CURRENT_BIT_LEN = CURRENT_BIT_LEN + '\x01';
        CURRENT_DICT_LEN = CURRENT_DICT_LEN << 1;
      }
    }
    local_6 = load_bits_by_len();
    local_12 = iVar3;
  }

使用最长的 if else 大致可以分为三块:if else 前,if elseif else 后。

if else

包含 两个函数,一个函数指针

FUN_1048_0e02

int __cdecl16far FUN_1048_0e02(void)
{
  int in_AX;
  int in_CX;
  
  if (1 < CPU_TYPE) {
    return in_AX * in_CX;
  }
  return in_AX * in_CX;
}

汇编很复杂,但是 ghidra 认为只是计算了两个寄存器的积,我们看看 cpu log,看看里面放了什么:

0207:00003E3D  call 0277:0E02                                         EAX:00000002 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005748 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1
0277:00000E02  cmp  byte [1470],02             ds:[00001470]=0003     EAX:00000002 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1
0277:00000E07  jc   00000E24 ($+1b)            (no jmp)               EAX:00000002 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0277:00000E09  shl  eax,10                                            EAX:00000002 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0277:00000E0D  shrd eax,edx,10                                        EAX:00020000 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:1 PF:1 IF:1
0277:00000E12  shl  ecx,10                                            EAX:00000002 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0277:00000E16  shrd ecx,ebx,10                                        EAX:00000002 EBX:00010000 ECX:00640000 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:1 PF:1 IF:1
0277:00000E1B  imul ecx                                               EAX:00000002 EBX:00010000 ECX:00000064 EDX:00640000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0277:00000E1E  shld edx,eax,10                                        EAX:000000C8 EBX:00010000 ECX:00000064 EDX:00000000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0277:00000E23  retf                                                   EAX:000000C8 EBX:00010000 ECX:00000064 EDX:00000000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005744 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:1 SF:0 OF:0 AF:0 PF:1 IF:1

似乎也就是 AX 乘了一个 64h,那似乎也真是没什么用,我们看下一个函数。

FUN_1048_0e3f

int __cdecl16far FUN_1048_0e3f(void)

{
	...  
  if (1 < CPU_TYPE) {
    if (CONCAT22(in_BX,in_CX) != 0) {
      return (int)(CONCAT22(in_DX,in_AX) / CONCAT22(in_BX,in_CX));
    }
LAB_1048_0eb8:
    iVar3 = FUN_1048_026d();
    return iVar3;
  }
  ...
}

虽然很长,但是根据寄存器和CPU 类型,其实也没什么看的。如果寄存器 BC 不为 0,就返回一个 DA/BC 的值,由于 BC 一直传入的是一个内存地址,基本不会为 0,所以这个看上去是一个偏移量的计算。每当偏移量发生变化时,它会去执行这个操作 (*DAT_1050_25e6)(0x1048,iVar3),但是这个地方 ghidra 看不到,不过我们还是有 cpu log:

0207:00003E5B  call far word [25E6]            ds:[000025E6]=3F04     EAX:0000002A EBX:00010000 ECX:00000008 EDX:00080000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005746 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0207:00003F04  enter 0038,00                                          EAX:0000002A EBX:00010000 ECX:00000008 EDX:00080000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005742 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0207:00003F08  leave                                                  EAX:0000002A EBX:00010000 ECX:00000008 EDX:00080000 ESI:00000070 EDI:00002608 EBP:00005740 ESP:00005708 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1
0207:00003F09  retf 0002                                              EAX:0000002A EBX:00010000 ECX:00000008 EDX:00080000 ESI:00000070 EDI:00002608 EBP:00005758 ESP:00005742 DS:0287 ES:0287 FS:0000 GS:0000 SS:028F CF:0 ZF:0 SF:0 OF:0 AF:0 PF:0 IF:1

感觉什么都没有做,我们也先忽略好了。

这么一来 if else 之前,感觉也没做什么值得我们注意的事情。

if else

这应该就是重中之重了。

if

if (local_6 == RESET_MARK_100) {
  init_dict();
  local_4 = load_bits_by_len();
  convert_to_pix(local_4);
}

如果读到了重设标记,则初始化字典,重读一个索引,没什么好说的。

else

else {
  if (LAST_DICT_INDEX < local_6) {
    uVar1 = FUN_1018_3be7(local_4);
  }
  else {
    uVar1 = FUN_1018_3be7(local_6);
  }
  FUN_1018_3cdc(local_4,uVar1);
  convert_to_pix(local_6);
  local_4 = local_6;
  cVar2 = FUN_1018_3bc3();
  if (cVar2 != '\0') {
    CURRENT_BIT_LEN = CURRENT_BIT_LEN + '\x01';
    CURRENT_DICT_LEN = CURRENT_DICT_LEN << 1;
  }
}

我们有几个函数要去看,首先是 FUN_1018_3be7

  • FUN_1018_3be7

    undefined2 __stdcall16far FUN_1018_3be7(int param_1)
    {
      int iVar1;
      undefined2 uVar2;
    
      while (uVar2 = (undefined2)((ulong)DICT_START >> 0x10), iVar1 = (int)DICT_START,
            *(int *)(iVar1 + param_1 * 4) != 0x7fff) {
        param_1 = *(int *)(iVar1 + param_1 * 4);
      }
    
      return CONCAT11((char)((uint)(param_1 * 4) >> 8),*(undefined *)(iVar1 + param_1 * 4 + 2));
    }
    

    这似乎是,根据传入参数,去取一个索引,如果取出的索引是另一个索引,那么继续按新的索引去取,直到取出一个颜色,退出循环。

  • FUN_1018_3cdc

    void __stdcall16far FUN_1018_3cdc(undefined2 param_1,undefined2 param_2)
    {
      undefined2 *puVar1;
      undefined2 uVar2;
    
      LAST_DICT_INDEX = LAST_DICT_INDEX + 1;
      uVar2 = (undefined2)((ulong)DICT_START >> 0x10);
      puVar1 = (undefined2 *)((int)DICT_START + LAST_DICT_INDEX * 4);
      *puVar1 = param_1;
      puVar1[1] = param_2;
      return;
    }
    

    这个显然就是更新索引了。把上次的索引,和这次取到的颜色,写入字典。

  • FUN_1018_3bc3

    undefined2 __cdecl16far FUN_1018_3bc3(void)
    {
      undefined local_3;
    
      if ((CURRENT_DICT_LEN + -1 == LAST_DICT_INDEX) && (CURRENT_BIT_LEN != '\f')) {
        local_3 = 1;
      }
      else {
        local_3 = 0;
      }
      return CONCAT11((char)((uint)(CURRENT_DICT_LEN + -1) >> 8),local_3);
    }
    

    当字典索引没有到达字典长度减一时,返回 0。而我们根据 后续的判断,不为 0 时,字典的长度会进行扩容。

if else

其实这段没什么了,读取一个索引,保存一下偏移量。 整个循环在没有读到结束标记时会一直继续下去。

总结

感觉我们可以开始重写程序了。整理一下主要内容:

init_dict();
load_bits_by_len();
last_index = load_bits_by_len();
convert_to_pix(last_index);
current_index = load_bits_by_len();
while (current_index != END_MARK_101) {
  if (current_index == RESET_MARK_100) {
    init_dict();
    last_index = load_bits_by_len();
    convert_to_pix(last_index);
  }
  else {
    if (LAST_DICT_INDEX < current_index) {
      uVar1 = get_color_by_index(last_index);
    }
    else {
      uVar1 = get_color_by_index(current_index);
    }
    add_dict_index(last_index,uVar1);
    convert_to_pix(current_index);
    last_index = current_index;
    cVar2 = dict_len_check();
    if (cVar2 != '\0') {
      CURRENT_BIT_LEN = CURRENT_BIT_LEN + '\x01';
      CURRENT_DICT_LEN = CURRENT_DICT_LEN << 1;
    }
  }
  current_index = load_bits_by_len();
}

感觉不难对不对,其实我们也花了不少时间,下一篇,我们按这个思路去整理代码, 看会不会遇到什么阻力。