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

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

目录

本系列已完结,以下是各章节说明,17 之前是 dos 版相关,之后是 2001 版:

  1. 背景、简单分析
  2. 显存位置
  3. 事件图保存算法: LZW
  4. 调色板
  5. MGP2
  6. 结局图
  7. 事件图中的眼睛
  8. 音频文件
  9. 按位读取
  10. 循环之前
  11. 读取循环
  12. 重现 LZW
  13. PAT 的图形格式
  14. STAFF 调色板
  15. 字体文件
  16. 脚本解密
  17. 版本比较
  18. 第一张图
  19. 调色板1
  20. 第二张图
  21. 调色板2
  22. 调色板处理
  23. 静态事件图、结局图
  24. 动态图、鉴赏模式

循环

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();
}

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