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

接下来我们看调用 load_bits_by_len 的地方,预计内容比较多,所以会做一些拆分。

目录

本系列已完结,以下是各章节说明,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. 动态图、鉴赏模式

引用

右键点击 load_bits_by_len,我们就可以找到查找引用的功能:

虽然引用的地方很多,但是实际上他们都在一个函数 FUN_1018_3da2 中。

FUN_1018_3da2

这个函数很长:

void __stdcall16far
FUN_1018_3da2(undefined2 param_1,undefined2 param_2,undefined4 param_3,undefined4 param_4)
{
  undefined uVar1;
  char cVar2;
  int iVar3;
  int local_12;
  int local_6;
  int local_4;
  
  FUN_1008_3cc0(0x4000,0,(int)&DICT_START,0x1050);
  BUFFER_2BYTES_AX = 0;
  BUFFER_2BYTES_DX = 0;
  CURRENT_BUFFER_LEN = 0;
  IMAGE_BUFFER_PTR = (undefined2)((ulong)param_4 >> 0x10);
  FILE_BUFFER_IDX = (undefined2)param_4;
  UK_BUFFER_PTR = (undefined2)((ulong)param_3 >> 0x10);
  UK_BUFFER_IDX = (undefined2)param_3;
  local_12 = -1;
  init_dict();
  load_bits_by_len();
  local_4 = load_bits_by_len();
  FUN_1018_3d00(local_4);
  local_6 = load_bits_by_len();
  while (local_6 != END_MARK_101) {
    ...
  }
  FUN_1008_3d5f(0x4000,0,(int)&DICT_START,0x1050);
  (*DAT_1050_25e6)(0x1008,100);
  return;
}

所以我估计会拆成几个部分来讲,这次我们只讲到循环开始前。 注意我标记了一些全局变量,有些是之前标记的,有些是这次标记的, 我们还是按顺序去讲函数,顺带分析变量。

FUN_1008_3cc0alloc_ram

第一个函数,传了一个 4000h,一个 0,一个字典的地址, 我们很容易猜这是一个初始化函数,不过我们还是看一下代码:

undefined4 __stdcall16far FUN_1008_3cc0(uint param_1,int param_2,undefined2 *param_3)
{
  uint uVar1;
  int iVar2;
  int iVar3;
  undefined2 uVar4;
  long lVar5;
  undefined4 uVar6;
  undefined2 uVar7;
  undefined2 local_6;
  undefined2 local_4;
  
  uVar7 = 0;
  lVar5 = GETFREESPACE();
  iVar2 = (int)((ulong)lVar5 >> 0x10);
  if (CONCAT22(param_2,param_1) < lVar5) {
    iVar3 = (int)param_3;
    uVar4 = (undefined2)((ulong)param_3 >> 0x10);
    if ((param_2 < 0) || ((param_2 < 1 && (param_1 < 0xfff8)))) {
      uVar1 = FUN_1048_03a6(uVar7);
      if ((param_2 < iVar2) || ((param_2 <= iVar2 && (param_1 < uVar1)))) {
        uVar6 = FUN_1048_033e(param_1);
        *param_3 = (int)uVar6;
        *(undefined2 *)(iVar3 + 2) = (int)((ulong)uVar6 >> 0x10);
      }
      else {
        *param_3 = 0;
        *(undefined2 *)(iVar3 + 2) = 0;
      }
    }
    else {
      uVar6 = FUN_1010_3d6f(param_1,param_2,2);
      *param_3 = (int)uVar6;
      *(undefined2 *)(iVar3 + 2) = (int)((ulong)uVar6 >> 0x10);
    }
    local_6 = *param_3;
    local_4 = *(undefined2 *)(iVar3 + 2);
  }
  else {
    local_6 = 0;
    local_4 = 0;
  }
  return CONCAT22(local_4,local_6);
}

感觉这个函数讲完也就差不多了,不过我觉得这个函数并不重要,感觉就是分配内存空间, 我们快速过一下好了。首先调用 GETFREESPACE,根据 DOS 2.0 API, 这是用来获取磁盘剩余空间。所以最外层的 if 是判断磁盘空间,如果空间不足, 直接返回空指针。

那么空间充足,则又分了两种情况, 不过 (param_2 < 0) || ((param_2 < 1 && (param_1 < 0xfff8)))这个不太好理解, 我们只能先理解为是判断我们需要的空间有没有超过一定范围。根据传入值的日志, 我们是符合条件的,不过 else 比较简单, 我们先看这个 else 中唯一的函数 FUN_1010_3d6f 好了。

FUN_1010_3d6falloc_huge_ram

void __stdcall16far FUN_1010_3d6f(undefined2 param_1,undefined2 param_2,undefined2 param_3)
{
  undefined2 uVar1;
  undefined2 unaff_CS;
  
  uVar1 = GLOBALALLOC(unaff_CS,param_1,param_2);
  GLOBALLOCK(0x1060,uVar1,param_3);
  return;
}

很简单,GLOBALALLOC 查了一下, 也是用来分配大量内存的,所以这个我们不需要在意,有一点摸不清头脑的是, 前面是检查磁盘空间,而这个好像是申请内存的,不过我们应该不用纠结这个问题, 分配内存不是我们要注意的东西。

else 我们搞清楚了,那其实 if 就是分配少量内存时使用, 由于函数中有调用 RTM::Ordinal_21,这个我没查到资料,那么我们索性推测, 整个函数 FUN_1008_3cc0,其实就是为了分配内存。

FUN_1018_3b3finit_dict

void __cdecl16far init_dict(void)
{
  undefined2 *puVar1;
  undefined2 uVar2;
  int local_4;
  
  FUN_1008_3de2(0,0x4000,0,(int)DICT_START,(int)((ulong)DICT_START >> 0x10));
  CURRENT_BIT_LEN = 8;
  RESET_MARK_100 = 0x100;
  END_MARK_101 = 0x101;
  LAST_DICT_INDEX = 0x101;
  local_4 = 0;
  CURRENT_DICT_LEN = RESET_MARK_100;
  while( true ) {
    uVar2 = (undefined2)((ulong)DICT_START >> 0x10);
    puVar1 = (undefined2 *)((int)DICT_START + local_4 * 4);
    *puVar1 = 0x7fff;
    *(undefined *)(puVar1 + 1) = (undefined)local_4;
    if (local_4 == 0x101) break;
    local_4 = local_4 + 1;
  }
  CURRENT_BIT_LEN = CURRENT_BIT_LEN + 1;
  CURRENT_DICT_LEN = CURRENT_DICT_LEN << 1;
  return;
}

看循环应该是把 0 - 101h 的字典值设为索引,前面 FUN_1008_3de2 推测是全部设为 0。注意:0-101h 对应的前两位值都是 7fffh, 来代表这是一个颜色

FUN_1008_3de2init_ram

void __stdcall16far FUN_1008_3de2(undefined init_val,uint dict_len,int param_3,undefined4 dict_ptr)
{
  undefined2 *puVar1;
  uint uVar2;
  undefined2 *puVar4;
  int iVar5;
  uint uVar3;
  
  iVar5 = (int)((ulong)dict_ptr >> 0x10);
  puVar4 = (undefined2 *)dict_ptr;
  while( true ) {
    if ((0 < param_3) || (uVar3 = dict_len, CARRY2(dict_len,(uint)puVar4))) {
      param_3 = param_3 - (uint)(dict_len < -(int)puVar4 - 1U);
      uVar3 = -(int)puVar4 - 1U;
    }
    for (uVar2 = uVar3 >> 1; uVar2 != 0; uVar2 = uVar2 - 1) {
      puVar1 = puVar4;
      puVar4 = puVar4 + 1;
      *puVar1 = CONCAT11(init_val,init_val);
    }
    for (uVar2 = (uint)((uVar3 & 1) != 0); uVar2 != 0; uVar2 = uVar2 - 1) {
      puVar1 = puVar4;
      puVar4 = (undefined2 *)((int)puVar4 + 1);
      *(undefined *)puVar1 = init_val;
    }
    if ((param_3 < 1) && (dict_len - uVar3 == 0)) break;
    puVar1 = puVar4;
    puVar4 = (undefined2 *)((int)puVar4 + 1);
    *(undefined *)puVar1 = init_val;
    dict_len = (dict_len - uVar3) - 1;
    if ((param_3 < 1) && (dict_len == 0)) {
      return;
    }
    iVar5 = iVar5 + 8;
  }
  return;
}

while(true) 的第一个 if 比较难看懂,不过我觉得问题不大, 这看上去就是处理了一下越界的问题。

第一个 for 就很容易理解了,相当于 memset

第二个 for,感觉是又做了一次 memset,感觉可能程序在这里遇到过什么 bug。

后面的处理,似乎也是在处理指针和长度的特殊情况,感觉应该也不是重点。 所以整个函数应该就是用来初始化内存空间。

FUN_1018_3d00convert_to_pix

void __stdcall16far FUN_1018_3d00(int param_1)
{
  int dict_start;
  undefined2 uVar1;
  undefined2 unaff_SS;
  int local_1006;
  int local_1004;
  undefined local_1002 [4096];
  
  uVar1 = (undefined2)((ulong)DICT_START >> 0x10);
  dict_start = (int)DICT_START;
  local_1002[0] = *(undefined *)(dict_start + param_1 * 4 + 2);
  local_1004 = 1;
  while (*(int *)(dict_start + param_1 * 4) != 0x7fff) {
    param_1 = *(int *)(dict_start + param_1 * 4);
    local_1002[local_1004] = *(undefined *)(dict_start + param_1 * 4 + 2);
    local_1004 = local_1004 + 1;
  }
  local_1006 = local_1004 + -1;
  if (-1 < local_1006) {
    while( true ) {
      *_UK_BUFFER_IDX = local_1002[local_1006];
      offset_file_buffer(1,(int)&UK_BUFFER_IDX,0x1050);
      if (local_1006 == 0) break;
      local_1006 = local_1006 + -1;
    }
  }
  return;
}

这段函数做两件事情:

  1. 首先根据传入索引的值,在字典中查找对应位置,并把对应的颜色放入 local_1002 这个数组中,然后如果查到的索引并不是一个颜色,也就是前两位不是 7fffh,那么这个循环会一直继续。
  2. local_1002 的颜色倒序输出。

这和我们之前的处理就有出入了,还记得我们读到未创建索引时是怎么处理的吗, 我们会把这个索引造出来,但是其实根据这段程序,他只是简单地返回了初始设定值。

基于这一点其实引申了一个事实:在查找索引的过程中,并不会创建新的索引。

总结

3da2 的函数,循环前的内容,我们基本看了一遍,主要是做了这些事:

  1. 分配内存
  2. 初始化内存
  3. 初始化字典
  4. 读取和转换部分索引

下一次,我们就要进入循环了。