Script directory

From Boktai Hacking Wiki
Revision as of 23:05, 9 August 2024 by Raphi (talk | contribs)

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:

struct script_directory {
  u32 unknown;                   // 0x3ea8eae6 in Boktai 1 (U)
  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; // unknown...

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

  // Unknown data, including the "special script"
  u8 unknown[?];
};

// 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) {
  // top bit is unknown, always seems to be 1?
  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) {
  // top bit is unknown, always seems to be 1?
  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);
}