mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-02 03:48:00 +03:00
aindump: support Hentai Labyrinth
Even though this game is still ain v14, it makes an incompatible change to the file format: struct definitions now contain a list of functions at the end (I believe this is a listing of the struct's vtable). There is now a system for tracking the minor version of the ain file format. This is a made up value, determined by the file name of the ain file. This commit also fixes an issue where the empty string was being added as a new string to the string table when rebuilding ain files with ainedit.
This commit is contained in:
@@ -21,6 +21,11 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define AIN_VERSION_GTE(ain, major, minor) \
|
||||
(ain->version > major || (ain->version == major && ain->minor_version >= minor))
|
||||
|
||||
#define AIN_VERSION_LT(ain, major, minor) !AIN_VERSION_GTE(ain, major, minor)
|
||||
|
||||
typedef uint32_t ain_addr_t;
|
||||
|
||||
struct string;
|
||||
@@ -193,6 +198,8 @@ struct ain_struct {
|
||||
int32_t destructor;
|
||||
int32_t nr_members;
|
||||
struct ain_variable *members;
|
||||
int32_t nr_vmethods;
|
||||
int32_t *vmethods;
|
||||
};
|
||||
|
||||
struct ain_hll_argument {
|
||||
@@ -254,6 +261,7 @@ struct ain_section {
|
||||
struct ain {
|
||||
char *ain_path;
|
||||
int32_t version;
|
||||
int32_t minor_version; // XXX: hack for incompatibilities within major versions
|
||||
int32_t keycode;
|
||||
uint8_t *code;
|
||||
size_t code_size;
|
||||
|
||||
@@ -29,8 +29,8 @@ struct hash_table;
|
||||
struct hash_table *ht_create(size_t nr_buckets);
|
||||
void ht_free(struct hash_table *ht);
|
||||
|
||||
void *ht_get(struct hash_table *ht, const char *key);
|
||||
struct ht_slot *ht_put(struct hash_table *ht, const char *key);
|
||||
void *ht_get(struct hash_table *ht, const char *key, void *dflt);
|
||||
struct ht_slot *ht_put(struct hash_table *ht, const char *key, void *dflt);
|
||||
void ht_foreach_value(struct hash_table *ht, void(*fun)(void*));
|
||||
|
||||
#endif /* SYSTEM4_HASHTABLE_H */
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "little_endian.h"
|
||||
@@ -41,7 +42,7 @@ struct func_list {
|
||||
|
||||
static void func_ht_add(struct ain *ain, int i)
|
||||
{
|
||||
struct ht_slot *kv = ht_put(ain->_func_ht, ain->functions[i].name);
|
||||
struct ht_slot *kv = ht_put(ain->_func_ht, ain->functions[i].name, NULL);
|
||||
if (kv->value) {
|
||||
// entry with this name already exists; add to list
|
||||
struct func_list *list = kv->value;
|
||||
@@ -68,7 +69,7 @@ static void init_func_ht(struct ain *ain)
|
||||
|
||||
static void struct_ht_add(struct ain *ain, struct ain_struct *s)
|
||||
{
|
||||
struct ht_slot *kv = ht_put(ain->_struct_ht, s->name);
|
||||
struct ht_slot *kv = ht_put(ain->_struct_ht, s->name, NULL);
|
||||
if (kv->value) {
|
||||
ERROR("Duplicate structure names: '%s'", s->name);
|
||||
}
|
||||
@@ -85,8 +86,8 @@ static void init_struct_ht(struct ain *ain)
|
||||
|
||||
static intptr_t string_ht_add(struct ain *ain, const char *str, intptr_t i)
|
||||
{
|
||||
struct ht_slot *kv = ht_put(ain->_string_ht, str);
|
||||
if (kv->value) {
|
||||
struct ht_slot *kv = ht_put(ain->_string_ht, str, (void*)-1);
|
||||
if ((intptr_t)kv->value >= 0) {
|
||||
return (intptr_t)kv->value;
|
||||
}
|
||||
kv->value = (void*)i;
|
||||
@@ -168,7 +169,7 @@ void ain_init_member_functions(struct ain *ain, char *(*to_ascii)(const char*))
|
||||
|
||||
static struct func_list *get_function(struct ain *ain, const char *name)
|
||||
{
|
||||
return ht_get(ain->_func_ht, name);
|
||||
return ht_get(ain->_func_ht, name, NULL);
|
||||
}
|
||||
|
||||
struct ain_function *ain_get_function(struct ain *ain, char *name)
|
||||
@@ -216,7 +217,7 @@ err:
|
||||
|
||||
struct ain_struct *ain_get_struct(struct ain *ain, char *name)
|
||||
{
|
||||
return ht_get(ain->_struct_ht, name);
|
||||
return ht_get(ain->_struct_ht, name, NULL);
|
||||
}
|
||||
|
||||
int ain_get_struct_no(struct ain *ain, char *name)
|
||||
@@ -241,7 +242,7 @@ struct ain_variable *ain_add_global(struct ain *ain, char *name)
|
||||
int no = ain->nr_globals;
|
||||
ain->globals = xrealloc_array(ain->globals, ain->nr_globals, ain->nr_globals+1, sizeof(struct ain_variable));
|
||||
ain->globals[no].name = strdup(name);
|
||||
if (ain->version >= 12)
|
||||
if (AIN_VERSION_GTE(ain, 12, 0))
|
||||
ain->globals[no].name2 = strdup("");
|
||||
ain->globals[no].var_type = AIN_VAR_GLOBAL;
|
||||
ain->nr_globals++;
|
||||
@@ -612,6 +613,7 @@ static void read_variable_initval(struct ain_reader *r, struct ain_variable *v)
|
||||
break;
|
||||
case AIN_DELEGATE:
|
||||
case AIN_REF_TYPE:
|
||||
case AIN_ARRAY:
|
||||
break;
|
||||
default:
|
||||
v->initval.i = read_int32(r);
|
||||
@@ -636,10 +638,10 @@ static struct ain_variable *read_variables(struct ain_reader *r, int count, stru
|
||||
struct ain_variable *v = &variables[i];
|
||||
v->var_type = var_type;
|
||||
v->name = read_string(r);
|
||||
if (ain->version >= 12)
|
||||
if (AIN_VERSION_GTE(ain, 12, 0))
|
||||
v->name2 = read_string(r); // ???
|
||||
read_variable_type(r, &v->type);
|
||||
if (ain->version >= 8)
|
||||
if (AIN_VERSION_GTE(ain, 8, 0))
|
||||
read_variable_initval(r, v);
|
||||
}
|
||||
return variables;
|
||||
@@ -647,7 +649,7 @@ static struct ain_variable *read_variables(struct ain_reader *r, int count, stru
|
||||
|
||||
static void read_return_type(struct ain_reader *r, struct ain_type *t, struct ain *ain)
|
||||
{
|
||||
if (ain->version >= 11) {
|
||||
if (AIN_VERSION_GTE(ain, 11, 0)) {
|
||||
read_variable_type(r, t);
|
||||
return;
|
||||
}
|
||||
@@ -673,7 +675,7 @@ static struct ain_function *read_functions(struct ain_reader *r, int count, stru
|
||||
funs[i].nr_args = read_int32(r);
|
||||
funs[i].nr_vars = read_int32(r);
|
||||
|
||||
if (ain->version >= 11) {
|
||||
if (AIN_VERSION_GTE(ain, 11, 0)) {
|
||||
funs[i].is_lambda = read_int32(r); // known values: 0, 1
|
||||
if (funs[i].is_lambda && funs[i].is_lambda != 1) {
|
||||
ERROR("function->is_lambda is not a boolean? %d (at %p)", funs[i].is_lambda, r->index - 4);
|
||||
@@ -695,10 +697,10 @@ static struct ain_variable *read_globals(struct ain_reader *r, int count, struct
|
||||
struct ain_variable *globals = calloc(count, sizeof(struct ain_variable));
|
||||
for (int i = 0; i < count; i++) {
|
||||
globals[i].name = read_string(r);
|
||||
if (ain->version >= 12)
|
||||
if (AIN_VERSION_GTE(ain, 12, 0))
|
||||
globals[i].name2 = read_string(r);
|
||||
read_variable_type(r, &globals[i].type);
|
||||
if (ain->version >= 5)
|
||||
if (AIN_VERSION_GTE(ain, 5, 0))
|
||||
globals[i].group_index = read_int32(r);
|
||||
globals[i].var_type = AIN_VAR_GLOBAL;
|
||||
}
|
||||
@@ -725,7 +727,7 @@ static struct ain_struct *read_structures(struct ain_reader *r, int count, struc
|
||||
struct ain_struct *structures = calloc(count, sizeof(struct ain_struct));
|
||||
for (int i = 0; i < count; i++) {
|
||||
structures[i].name = read_string(r);
|
||||
if (ain->version >= 11) {
|
||||
if (AIN_VERSION_GTE(ain, 11, 0)) {
|
||||
structures[i].nr_interfaces = read_int32(r);
|
||||
structures[i].interfaces = xcalloc(structures[i].nr_interfaces, sizeof(struct ain_interface));
|
||||
for (int j = 0; j < structures[i].nr_interfaces; j++) {
|
||||
@@ -737,6 +739,16 @@ static struct ain_struct *read_structures(struct ain_reader *r, int count, struc
|
||||
structures[i].destructor = read_int32(r);
|
||||
structures[i].nr_members = read_int32(r);
|
||||
structures[i].members = read_variables(r, structures[i].nr_members, ain, AIN_VAR_MEMBER);
|
||||
|
||||
// Staring with Hentai Labyrinth, there is a list of functions at the end.
|
||||
// I believe this is a listing of the functions in the vtable.
|
||||
if (AIN_VERSION_GTE(ain, 14, 1)) {
|
||||
structures[i].nr_vmethods = read_int32(r);
|
||||
structures[i].vmethods = xcalloc(structures[i].nr_vmethods, sizeof(int32_t));
|
||||
for (int j = 0; j < structures[i].nr_vmethods; j++) {
|
||||
structures[i].vmethods[j] = read_int32(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
return structures;
|
||||
}
|
||||
@@ -746,7 +758,7 @@ static struct ain_hll_argument *read_hll_arguments(struct ain_reader *r, int cou
|
||||
struct ain_hll_argument *arguments = calloc(count, sizeof(struct ain_hll_argument));
|
||||
for (int i = 0; i < count; i++) {
|
||||
arguments[i].name = read_string(r);
|
||||
if (r->ain->version >= 14) {
|
||||
if (AIN_VERSION_GTE(r->ain, 14, 0)) {
|
||||
read_variable_type(r, &arguments[i].type);
|
||||
} else {
|
||||
arguments[i].type.data = read_int32(r);
|
||||
@@ -762,7 +774,7 @@ static struct ain_hll_function *read_hll_functions(struct ain_reader *r, int cou
|
||||
struct ain_hll_function *functions = calloc(count, sizeof(struct ain_hll_function));
|
||||
for (int i = 0; i < count; i++) {
|
||||
functions[i].name = read_string(r);
|
||||
if (r->ain->version >= 14) {
|
||||
if (AIN_VERSION_GTE(r->ain, 14, 0)) {
|
||||
read_variable_type(r, &functions[i].return_type);
|
||||
} else {
|
||||
functions[i].return_type.data = read_int32(r);
|
||||
@@ -893,6 +905,19 @@ static void start_section(struct ain_reader *r, struct ain_section *section)
|
||||
}
|
||||
}
|
||||
|
||||
static void detect_quirks(struct ain *ain)
|
||||
{
|
||||
char *tmp = strdup(ain->ain_path);
|
||||
char *name = basename(tmp);
|
||||
|
||||
if (ain->version == 14) {
|
||||
if (!strcasecmp(name, "HentaiLabyrinth.ain"))
|
||||
ain->minor_version = 1;
|
||||
}
|
||||
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
static bool read_tag(struct ain_reader *r, struct ain *ain)
|
||||
{
|
||||
if (r->index + 4 >= r->size) {
|
||||
@@ -903,11 +928,11 @@ static bool read_tag(struct ain_reader *r, struct ain *ain)
|
||||
uint8_t *tag_loc = r->buf + r->index;
|
||||
|
||||
#define TAG_EQ(tag) !strncmp((char*)tag_loc, tag, 4)
|
||||
// FIXME: need to check len or could segfault on currupt AIN file
|
||||
// FIXME: need to check len or could segfault on corrupt AIN file
|
||||
if (TAG_EQ("VERS")) {
|
||||
start_section(r, &ain->VERS);
|
||||
ain->version = read_int32(r);
|
||||
if (ain->version >= 11) {
|
||||
if (AIN_VERSION_GTE(ain, 11, 0)) {
|
||||
instructions[CALLHLL].nr_args = 3;
|
||||
instructions[NEW].nr_args = 2;
|
||||
instructions[S_MOD].nr_args = 1;
|
||||
@@ -915,6 +940,7 @@ static bool read_tag(struct ain_reader *r, struct ain *ain)
|
||||
instructions[DG_STR_TO_METHOD].nr_args = 1;
|
||||
instructions[CALLMETHOD].args[0] = T_INT;
|
||||
}
|
||||
detect_quirks(ain);
|
||||
} else if (TAG_EQ("KEYC")) {
|
||||
start_section(r, &ain->KEYC);
|
||||
ain->keycode = read_int32(r);
|
||||
@@ -1163,6 +1189,7 @@ struct ain *ain_open(const char *path, int *error)
|
||||
|
||||
// read data into ain struct
|
||||
ain = xcalloc(1, sizeof(struct ain));
|
||||
ain->ain_path = xstrdup(path);
|
||||
struct ain_reader r = {
|
||||
.buf = buf,
|
||||
.index = 0,
|
||||
@@ -1330,6 +1357,7 @@ void ain_free_structures(struct ain *ain)
|
||||
for (int s = 0; s < ain->nr_structures; s++) {
|
||||
free(ain->structures[s].name);
|
||||
free(ain->structures[s].interfaces);
|
||||
free(ain->structures[s].vmethods);
|
||||
ain_free_variables(ain->structures[s].members, ain->structures[s].nr_members);
|
||||
}
|
||||
free(ain->structures);
|
||||
@@ -1422,6 +1450,7 @@ void ain_free_enums(struct ain *ain)
|
||||
|
||||
void ain_free(struct ain *ain)
|
||||
{
|
||||
free(ain->ain_path);
|
||||
free(ain->code);
|
||||
|
||||
ht_foreach_value(ain->_func_ht, free);
|
||||
|
||||
+6
-6
@@ -51,19 +51,19 @@ struct hash_table *ht_create(size_t nr_buckets)
|
||||
return ht;
|
||||
}
|
||||
|
||||
void *ht_get(struct hash_table *ht, const char *key)
|
||||
void *ht_get(struct hash_table *ht, const char *key, void *dflt)
|
||||
{
|
||||
unsigned long k = string_hash(key) & (ht->nr_buckets - 1);
|
||||
if (!ht->buckets[k])
|
||||
return NULL;
|
||||
return dflt;
|
||||
for (size_t i = 0; i < ht->buckets[k]->nr_slots; i++) {
|
||||
if (!strcmp(ht->buckets[k]->slots[i].key, key))
|
||||
return ht->buckets[k]->slots[i].value;
|
||||
}
|
||||
return NULL;
|
||||
return dflt;
|
||||
}
|
||||
|
||||
struct ht_slot *ht_put(struct hash_table *ht, const char *key)
|
||||
struct ht_slot *ht_put(struct hash_table *ht, const char *key, void *dflt)
|
||||
{
|
||||
unsigned long k = string_hash(key) & (ht->nr_buckets - 1);
|
||||
|
||||
@@ -72,7 +72,7 @@ struct ht_slot *ht_put(struct hash_table *ht, const char *key)
|
||||
ht->buckets[k] = xmalloc(sizeof(struct ht_bucket) + sizeof(struct ht_slot));
|
||||
ht->buckets[k]->nr_slots = 1;
|
||||
ht->buckets[k]->slots[0].key = strdup(key);
|
||||
ht->buckets[k]->slots[0].value = NULL;
|
||||
ht->buckets[k]->slots[0].value = dflt;
|
||||
return &ht->buckets[k]->slots[0];
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ struct ht_slot *ht_put(struct hash_table *ht, const char *key)
|
||||
ht->buckets[k] = xrealloc(ht->buckets[k], sizeof(struct ht_bucket) + sizeof(struct ht_slot)*(i+1));
|
||||
ht->buckets[k]->nr_slots = i+1;
|
||||
ht->buckets[k]->slots[i].key = strdup(key);
|
||||
ht->buckets[k]->slots[i].value = NULL;
|
||||
ht->buckets[k]->slots[i].value = dflt;
|
||||
return &ht->buckets[k]->slots[i];
|
||||
}
|
||||
|
||||
|
||||
@@ -134,11 +134,16 @@ static bool ain_compare_code(struct ain *_a, struct ain *_b)
|
||||
}
|
||||
} else {
|
||||
if (ia != ib) {
|
||||
NOTICE("argument differs at 0x%08x (%d vs %d)", (uint32_t)a.addr, ia, ib);
|
||||
// NOTE: If there's duplicate strings in the string table, string arguments
|
||||
// can change when rebuilding. This shouldn't matter (?).
|
||||
if (a.instr->opcode != S_PUSH || strcmp(_a->strings[ia]->text, _b->strings[ib]->text))
|
||||
if (a.instr->args[i] == T_STRING && strcmp(_a->strings[ia]->text, _b->strings[ib]->text)) {
|
||||
NOTICE("string argument differs at 0x%08x (%s vs %s)", (uint32_t)a.addr,
|
||||
_a->strings[ia]->text, _b->strings[ib]->text);
|
||||
return false;
|
||||
} else if (a.instr->args[i] != T_STRING) {
|
||||
NOTICE("argument differs at 0x%08x (%d vs %d)", (uint32_t)a.addr, ia, ib);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user