mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-05 21:37:59 +03:00
1488 lines
41 KiB
C
1488 lines
41 KiB
C
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
|
*
|
|
* Credit to SLC for reverse engineering AIN formats.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <libgen.h>
|
|
#include <zlib.h>
|
|
|
|
#include "little_endian.h"
|
|
#include "system4.h"
|
|
#include "system4/ain.h"
|
|
#include "system4/hashtable.h"
|
|
#include "system4/instructions.h"
|
|
#include "system4/string.h"
|
|
#include "system4/utfsjis.h"
|
|
|
|
struct func_list {
|
|
int nr_slots;
|
|
int slots[];
|
|
};
|
|
#define func_list_size(nr_slots) (sizeof(struct func_list) + sizeof(int)*(nr_slots))
|
|
|
|
static void func_ht_add(struct ain *ain, int i)
|
|
{
|
|
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;
|
|
list = xrealloc(list, func_list_size(list->nr_slots+1));
|
|
list->slots[list->nr_slots] = i;
|
|
list->nr_slots++;
|
|
kv->value = list;
|
|
} else {
|
|
// empty bucket: create list
|
|
struct func_list *list = xmalloc(func_list_size(1));
|
|
list->nr_slots = 1;
|
|
list->slots[0] = i;
|
|
kv->value = list;
|
|
}
|
|
}
|
|
|
|
static void init_func_ht(struct ain *ain)
|
|
{
|
|
ain->_func_ht = ht_create(1024);
|
|
for (int i = 0; i < ain->nr_functions; i++) {
|
|
func_ht_add(ain, i);
|
|
}
|
|
}
|
|
|
|
static void struct_ht_add(struct ain *ain, struct ain_struct *s)
|
|
{
|
|
struct ht_slot *kv = ht_put(ain->_struct_ht, s->name, NULL);
|
|
if (kv->value) {
|
|
ERROR("Duplicate structure names: '%s'", s->name);
|
|
}
|
|
kv->value = s;
|
|
}
|
|
|
|
static void init_struct_ht(struct ain *ain)
|
|
{
|
|
ain->_struct_ht = ht_create(1024);
|
|
for (int i = 0; i < ain->nr_structures; i++) {
|
|
struct_ht_add(ain, &ain->structures[i]);
|
|
}
|
|
}
|
|
|
|
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, (void*)-1);
|
|
if ((intptr_t)kv->value >= 0) {
|
|
return (intptr_t)kv->value;
|
|
}
|
|
kv->value = (void*)i;
|
|
return i;
|
|
}
|
|
|
|
static void init_string_ht(struct ain *ain)
|
|
{
|
|
ain->_string_ht = ht_create(1024);
|
|
for (int i = 0; i < ain->nr_strings; i++) {
|
|
if (string_ht_add(ain, ain->strings[i]->text, i) != i) {
|
|
WARNING("Duplicate string in string table");
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool function_is_member_of(char *func_name, char *struct_name)
|
|
{
|
|
while (*func_name && *struct_name && *func_name != '@' && *func_name == *struct_name) {
|
|
func_name++;
|
|
struct_name++;
|
|
}
|
|
return !*struct_name && *func_name == '@';
|
|
}
|
|
|
|
/*
|
|
* Infer struct member functions from function names.
|
|
*/
|
|
void ain_init_member_functions(struct ain *ain, char *(*to_ascii)(const char*))
|
|
{
|
|
// XXX: we convert all struct names up front to avoid repeated conversions below
|
|
char **struct_names = xcalloc(ain->nr_structures, sizeof(char*));
|
|
for (int i = 0; i < ain->nr_structures; i++) {
|
|
struct_names[i] = to_ascii(ain->structures[i].name);
|
|
}
|
|
char **enum_names = xcalloc(ain->nr_enums, sizeof(char*));
|
|
for (int i = 0; i < ain->nr_enums; i++) {
|
|
enum_names[i] = to_ascii(ain->enums[i].name);
|
|
}
|
|
|
|
for (int f = 0; f < ain->nr_functions; f++) {
|
|
ain->functions[f].struct_type = -1;
|
|
ain->functions[f].enum_type = -1;
|
|
char *name = to_ascii(ain->functions[f].name);
|
|
if (!strchr(name, '@')) {
|
|
free(name);
|
|
continue;
|
|
}
|
|
for (int s = 0; s < ain->nr_structures; s++) {
|
|
if (function_is_member_of(name, struct_names[s])) {
|
|
ain->functions[f].struct_type = s;
|
|
break;
|
|
}
|
|
}
|
|
if (ain->functions[f].struct_type != -1) {
|
|
free(name);
|
|
continue;
|
|
}
|
|
// check enums
|
|
for (int e = 0; e < ain->nr_enums; e++) {
|
|
if (function_is_member_of(name, enum_names[e])) {
|
|
ain->functions[f].enum_type = e;
|
|
break;
|
|
}
|
|
}
|
|
if (ain->functions[f].enum_type == -1)
|
|
WARNING("Failed to find struct type for function \"%s\"", name);
|
|
free(name);
|
|
}
|
|
for (int i = 0; i < ain->nr_structures; i++) {
|
|
free(struct_names[i]);
|
|
}
|
|
free(struct_names);
|
|
for (int i = 0; i < ain->nr_enums; i++) {
|
|
free(enum_names[i]);
|
|
}
|
|
free(enum_names);
|
|
}
|
|
|
|
static struct func_list *get_function(struct ain *ain, const char *name)
|
|
{
|
|
return ht_get(ain->_func_ht, name, NULL);
|
|
}
|
|
|
|
struct ain_function *ain_get_function(struct ain *ain, char *name)
|
|
{
|
|
size_t len;
|
|
long n = 0;
|
|
|
|
// handle name#index syntax
|
|
for (len = 0; name[len]; len++) {
|
|
if (name[len] == '#') {
|
|
char *endptr;
|
|
n = strtol(name+len+1, &endptr, 10);
|
|
if (!name[len+1] || *endptr || n < 0)
|
|
ERROR("Invalid function name: '%s'", name);
|
|
name[len] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct func_list *funs = get_function(ain, name);
|
|
if (!funs || n >= funs->nr_slots)
|
|
return NULL;
|
|
return &ain->functions[funs->slots[n]];
|
|
}
|
|
|
|
int ain_get_function_no(struct ain *ain, char *name)
|
|
{
|
|
struct ain_function *f = ain_get_function(ain, name);
|
|
return f ? f - ain->functions : -1;
|
|
}
|
|
|
|
int ain_get_function_index(struct ain *ain, struct ain_function *f)
|
|
{
|
|
struct func_list *funs = get_function(ain, f->name);
|
|
if (!funs)
|
|
goto err;
|
|
|
|
for (int i = 0; i < funs->nr_slots; i++) {
|
|
if (&ain->functions[funs->slots[i]] == f)
|
|
return i;
|
|
}
|
|
err:
|
|
ERROR("Invalid function: '%s'", f->name);
|
|
}
|
|
|
|
struct ain_struct *ain_get_struct(struct ain *ain, char *name)
|
|
{
|
|
return ht_get(ain->_struct_ht, name, NULL);
|
|
}
|
|
|
|
int ain_get_struct_no(struct ain *ain, char *name)
|
|
{
|
|
struct ain_struct *s = ain_get_struct(ain, name);
|
|
return s ? s - ain->structures : -1;
|
|
}
|
|
|
|
int ain_add_struct(struct ain *ain, char *name)
|
|
{
|
|
ain->structures = xrealloc_array(ain->structures, ain->nr_structures, ain->nr_structures+1, sizeof(struct ain_struct));
|
|
ain->structures[ain->nr_structures].name = strdup(name);
|
|
ain->structures[ain->nr_structures].constructor = -1;
|
|
ain->structures[ain->nr_structures].destructor = -1;
|
|
struct_ht_add(ain, &ain->structures[ain->nr_structures]);
|
|
ain->nr_structures++;
|
|
return ain->nr_structures - 1;
|
|
}
|
|
|
|
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_GTE(ain, 12, 0))
|
|
ain->globals[no].name2 = strdup("");
|
|
ain->globals[no].var_type = AIN_VAR_GLOBAL;
|
|
ain->nr_globals++;
|
|
return &ain->globals[no];
|
|
}
|
|
|
|
struct ain_variable *ain_get_global(struct ain *ain, const char *name)
|
|
{
|
|
// TODO: use hash table for faster lookup
|
|
for (int i = 0; i < ain->nr_globals; i++) {
|
|
if (!strcmp(ain->globals[i].name, name))
|
|
return &ain->globals[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int ain_get_global_no(struct ain *ain, const char *name)
|
|
{
|
|
struct ain_variable *v = ain_get_global(ain, name);
|
|
return v ? v - ain->globals : -1;
|
|
}
|
|
|
|
void ain_add_initval(struct ain *ain, struct ain_initval *init)
|
|
{
|
|
ain->global_initvals = xrealloc_array(ain->global_initvals, ain->nr_initvals, ain->nr_initvals+1,
|
|
sizeof(struct ain_initval));
|
|
ain->global_initvals[ain->nr_initvals++] = *init;
|
|
}
|
|
|
|
int ain_add_function(struct ain *ain, struct ain_function *fun)
|
|
{
|
|
int no = ain->nr_functions;
|
|
ain->functions = xrealloc_array(ain->functions, no, no+1, sizeof (struct ain_function));
|
|
ain->functions[no] = *fun;
|
|
func_ht_add(ain, no);
|
|
ain->nr_functions++;
|
|
return no;
|
|
}
|
|
|
|
int ain_add_functype(struct ain *ain, struct ain_function_type *fun)
|
|
{
|
|
int no = ain->nr_function_types;
|
|
ain->function_types = xrealloc_array(ain->function_types, no, no+1, sizeof(struct ain_function_type));
|
|
ain->function_types[no] = *fun;
|
|
ain->nr_function_types++;
|
|
return no;
|
|
}
|
|
|
|
int ain_get_functype(struct ain *ain, const char *name)
|
|
{
|
|
for (int i = 0; i < ain->nr_function_types; i++) {
|
|
if (!strcmp(ain->function_types[i].name, name))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int ain_add_string(struct ain *ain, const char *str)
|
|
{
|
|
if (!ain->_string_ht)
|
|
init_string_ht(ain);
|
|
int i = string_ht_add(ain, str, ain->nr_strings);
|
|
if (i == ain->nr_strings) {
|
|
ain->strings = xrealloc_array(ain->strings, ain->nr_strings, ain->nr_strings+1, sizeof(struct string*));
|
|
ain->strings[ain->nr_strings++] = make_string(str, strlen(str));
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int ain_add_message(struct ain *ain, const char *str)
|
|
{
|
|
ain->messages = xrealloc_array(ain->messages, ain->nr_messages, ain->nr_messages+1, sizeof(struct string*));
|
|
ain->messages[ain->nr_messages++] = make_string(str, strlen(str));
|
|
return ain->nr_messages - 1;
|
|
}
|
|
|
|
int ain_add_file(struct ain *ain, const char *filename)
|
|
{
|
|
ain->filenames = xrealloc_array(ain->filenames, ain->nr_filenames, ain->nr_filenames+1, sizeof(char*));
|
|
ain->filenames[ain->nr_filenames++] = strdup(filename);
|
|
return ain->nr_filenames - 1;
|
|
}
|
|
|
|
int ain_add_library(struct ain *ain, struct ain_library *lib)
|
|
{
|
|
int no = ain->nr_libraries;
|
|
ain->libraries = xrealloc_array(ain->libraries, no, no+1, sizeof(struct ain_library));
|
|
ain->libraries[no] = *lib;
|
|
ain->nr_libraries++;
|
|
return no;
|
|
}
|
|
|
|
static const char *errtab[AIN_MAX_ERROR] = {
|
|
[AIN_SUCCESS] = "Success",
|
|
[AIN_FILE_ERROR] = "Error opening AIN file",
|
|
[AIN_UNRECOGNIZED_FORMAT] = "Unrecognized or invalid AIN format",
|
|
[AIN_INVALID] = "Invalid AIN file"
|
|
};
|
|
|
|
const char *ain_strerror(int error)
|
|
{
|
|
if (error < AIN_MAX_ERROR)
|
|
return errtab[error];
|
|
return "Invalid error code";
|
|
}
|
|
|
|
const char *ain_strtype(struct ain *ain, enum ain_data_type type, int struct_type)
|
|
{
|
|
static char buf[1024] = { [1023] = '\0' };
|
|
struct ain_type t = { .data = type, .struc = struct_type, .rank = 0 };
|
|
char *str = ain_strtype_d(ain, &t);
|
|
strncpy(buf, str, 1023);
|
|
free(str);
|
|
return buf;
|
|
}
|
|
|
|
static char *type_sprintf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *buf = xmalloc(1024);
|
|
va_start(ap, fmt);
|
|
|
|
vsnprintf(buf, 1023, fmt, ap);
|
|
buf[1023] = '\0';
|
|
|
|
va_end(ap);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static char *array_type_string(const char *str, int rank)
|
|
{
|
|
if (rank <= 1)
|
|
return strdup(str);
|
|
return type_sprintf("%s@%d", str, rank);
|
|
}
|
|
|
|
static char *container_type_string(struct ain *ain, struct ain_type *t)
|
|
{
|
|
char *buf = xmalloc(1024);
|
|
char *type = ain_strtype_d(ain, t->array_type);
|
|
const char *container_type
|
|
= t->data == AIN_ARRAY ? "array"
|
|
: t->data == AIN_REF_ARRAY ? "ref array"
|
|
: t->data == AIN_ITERATOR ? "iter"
|
|
: "unknown_container";
|
|
|
|
snprintf(buf, 1023, "%s<%s>", container_type, type);
|
|
|
|
free(type);
|
|
buf[1023] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
char *ain_strtype_d(struct ain *ain, struct ain_type *v)
|
|
{
|
|
char buf[1024];
|
|
if (!v)
|
|
return strdup("?");
|
|
switch (v->data) {
|
|
case AIN_VOID: return strdup("void");
|
|
case AIN_INT: return strdup("int");
|
|
case AIN_FLOAT: return strdup("float");
|
|
case AIN_STRING: return strdup("string");
|
|
case AIN_STRUCT:
|
|
if (v->struc == -1 || !ain)
|
|
return strdup("struct");
|
|
return strdup(ain->structures[v->struc].name);
|
|
case AIN_ARRAY_INT: return array_type_string("array@int", v->rank);
|
|
case AIN_ARRAY_FLOAT: return array_type_string("array@float", v->rank);
|
|
case AIN_ARRAY_STRING: return array_type_string("array@string", v->rank);
|
|
case AIN_ARRAY_STRUCT:
|
|
if (v->struc == -1 || !ain)
|
|
return array_type_string("array@struct", v->rank);
|
|
snprintf(buf, 1024, "array@%s", ain->structures[v->struc].name);
|
|
return array_type_string(buf, v->rank);
|
|
case AIN_REF_INT: return strdup("ref int");
|
|
case AIN_REF_FLOAT: return strdup("ref float");
|
|
case AIN_REF_STRING: return strdup("ref string");
|
|
case AIN_REF_STRUCT:
|
|
if (v->struc == -1 || !ain)
|
|
return strdup("ref struct");
|
|
return type_sprintf("ref %s", ain->structures[v->struc].name);
|
|
case AIN_REF_ARRAY_INT: return array_type_string("ref array@int", v->rank);
|
|
case AIN_REF_ARRAY_FLOAT: return array_type_string("ref array@float", v->rank);
|
|
case AIN_REF_ARRAY_STRING: return array_type_string("ref array@string", v->rank);
|
|
case AIN_REF_ARRAY_STRUCT:
|
|
if (v->struc == -1 || !ain)
|
|
return strdup("ref array@struct");
|
|
snprintf(buf, 1024, "ref array@%s", ain->structures[v->struc].name);
|
|
return array_type_string(buf, v->rank);
|
|
case AIN_IMAIN_SYSTEM: return strdup("imain_system");
|
|
case AIN_FUNC_TYPE: return strdup("functype");
|
|
case AIN_ARRAY_FUNC_TYPE: return array_type_string("array@functype", v->rank);
|
|
case AIN_REF_FUNC_TYPE: return strdup("ref functype");
|
|
case AIN_REF_ARRAY_FUNC_TYPE: return array_type_string("ref array@functype", v->rank);
|
|
case AIN_BOOL: return strdup("bool");
|
|
case AIN_ARRAY_BOOL: return array_type_string("array@bool", v->rank);
|
|
case AIN_REF_BOOL: return strdup("ref bool");
|
|
case AIN_REF_ARRAY_BOOL: return array_type_string("ref array@bool", v->rank);
|
|
case AIN_LONG_INT: return strdup("lint");
|
|
case AIN_ARRAY_LONG_INT: return array_type_string("array@lint", v->rank);
|
|
case AIN_REF_LONG_INT: return strdup("ref lint");
|
|
case AIN_REF_ARRAY_LONG_INT: return array_type_string("ref array@lint", v->rank);
|
|
case AIN_DELEGATE: return strdup("delegate");
|
|
case AIN_ARRAY_DELEGATE: return array_type_string("array@delegate", v->rank);
|
|
case AIN_REF_ARRAY_DELEGATE: return array_type_string("ref array@delegate", v->rank);
|
|
case AIN_UNKNOWN_TYPE_67: return strdup("type_67");
|
|
case AIN_UNKNOWN_TYPE_74: return strdup("type_74");
|
|
case AIN_UNKNOWN_TYPE_75: return strdup("type_75");
|
|
case AIN_ARRAY:
|
|
case AIN_REF_ARRAY:
|
|
case AIN_ITERATOR:
|
|
return container_type_string(ain, v);
|
|
case AIN_ENUM1:
|
|
if (!v->array_type || v->array_type->struc == -1 || !ain || v->array_type->struc >= ain->nr_enums)
|
|
return strdup("enum");
|
|
return type_sprintf("%s#86", ain->enums[v->array_type->struc].name);
|
|
case AIN_UNKNOWN_TYPE_87: return strdup("type_87");
|
|
case AIN_IFACE:
|
|
if (v->struc == -1 || !ain)
|
|
return strdup("interface");
|
|
return strdup(ain->structures[v->struc].name);
|
|
case AIN_ENUM2:
|
|
case AIN_ENUM3:
|
|
if (v->struc == -1 || !ain || v->struc >= ain->nr_enums)
|
|
return strdup("enum");
|
|
return type_sprintf("%s#%d", ain->enums[v->struc].name, v->data);
|
|
case AIN_REF_ENUM:
|
|
if (v->struc == -1 || !ain || v->struc >= ain->nr_enums)
|
|
return strdup("enum");
|
|
return type_sprintf("ref %s", ain->enums[v->struc].name);
|
|
case AIN_UNKNOWN_TYPE_95: return strdup("type_95");
|
|
case AIN_UNKNOWN_TYPE_100: return strdup("type_100");
|
|
default:
|
|
WARNING("Unknown type: %d", v->data);
|
|
return type_sprintf("unknown_type_%d", v->data);
|
|
}
|
|
}
|
|
|
|
const char *ain_variable_to_string(struct ain *ain, struct ain_variable *v)
|
|
{
|
|
static char buf[2048] = { [2047] = '\0' };
|
|
char *type = ain_strtype_d(ain, &v->type);
|
|
int i = snprintf(buf, 2047, "%s %s", type, v->name);
|
|
free(type);
|
|
|
|
if (v->has_initval) {
|
|
switch (v->type.data) {
|
|
case AIN_STRING: {
|
|
// FIXME: string needs escaping
|
|
snprintf(buf+i, 2047-i, " = \"%s\"", v->initval.s);
|
|
break;
|
|
}
|
|
case AIN_DELEGATE:
|
|
case AIN_REF_TYPE:
|
|
break;
|
|
case AIN_FLOAT:
|
|
snprintf(buf+i, 2047-i, " = %f", v->initval.f);
|
|
break;
|
|
default:
|
|
snprintf(buf+i, 2047-i, " = %d", v->initval.i);
|
|
break;
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
struct ain_reader {
|
|
uint8_t *buf;
|
|
size_t size;
|
|
size_t index;
|
|
struct ain_section *section;
|
|
struct ain *ain;
|
|
};
|
|
|
|
static int32_t read_int32(struct ain_reader *r)
|
|
{
|
|
int32_t v = LittleEndian_getDW(r->buf, r->index);
|
|
r->index += 4;
|
|
return v;
|
|
}
|
|
|
|
static uint8_t *read_bytes(struct ain_reader *r, size_t len)
|
|
{
|
|
uint8_t *bytes = xmalloc(len);
|
|
memcpy(bytes, r->buf + r->index, len);
|
|
r->index += len;
|
|
return bytes;
|
|
}
|
|
|
|
static char *read_string(struct ain_reader *r)
|
|
{
|
|
char *str = strdup((char*)r->buf + r->index);
|
|
r->index += strlen(str) + 1;
|
|
return str;
|
|
}
|
|
|
|
// TODO: should just memcpy the whole section and create pointers into it
|
|
// e.g. char *_messages; // raw AIN section
|
|
// char **messages; // array of pointers into _messages
|
|
static char **read_strings(struct ain_reader *r, int count)
|
|
{
|
|
char **strings = calloc(count, sizeof(char*));
|
|
for (int i = 0; i < count; i++) {
|
|
strings[i] = read_string(r);
|
|
}
|
|
return strings;
|
|
}
|
|
|
|
static struct string *read_vm_string(struct ain_reader *r)
|
|
{
|
|
size_t len = strlen((char*)r->buf + r->index);
|
|
struct string *s = make_string((char*)r->buf + r->index, len);
|
|
s->cow = true;
|
|
r->index += len + 1;
|
|
return s;
|
|
}
|
|
|
|
static struct string **read_vm_strings(struct ain_reader *r, int count)
|
|
{
|
|
struct string **strings = calloc(count, sizeof(struct string*));
|
|
for (int i = 0; i < count; i++) {
|
|
strings[i] = read_vm_string(r);
|
|
}
|
|
return strings;
|
|
}
|
|
|
|
static struct string *read_msg1_string(struct ain_reader *r)
|
|
{
|
|
int32_t len = read_int32(r);
|
|
struct string *s = make_string((char*)r->buf + r->index, len);
|
|
s->cow = true;
|
|
r->index += len;
|
|
|
|
// why...
|
|
for (int i = 0; i < len; i++) {
|
|
s->text[i] -= (uint8_t)i;
|
|
s->text[i] -= 0x60;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static struct string **read_msg1_strings(struct ain_reader *r, int count)
|
|
{
|
|
struct string **strings = calloc(count, sizeof(struct string*));
|
|
for (int i = 0; i < count; i++) {
|
|
strings[i] = read_msg1_string(r);
|
|
}
|
|
return strings;
|
|
}
|
|
|
|
static void read_variable_type(struct ain_reader *r, struct ain_type *t);
|
|
|
|
static struct ain_type *read_array_type(struct ain_reader *r, int rank)
|
|
{
|
|
if (rank <= 0)
|
|
ERROR("Invalid array rank: %d", rank);
|
|
|
|
struct ain_type *type = xcalloc(rank, sizeof(struct ain_type));
|
|
for (int i = 0; i < rank; i++) {
|
|
read_variable_type(r, type+i);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
static void read_variable_initval(struct ain_reader *r, struct ain_variable *v)
|
|
{
|
|
if ((v->has_initval = read_int32(r))) {
|
|
if (v->has_initval != 1)
|
|
ERROR("variable->has_initval is not boolean? %d (at %p)", v->has_initval, r->index - 4);
|
|
switch (v->type.data) {
|
|
case AIN_STRING:
|
|
v->initval.s = read_string(r);
|
|
break;
|
|
case AIN_STRUCT:
|
|
case AIN_DELEGATE:
|
|
case AIN_REF_TYPE:
|
|
case AIN_ARRAY:
|
|
break;
|
|
default:
|
|
v->initval.i = read_int32(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void read_variable_type(struct ain_reader *r, struct ain_type *t)
|
|
{
|
|
t->data = read_int32(r);
|
|
t->struc = read_int32(r);
|
|
t->rank = read_int32(r);
|
|
|
|
if (ain_is_array_data_type(t->data))
|
|
t->array_type = read_array_type(r, t->rank);
|
|
}
|
|
|
|
static struct ain_variable *read_variables(struct ain_reader *r, int count, struct ain *ain, enum ain_variable_type var_type)
|
|
{
|
|
struct ain_variable *variables = calloc(count, sizeof(struct ain_variable));
|
|
for (int i = 0; i < count; i++) {
|
|
struct ain_variable *v = &variables[i];
|
|
v->var_type = var_type;
|
|
v->name = read_string(r);
|
|
if (AIN_VERSION_GTE(ain, 12, 0))
|
|
v->name2 = read_string(r); // ???
|
|
read_variable_type(r, &v->type);
|
|
if (AIN_VERSION_GTE(ain, 8, 0))
|
|
read_variable_initval(r, v);
|
|
}
|
|
return variables;
|
|
}
|
|
|
|
static void read_return_type(struct ain_reader *r, struct ain_type *t, struct ain *ain)
|
|
{
|
|
if (AIN_VERSION_GTE(ain, 11, 0)) {
|
|
read_variable_type(r, t);
|
|
return;
|
|
}
|
|
|
|
t->data = read_int32(r);
|
|
t->struc = read_int32(r);
|
|
}
|
|
|
|
static struct ain_function *read_functions(struct ain_reader *r, int count, struct ain *ain)
|
|
{
|
|
struct ain_function *funs = calloc(count, sizeof(struct ain_function));
|
|
for (int i = 0; i < count; i++) {
|
|
funs[i].address = read_int32(r);
|
|
funs[i].name = read_string(r);
|
|
if (!strcmp(funs[i].name, "0"))
|
|
ain->alloc = i;
|
|
|
|
if (ain->version > 0 && ain->version < 7)
|
|
funs[i].is_label = read_int32(r);
|
|
|
|
read_return_type(r, &funs[i].return_type, ain);
|
|
|
|
funs[i].nr_args = read_int32(r);
|
|
funs[i].nr_vars = read_int32(r);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (ain->version > 0) {
|
|
funs[i].crc = read_int32(r);
|
|
}
|
|
if (funs[i].nr_vars > 0) {
|
|
funs[i].vars = read_variables(r, funs[i].nr_vars, ain, AIN_VAR_LOCAL);
|
|
}
|
|
}
|
|
return funs;
|
|
}
|
|
|
|
static struct ain_variable *read_globals(struct ain_reader *r, int count, struct ain *ain)
|
|
{
|
|
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_GTE(ain, 12, 0))
|
|
globals[i].name2 = read_string(r);
|
|
read_variable_type(r, &globals[i].type);
|
|
if (AIN_VERSION_GTE(ain, 5, 0))
|
|
globals[i].group_index = read_int32(r);
|
|
globals[i].var_type = AIN_VAR_GLOBAL;
|
|
}
|
|
return globals;
|
|
}
|
|
|
|
static struct ain_initval *read_initvals(struct ain_reader *r, int count)
|
|
{
|
|
struct ain_initval *values = calloc(count, sizeof(struct ain_initval));
|
|
for (int i = 0; i < count; i++) {
|
|
values[i].global_index = read_int32(r);
|
|
values[i].data_type = read_int32(r);
|
|
if (values[i].data_type == AIN_STRING) {
|
|
values[i].string_value = read_string(r);
|
|
} else {
|
|
values[i].int_value = read_int32(r);
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
static struct ain_struct *read_structures(struct ain_reader *r, int count, struct ain *ain)
|
|
{
|
|
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_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++) {
|
|
structures[i].interfaces[j].struct_type = read_int32(r);
|
|
structures[i].interfaces[j].uk = read_int32(r);
|
|
}
|
|
}
|
|
structures[i].constructor = read_int32(r);
|
|
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;
|
|
}
|
|
|
|
static struct ain_hll_argument *read_hll_arguments(struct ain_reader *r, int count)
|
|
{
|
|
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 (AIN_VERSION_GTE(r->ain, 14, 0)) {
|
|
read_variable_type(r, &arguments[i].type);
|
|
} else {
|
|
arguments[i].type.data = read_int32(r);
|
|
arguments[i].type.struc = -1;
|
|
arguments[i].type.rank = 0;
|
|
}
|
|
}
|
|
return arguments;
|
|
}
|
|
|
|
static struct ain_hll_function *read_hll_functions(struct ain_reader *r, int count)
|
|
{
|
|
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 (AIN_VERSION_GTE(r->ain, 14, 0)) {
|
|
read_variable_type(r, &functions[i].return_type);
|
|
} else {
|
|
functions[i].return_type.data = read_int32(r);
|
|
functions[i].return_type.struc = -1;
|
|
functions[i].return_type.rank = 0;
|
|
}
|
|
functions[i].nr_arguments = read_int32(r);
|
|
if (functions[i].nr_arguments > 100 || functions[i].nr_arguments < 0)
|
|
ERROR("TOO MANY ARGUMENTS (AT 0x%x)", r->index);
|
|
functions[i].arguments = read_hll_arguments(r, functions[i].nr_arguments);
|
|
}
|
|
return functions;
|
|
}
|
|
|
|
static struct ain_library *read_libraries(struct ain_reader *r, int count)
|
|
{
|
|
struct ain_library *libraries = calloc(count, sizeof(struct ain_library));
|
|
for (int i = 0; i < count; i++) {
|
|
libraries[i].name = read_string(r);
|
|
libraries[i].nr_functions = read_int32(r);
|
|
libraries[i].functions = read_hll_functions(r, libraries[i].nr_functions);
|
|
}
|
|
return libraries;
|
|
}
|
|
|
|
static struct ain_switch_case *read_switch_cases(struct ain_reader *r, int count, struct ain_switch *parent)
|
|
{
|
|
struct ain_switch_case *cases = calloc(count, sizeof(struct ain_switch));
|
|
for (int i = 0; i < count; i++) {
|
|
cases[i].value = read_int32(r);
|
|
cases[i].address = read_int32(r);
|
|
cases[i].parent = parent;
|
|
}
|
|
return cases;
|
|
}
|
|
|
|
static struct ain_switch *read_switches(struct ain_reader *r, int count)
|
|
{
|
|
struct ain_switch *switches = calloc(count, sizeof(struct ain_switch));
|
|
for (int i = 0; i < count; i++) {
|
|
switches[i].case_type = read_int32(r);
|
|
switches[i].default_address = read_int32(r);
|
|
switches[i].nr_cases = read_int32(r);
|
|
switches[i].cases = read_switch_cases(r, switches[i].nr_cases, &switches[i]);
|
|
}
|
|
return switches;
|
|
}
|
|
|
|
static struct ain_function_type *read_function_types(struct ain_reader *r, int count, struct ain *ain)
|
|
{
|
|
struct ain_function_type *types = calloc(count, sizeof(struct ain_function_type));
|
|
for (int i = 0; i < count; i++) {
|
|
types[i].name = read_string(r);
|
|
read_return_type(r, &types[i].return_type, ain);
|
|
types[i].nr_arguments = read_int32(r);
|
|
types[i].nr_variables = read_int32(r);
|
|
types[i].variables = read_variables(r, types[i].nr_variables, ain, AIN_VAR_LOCAL);
|
|
}
|
|
return types;
|
|
}
|
|
|
|
#define for_each_instruction(ain, addr, instr, start, user_code) \
|
|
do { \
|
|
const struct instruction *instr; \
|
|
for (size_t addr = start; addr < ain->code_size; addr += instruction_width(instr->opcode)) { \
|
|
uint16_t _fei_opcode = LittleEndian_getW(ain->code, addr); \
|
|
if (_fei_opcode >= NR_OPCODES) \
|
|
ERROR("Unknown/invalid opcode: %u", _fei_opcode); \
|
|
instr = &instructions[_fei_opcode]; \
|
|
if (addr + instr->nr_args * 4 >= ain->code_size) \
|
|
ERROR("CODE section truncated?"); \
|
|
user_code; \
|
|
} \
|
|
} while (0)
|
|
|
|
struct ain_enum *read_enums(struct ain_reader *r, int count, struct ain *ain)
|
|
{
|
|
char **names = read_strings(r, count);
|
|
struct ain_enum *enums = xcalloc(count, sizeof(struct ain_enum));
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
enums[i].name = names[i];
|
|
|
|
char buf[1024] = { [1023] = '\0' };
|
|
snprintf(buf, 1023, ain->version < 14 ? "%s@String" : "%s::ToString", names[i]);
|
|
|
|
struct func_list *funs = get_function(ain, buf);
|
|
if (!funs || funs->nr_slots != 1) {
|
|
WARNING("Failed to parse enum: %s", names[i]);
|
|
continue;
|
|
}
|
|
|
|
int j = 0;
|
|
for_each_instruction(ain, addr, instr, ain->functions[funs->slots[0]].address, {
|
|
if (instr->opcode == ENDFUNC)
|
|
break;
|
|
if (instr->opcode != S_PUSH)
|
|
continue;
|
|
|
|
int32_t strno = LittleEndian_getDW(ain->code, addr + 2);
|
|
if (strno < 0 || strno >= ain->nr_strings) {
|
|
WARNING("Encountered invalid string number when parsing enums");
|
|
continue;
|
|
}
|
|
if (!ain->strings[strno]->size)
|
|
continue;
|
|
|
|
enums[i].symbols = xrealloc(enums[i].symbols, sizeof(char*) * (j+1));
|
|
enums[i].symbols[j] = ain->strings[strno]->text;
|
|
enums[i].nr_symbols = j + 1;
|
|
j++;
|
|
});
|
|
}
|
|
|
|
free(names);
|
|
return enums;
|
|
}
|
|
|
|
static void start_section(struct ain_reader *r, struct ain_section *section)
|
|
{
|
|
if (r->section)
|
|
r->section->size = r->index - r->section->addr;
|
|
r->section = section;
|
|
if (section) {
|
|
r->section->addr = r->index;
|
|
r->section->present = true;
|
|
r->index += 4;
|
|
}
|
|
}
|
|
|
|
static void detect_quirks(struct ain *ain)
|
|
{
|
|
char *tmp = strdup(ain->ain_path);
|
|
char *name = basename(tmp);
|
|
|
|
if (ain->version == 14) {
|
|
if (!strncasecmp(name, "HentaiLabyrinth.ain", 19))
|
|
ain->minor_version = 1;
|
|
}
|
|
|
|
free(tmp);
|
|
}
|
|
|
|
static bool read_tag(struct ain_reader *r, struct ain *ain)
|
|
{
|
|
if (r->index + 4 >= r->size) {
|
|
start_section(r, NULL);
|
|
return false;
|
|
}
|
|
|
|
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 corrupt AIN file
|
|
if (TAG_EQ("VERS")) {
|
|
start_section(r, &ain->VERS);
|
|
ain->version = read_int32(r);
|
|
if (AIN_VERSION_GTE(ain, 11, 0)) {
|
|
instructions[CALLHLL].nr_args = 3;
|
|
instructions[NEW].nr_args = 2;
|
|
instructions[S_MOD].nr_args = 1;
|
|
instructions[OBJSWAP].nr_args = 1;
|
|
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);
|
|
} else if (TAG_EQ("CODE")) {
|
|
start_section(r, &ain->CODE);
|
|
ain->code_size = read_int32(r);
|
|
ain->code = read_bytes(r, ain->code_size);
|
|
} else if (TAG_EQ("FUNC")) {
|
|
start_section(r, &ain->FUNC);
|
|
ain->nr_functions = read_int32(r);
|
|
ain->functions = read_functions(r, ain->nr_functions, ain);
|
|
init_func_ht(ain);
|
|
} else if (TAG_EQ("GLOB")) {
|
|
start_section(r, &ain->GLOB);
|
|
ain->nr_globals = read_int32(r);
|
|
ain->globals = read_globals(r, ain->nr_globals, ain);
|
|
} else if (TAG_EQ("GSET")) {
|
|
start_section(r, &ain->GSET);
|
|
ain->nr_initvals = read_int32(r);
|
|
ain->global_initvals = read_initvals(r, ain->nr_initvals);
|
|
} else if (TAG_EQ("STRT")) {
|
|
start_section(r, &ain->STRT);
|
|
ain->nr_structures = read_int32(r);
|
|
ain->structures = read_structures(r, ain->nr_structures, ain);
|
|
init_struct_ht(ain);
|
|
} else if (TAG_EQ("MSG0")) {
|
|
start_section(r, &ain->MSG0);
|
|
ain->nr_messages = read_int32(r);
|
|
ain->messages = read_vm_strings(r, ain->nr_messages);
|
|
} else if (TAG_EQ("MSG1")) {
|
|
start_section(r, &ain->MSG1);
|
|
ain->nr_messages = read_int32(r);
|
|
ain->msg1_uk = read_int32(r); // ???
|
|
ain->messages = read_msg1_strings(r, ain->nr_messages);
|
|
} else if (TAG_EQ("MAIN")) {
|
|
start_section(r, &ain->MAIN);
|
|
ain->main = read_int32(r);
|
|
} else if (TAG_EQ("MSGF")) {
|
|
start_section(r, &ain->MSGF);
|
|
ain->msgf = read_int32(r);
|
|
} else if (TAG_EQ("HLL0")) {
|
|
start_section(r, &ain->HLL0);
|
|
ain->nr_libraries = read_int32(r);
|
|
ain->libraries = read_libraries(r, ain->nr_libraries);
|
|
} else if (TAG_EQ("SWI0")) {
|
|
start_section(r, &ain->SWI0);
|
|
ain->nr_switches = read_int32(r);
|
|
ain->switches = read_switches(r, ain->nr_switches);
|
|
} else if (TAG_EQ("GVER")) {
|
|
start_section(r, &ain->GVER);
|
|
ain->game_version = read_int32(r);
|
|
} else if (TAG_EQ("STR0")) {
|
|
start_section(r, &ain->STR0);
|
|
ain->nr_strings = read_int32(r);
|
|
ain->strings = read_vm_strings(r, ain->nr_strings);
|
|
} else if (TAG_EQ("FNAM")) {
|
|
start_section(r, &ain->FNAM);
|
|
ain->nr_filenames = read_int32(r);
|
|
ain->filenames = read_strings(r, ain->nr_filenames);
|
|
} else if (TAG_EQ("OJMP")) {
|
|
start_section(r, &ain->OJMP);
|
|
ain->ojmp = read_int32(r);
|
|
} else if (TAG_EQ("FNCT")) {
|
|
start_section(r, &ain->FNCT);
|
|
ain->fnct_size = read_int32(r);
|
|
ain->nr_function_types = read_int32(r);
|
|
ain->function_types = read_function_types(r, ain->nr_function_types, ain);
|
|
} else if (TAG_EQ("DELG")) {
|
|
start_section(r, &ain->DELG);
|
|
ain->delg_size = read_int32(r);
|
|
ain->nr_delegates = read_int32(r);
|
|
ain->delegates = read_function_types(r, ain->nr_delegates, ain);
|
|
} else if (TAG_EQ("OBJG")) {
|
|
start_section(r, &ain->OBJG);
|
|
ain->nr_global_groups = read_int32(r);
|
|
ain->global_group_names = read_strings(r, ain->nr_global_groups);
|
|
} else if (TAG_EQ("ENUM")) {
|
|
start_section(r, &ain->ENUM);
|
|
ain->ENUM.present = true;
|
|
ain->nr_enums = read_int32(r);
|
|
ain->enums = read_enums(r, ain->nr_enums, ain);
|
|
} else {
|
|
start_section(r, NULL);
|
|
WARNING("Junk at end of AIN file?");
|
|
return false;
|
|
}
|
|
#undef TAG_EQ
|
|
|
|
return true;
|
|
}
|
|
|
|
static void distribute_initvals(struct ain *ain)
|
|
{
|
|
for (int i = 0; i < ain->nr_initvals; i++) {
|
|
struct ain_variable *g = &ain->globals[ain->global_initvals[i].global_index];
|
|
g->has_initval = true;
|
|
if (ain->global_initvals[i].data_type == AIN_STRING)
|
|
g->initval.s = ain->global_initvals[i].string_value;
|
|
else
|
|
g->initval.i = ain->global_initvals[i].int_value;
|
|
}
|
|
}
|
|
|
|
static uint8_t *decompress_ain(uint8_t *in, long *len)
|
|
{
|
|
uint8_t *out;
|
|
long out_len = LittleEndian_getDW(in, 8);
|
|
long in_len = LittleEndian_getDW(in, 12);
|
|
|
|
if (out_len < 0 || in_len < 0)
|
|
return NULL;
|
|
|
|
out = xmalloc(out_len);
|
|
int r = uncompress(out, (unsigned long*)&out_len, in+16, in_len);
|
|
if (r != Z_OK) {
|
|
if (r == Z_BUF_ERROR)
|
|
WARNING("uncompress failed: Z_BUF_ERROR");
|
|
else if (r == Z_MEM_ERROR)
|
|
WARNING("uncompress failed: Z_MEM_ERROR");
|
|
else if (r == Z_DATA_ERROR)
|
|
WARNING("uncompress failed: Z_DATA_ERROR");
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
|
|
*len = out_len;
|
|
return out;
|
|
}
|
|
|
|
static uint32_t temper(uint32_t in)
|
|
{
|
|
in ^= (in >> 11);
|
|
in ^= (in << 7) & 0x9D2C5680;
|
|
in ^= (in << 15) & 0xEFC60000;
|
|
in ^= (in >> 18);
|
|
return in;
|
|
}
|
|
|
|
static void update(uint32_t *state)
|
|
{
|
|
int ahead = 0x18D;
|
|
int write = 0;
|
|
int read = 2;
|
|
uint32_t current = state[1];
|
|
uint32_t last = state[0];
|
|
uint32_t tmp;
|
|
|
|
for (int i = 0; i < 0x270; i++) {
|
|
tmp = (((current ^ last) & 0x7FFFFFFE) ^ last) >> 1;
|
|
tmp = (current & 1) ? (tmp ^ 0x9908B0DF) : tmp;
|
|
state[write] = tmp ^ state[ahead];
|
|
last = current;
|
|
current = state[read];
|
|
write++;
|
|
read++;
|
|
ahead++;
|
|
while (read >= 0x270)
|
|
read -= 0x270;
|
|
while (ahead >= 0x270)
|
|
ahead -= 0x270;
|
|
}
|
|
}
|
|
|
|
void ain_decrypt(uint8_t *buf, size_t len)
|
|
{
|
|
uint32_t state[0x270];
|
|
uint32_t key = 0x5D3E3;
|
|
int off = 0;
|
|
|
|
for (int i = 0; i < 0x270; i++) {
|
|
state[i] = key;
|
|
key *= 0x10DCD;
|
|
}
|
|
|
|
update(state);
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (off >= 0x270) {
|
|
update(state);
|
|
off = 0;
|
|
}
|
|
buf[i] ^= (uint8_t)(temper(state[off]) & 0xFF);
|
|
off++;
|
|
}
|
|
}
|
|
|
|
static bool ain_is_encrypted(uint8_t *buf)
|
|
{
|
|
uint8_t magic[8];
|
|
|
|
memcpy(magic, buf, 8);
|
|
ain_decrypt(magic, 8);
|
|
|
|
return !strncmp((char*)magic, "VERS", 4) && !magic[5] && !magic[6] && !magic[7];
|
|
}
|
|
|
|
uint8_t *ain_read(const char *path, long *len, int *error)
|
|
{
|
|
FILE *fp;
|
|
uint8_t *buf = NULL;
|
|
|
|
if (!(fp = fopen(path, "rb"))) {
|
|
*error = AIN_FILE_ERROR;
|
|
goto err;
|
|
}
|
|
|
|
// get size of AIN file
|
|
fseek(fp, 0, SEEK_END);
|
|
*len = ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
|
|
// read AIN file into memory
|
|
buf = xmalloc(*len + 4); // why +4?
|
|
fread(buf, 1, *len, fp);
|
|
fclose(fp);
|
|
|
|
// check magic
|
|
if (!strncmp((char*)buf, "AI2\0\0\0\0", 8)) {
|
|
uint8_t *uc = decompress_ain(buf, len);
|
|
if (!uc) {
|
|
*error = AIN_INVALID;
|
|
goto err;
|
|
}
|
|
free(buf);
|
|
buf = uc;
|
|
} else if (ain_is_encrypted(buf)) {
|
|
ain_decrypt(buf, *len);
|
|
} else {
|
|
printf("%.4s\n", buf);
|
|
*error = AIN_UNRECOGNIZED_FORMAT;
|
|
goto err;
|
|
}
|
|
|
|
return buf;
|
|
err:
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
struct ain *ain_open(const char *path, int *error)
|
|
{
|
|
long len;
|
|
struct ain *ain = NULL;
|
|
uint8_t *buf = ain_read(path, &len, error);
|
|
if (!buf)
|
|
goto err;
|
|
|
|
// read data into ain struct
|
|
ain = xcalloc(1, sizeof(struct ain));
|
|
ain->ain_path = xstrdup(path);
|
|
struct ain_reader r = {
|
|
.buf = buf,
|
|
.index = 0,
|
|
.size = len,
|
|
.ain = ain
|
|
};
|
|
while (read_tag(&r, ain));
|
|
if (!ain->version) {
|
|
*error = AIN_INVALID;
|
|
goto err;
|
|
}
|
|
distribute_initvals(ain);
|
|
|
|
free(buf);
|
|
*error = AIN_SUCCESS;
|
|
return ain;
|
|
err:
|
|
free(buf);
|
|
free(ain);
|
|
return NULL;
|
|
}
|
|
|
|
struct ain *ain_new(int version)
|
|
{
|
|
struct ain *ain = xcalloc(1, sizeof(struct ain));
|
|
ain->VERS.present = true;
|
|
ain->KEYC.present = version < 12;
|
|
ain->CODE.present = true;
|
|
ain->FUNC.present = true;
|
|
ain->GLOB.present = true;
|
|
ain->GSET.present = version < 12;
|
|
ain->STRT.present = true;
|
|
// XXX: Starting with Rance IX, MSG1 section is used instead of MSG0.
|
|
// We can't tell from the ain version alone whether MSG0 or MSG1
|
|
// should be used since the ain version did not increase with
|
|
// this change, but v7+ at least should always use MSG1.
|
|
ain->MSG0.present = ain->version < 7;
|
|
ain->MSG1.present = !ain->MSG0.present;
|
|
ain->MAIN.present = true;
|
|
ain->MSGF.present = true;
|
|
ain->HLL0.present = true;
|
|
ain->SWI0.present = true;
|
|
ain->GVER.present = true;
|
|
ain->STR0.present = true;
|
|
ain->FNAM.present = ain->version < 12;
|
|
ain->OJMP.present = ain->version < 7;
|
|
// XXX: Another change starting from Rance IX: FNCT section disappears.
|
|
// Maybe they just stopped using function types, but still support
|
|
// them in the VM? Will need to change this flag when function
|
|
// types are added to the ain file.
|
|
ain->FNCT.present = ain->version < 7;
|
|
// XXX: Starting with Oyako Rankan... earlier v6 games don't have DELG.
|
|
// Will need to change this flag when delegates are added to the
|
|
// ain file.
|
|
ain->DELG.present = ain->version >= 7;
|
|
ain->OBJG.present = ain->version >= 5;
|
|
ain->ENUM.present = ain->version >= 12;
|
|
|
|
ain->version = version;
|
|
ain->main = -1;
|
|
ain->msgf = -1;
|
|
ain->ojmp = -1;
|
|
ain->game_version = 100;
|
|
|
|
ain->nr_functions = 1;
|
|
ain->functions = xcalloc(1, sizeof(struct ain_function));
|
|
ain->functions[0].name = strdup("NULL");
|
|
ain->functions[0].return_type = (struct ain_type) {
|
|
.data = AIN_VOID,
|
|
.struc = -1,
|
|
.rank = 0
|
|
};
|
|
|
|
ain->nr_messages = 1;
|
|
ain->messages = xcalloc(1, sizeof(struct string*));
|
|
ain->messages[0] = make_string("", 0);
|
|
|
|
ain->nr_strings = 1;
|
|
ain->strings = xcalloc(1, sizeof(struct string *));
|
|
ain->strings[0] = make_string("", 0);
|
|
|
|
init_struct_ht(ain);
|
|
init_func_ht(ain);
|
|
|
|
return ain;
|
|
}
|
|
|
|
static void ain_free_type(struct ain_type *type)
|
|
{
|
|
if (!type->array_type)
|
|
return;
|
|
|
|
for (int i = 0; i < type->rank; i++) {
|
|
ain_free_type(&type->array_type[i]);
|
|
}
|
|
free(type->array_type);
|
|
}
|
|
|
|
static void ain_free_variables(struct ain_variable *vars, int nr_vars)
|
|
{
|
|
for (int i = 0; i < nr_vars; i++) {
|
|
free(vars[i].name);
|
|
free(vars[i].name2);
|
|
ain_free_type(&vars[i].type);
|
|
if (vars[i].has_initval && vars[i].type.data == AIN_STRING)
|
|
free(vars[i].initval.s);
|
|
}
|
|
free(vars);
|
|
}
|
|
|
|
static void _ain_free_function_types(struct ain_function_type *funs, int n)
|
|
{
|
|
for (int i = 0; i < n; i++) {
|
|
free(funs[i].name);
|
|
ain_free_type(&funs[i].return_type);
|
|
ain_free_variables(funs[i].variables, funs[i].nr_variables);
|
|
}
|
|
free(funs);
|
|
}
|
|
|
|
static void ain_free_vmstrings(struct string **strings, int n)
|
|
{
|
|
for (int i = 0; i < n; i++) {
|
|
free_string(strings[i]);
|
|
}
|
|
free(strings);
|
|
}
|
|
|
|
static void ain_free_cstrings(char **strings, int n)
|
|
{
|
|
for (int i = 0; i < n; i++) {
|
|
free(strings[i]);
|
|
}
|
|
free(strings);
|
|
}
|
|
|
|
void ain_free_functions(struct ain *ain)
|
|
{
|
|
for (int f = 0; f < ain->nr_functions; f++) {
|
|
free(ain->functions[f].name);
|
|
ain_free_type(&ain->functions[f].return_type);
|
|
ain_free_variables(ain->functions[f].vars, ain->functions[f].nr_vars);
|
|
}
|
|
free(ain->functions);
|
|
ain->functions = NULL;
|
|
ain->nr_functions = 0;
|
|
}
|
|
|
|
void ain_free_globals(struct ain *ain)
|
|
{
|
|
ain_free_variables(ain->globals, ain->nr_globals);
|
|
ain->globals = NULL;
|
|
ain->nr_globals = 0;
|
|
}
|
|
|
|
void ain_free_initvals(struct ain *ain)
|
|
{
|
|
free(ain->global_initvals);
|
|
ain->global_initvals = NULL;
|
|
ain->nr_initvals = 0;
|
|
}
|
|
|
|
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);
|
|
ain->structures = NULL;
|
|
ain->nr_structures = 0;
|
|
}
|
|
|
|
void ain_free_messages(struct ain *ain)
|
|
{
|
|
ain_free_vmstrings(ain->messages, ain->nr_messages);
|
|
ain->messages = NULL;
|
|
ain->nr_messages = 0;
|
|
}
|
|
|
|
void ain_free_libraries(struct ain *ain)
|
|
{
|
|
for (int lib = 0; lib < ain->nr_libraries; lib++) {
|
|
free(ain->libraries[lib].name);
|
|
for (int f = 0; f < ain->libraries[lib].nr_functions; f++) {
|
|
free(ain->libraries[lib].functions[f].name);
|
|
ain_free_type(&ain->libraries[lib].functions[f].return_type);
|
|
for (int a = 0; a < ain->libraries[lib].functions[f].nr_arguments; a++) {
|
|
free(ain->libraries[lib].functions[f].arguments[a].name);
|
|
ain_free_type(&ain->libraries[lib].functions[f].arguments[a].type);
|
|
}
|
|
free(ain->libraries[lib].functions[f].arguments);
|
|
}
|
|
free(ain->libraries[lib].functions);
|
|
}
|
|
free(ain->libraries);
|
|
ain->libraries = NULL;
|
|
ain->nr_libraries = 0;
|
|
}
|
|
|
|
void ain_free_switches(struct ain *ain)
|
|
{
|
|
for (int i = 0; i < ain->nr_switches; i++) {
|
|
free(ain->switches[i].cases);
|
|
}
|
|
free(ain->switches);
|
|
ain->switches = NULL;
|
|
ain->nr_switches = 0;
|
|
}
|
|
|
|
void ain_free_strings(struct ain *ain)
|
|
{
|
|
ain_free_vmstrings(ain->strings, ain->nr_strings);
|
|
ain->strings = NULL;
|
|
ain->nr_strings = 0;
|
|
}
|
|
|
|
void ain_free_filenames(struct ain *ain)
|
|
{
|
|
ain_free_cstrings(ain->filenames, ain->nr_filenames);
|
|
ain->filenames = NULL;
|
|
ain->nr_filenames = 0;
|
|
}
|
|
|
|
void ain_free_function_types(struct ain *ain)
|
|
{
|
|
_ain_free_function_types(ain->function_types, ain->nr_function_types);
|
|
ain->function_types = NULL;
|
|
ain->nr_function_types = 0;
|
|
}
|
|
|
|
void ain_free_delegates(struct ain *ain)
|
|
{
|
|
_ain_free_function_types(ain->delegates, ain->nr_delegates);
|
|
ain->delegates = NULL;
|
|
ain->nr_delegates = 0;
|
|
}
|
|
|
|
void ain_free_global_groups(struct ain *ain)
|
|
{
|
|
ain_free_cstrings(ain->global_group_names, ain->nr_global_groups);
|
|
ain->global_group_names = NULL;
|
|
ain->nr_global_groups = 0;
|
|
}
|
|
|
|
void ain_free_enums(struct ain *ain)
|
|
{
|
|
for (int i = 0; i < ain->nr_enums; i++) {
|
|
free(ain->enums[i].name);
|
|
free(ain->enums[i].symbols);
|
|
}
|
|
free(ain->enums);
|
|
ain->enums = NULL;
|
|
ain->nr_enums = 0;
|
|
}
|
|
|
|
void ain_free(struct ain *ain)
|
|
{
|
|
free(ain->ain_path);
|
|
free(ain->code);
|
|
|
|
ht_foreach_value(ain->_func_ht, free);
|
|
ht_free(ain->_func_ht);
|
|
ht_free(ain->_struct_ht);
|
|
if (ain->_string_ht)
|
|
ht_free(ain->_string_ht);
|
|
|
|
ain_free_functions(ain);
|
|
ain_free_globals(ain);
|
|
ain_free_initvals(ain);
|
|
ain_free_structures(ain);
|
|
ain_free_messages(ain);
|
|
ain_free_libraries(ain);
|
|
ain_free_switches(ain);
|
|
ain_free_strings(ain);
|
|
ain_free_filenames(ain);
|
|
ain_free_function_types(ain);
|
|
ain_free_delegates(ain);
|
|
ain_free_global_groups(ain);
|
|
ain_free_enums(ain);
|
|
|
|
free(ain);
|
|
}
|