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

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

目录

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

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

引用

右键点击 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. 读取和转换部分索引

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