Script directory

From Boktai Hacking Wiki
Revision as of 13:46, 8 December 2024 by Raphi (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

The script directory (contained in the master file table) contains both bytecode scripts and strings used in the game. It's layout is different than the other directories in the MFT. The build_date is likely referring to Konami's authoring tools, and not to the game itself. The entire script directory and its contents may be aligned to a 1-byte boundary, as the game will only issue 1-byte reads to it. However, in practice it is still aligned to a 4-byte boundary.

struct script_directory {
  u32 build_date; // seconds since unix epoch
  script_entry script_entries[];

  directory_offsets offsets;

  // string_index contains the start index (in string_data) for each string
  // NOTE: offsets.string_index points here
  u32 string_index[];
  // NOTE: offsets.string_data points here
  char string_data[];

  // NOTE: offsets.script_data points here
  u32 special_script_offset;

  // NOTE: script_entry.offset is relative to here
  u8 bytecode[];

  // NOTE: special_script_offset points here
  u32 special_script_size;
  u8 special_script_data[];
};

// Use one of these two structs for script_entry, depending on the game:

// script_entries[] array is null-terminated (ends when all fields are 0)
struct script_entry_boktai_1 {
  u32 id;     // script id
  u32 offset; // top 8 bits unknown (not part of offset)
};

// script_entries[] array is -1 terminated (ends when all fields are -1)
struct script_entry_boktai_2_and_3 {
  // id is implied (id = array index + 1), meaning the 1st script has ID 1, not ID 0!
  u32 offset; // top 8 bits unknown (not part of offset)
};

struct directory_offsets {
  // all offsets here are relative to the start of this struct
  u32 script_data;
  u32 string_index;
  u32 string_data;
  u32 unknown;     // offset to unknown data
};

Example code (Boktai 1)

script_entry_boktai_1* script_index;
int script_count;
u8* script_data;

char* string_data;
u32* string_index;

// ptr = pointer to script_directory
void open_script_directory(u8* ptr) {
  // skip "unknown" field
  ptr += 4;

  script_index = (script_entry_boktai_1*) ptr;
  // skip over script index
  while (true) {
    script_entry_boktai_1* entry = (script_entry_boktai_1*) ptr;
    ptr += 8;
    if (entry->id == 0 && entry->offset == 0) {
      break;
    }
    script_count++;
  }

  script_data = (u8*)(ptr + *(uint32_t*)ptr);
  string_index = (u32*)(ptr + *(uint32_t*)(ptr + 4));
  string_data = (char*)(ptr + *(uint32_t*)(ptr + 8));
}

char* get_string(int id) {
  // Mask out the top bit, it's not part of the offset.
  // If the top bit = 1, then the string is text.
  // If the top bit = 0, then the string is other binary data.
  u32 offset = string_index[id] & 0x7fffffff;
  return string_data + offset;
}

u8* get_script(int id) {
  // NOTE: you should use a binary search (script_index is sorted by id)
  for (int i=0; i<script_count; i++) {
    script_entry_boktai_1* entry = script_index + i;
    if (entry->id == id) {
      // +4 to skip over the "special_script_offset" field
      return script_data + 4 + (entry->offset & 0x00ffffff);
    }
  }
  return NULL;
}

Example code (Boktai 2 & 3)

u32* script_index;
char* string_data;
u8* script_data;

int script_count;
u32* string_index;

// ptr = pointer to script_directory
void open_script_directory(u8* ptr) {
  // skip "unknown" field
  ptr += 4;

  script_index = (u32*) ptr;
  // skip over script index
  while (true) {
    u32 script_offset = *(u32*)ptr;
    ptr += 4;
    if (script_offset == 0xffffffff) {
      break;
    }
    script_count++;
  }

  script_data = (u8*)(ptr + *(uint32_t*)ptr);
  string_index = (u32*)(ptr + *(uint32_t*)(ptr + 4));
  string_data = (char*)(ptr + *(uint32_t*)(ptr + 8));
}

char* get_string(int id) {
  // Mask out the top bit, it's not part of the offset.
  // If the top bit = 1, then the string is text.
  // If the top bit = 0, then the string is other binary data.
  u32 offset = string_index[id] & 0x7fffffff;
  return string_data + offset;
}

u8* get_script(int id) {
  if (id <= 0 || id > script_count) {
    return NULL;
  }
  u32 offset = script_index[id - 1];
  return script_data + 4 + (offset & 0x00ffffff);
}

See also