From edffc0ad76fe75db756213a3da2365722d3d587d Mon Sep 17 00:00:00 2001 From: Nunuhara Cabbage Date: Mon, 4 Nov 2019 20:35:42 -0800 Subject: [PATCH] Initial CALLHLL and Math.dll implementation HLL libraries are compiled into the executable rather than dynamically loaded as in early versions of System 4. These days AliceSoft does the same thing. --- ain.c | 4 +- ain.h | 6 +- hll/hll.h | 59 ++++++++++ hll/math.c | 136 +++++++++++++++++++++++ instructions.c | 2 +- meson.build | 7 +- page.h | 11 ++ test/.gitignore | 3 +- test/Source/System/Etc/Etc.inc | 12 ++ test/Source/System/Etc/HLL/Math.hll | 50 +++++++++ test/Source/System/Etc/SACT_DLL.inc | 3 + test/Source/System/README.md | 18 +++ test/Source/System/System.inc | 1 + test/Source/System/System42/README.md | 12 -- test/Source/System/System42/System40.ini | 5 + test/Source/System/System42/System42.inc | 2 +- test/Source/main.jaf | 15 +++ test/Source/math.jaf | 32 ++++++ test/Source/test.inc | 3 +- vm.c | 85 ++++++++++++++ vm.h | 13 +++ 21 files changed, 457 insertions(+), 22 deletions(-) create mode 100644 hll/hll.h create mode 100644 hll/math.c create mode 100755 test/Source/System/Etc/Etc.inc create mode 100755 test/Source/System/Etc/HLL/Math.hll create mode 100755 test/Source/System/Etc/SACT_DLL.inc create mode 100755 test/Source/System/README.md delete mode 100755 test/Source/System/System42/README.md create mode 100755 test/Source/math.jaf diff --git a/ain.c b/ain.c index 2c1aea4..cff3944 100644 --- a/ain.c +++ b/ain.c @@ -448,8 +448,8 @@ static bool read_tag(struct ain_reader *r, struct ain *ain) } else if (TAG_EQ("MSGF")) { ain->msgf = read_int32(r); } else if (TAG_EQ("HLL0")) { - int32_t count = read_int32(r); - ain->libraries = read_libraries(r, count); + ain->nr_libraries = read_int32(r); + ain->libraries = read_libraries(r, ain->nr_libraries); } else if (TAG_EQ("SWI0")) { int32_t count = read_int32(r); ain->switches = read_switches(r, count); diff --git a/ain.h b/ain.h index b6bd0d5..1c8543f 100644 --- a/ain.h +++ b/ain.h @@ -149,6 +149,7 @@ struct ain_hll_argument { struct ain_hll_function { char *name; + void (*fun)(void); int32_t data_type; int32_t nr_arguments; struct ain_hll_argument *arguments; @@ -188,14 +189,15 @@ struct ain { uint8_t *code; size_t code_size; struct ain_function *functions; - struct ain_global *globals; int32_t nr_globals; - struct ain_initval *global_initvals; + struct ain_global *globals; int32_t nr_initvals; + struct ain_initval *global_initvals; struct ain_struct *structures; struct string **messages; int32_t main; int32_t msgf; + int32_t nr_libraries; struct ain_library *libraries; struct ain_switch *switches; int32_t game_version; diff --git a/hll/hll.h b/hll/hll.h new file mode 100644 index 0000000..2d44d92 --- /dev/null +++ b/hll/hll.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#ifndef SYSTEM4_HLL_H +#define SYSTEM4_HLL_H + +/* + * DSL for implementing for implementing libraries. + */ + +#include "../vm.h" +#include "../page.h" + +#define hll_defun(fname) \ + static void _hllfun_ ## fname(void); \ + static struct hll_function _hllstruct_ ## fname = { \ + .name = #fname, \ + .fun = _hllfun_ ## fname \ + }; \ + static void _hllfun_ ## fname(void) + +#define hll_unimplemented(fname) \ + hll_defun(fname) { \ + ERROR("Unimplemented HLL function: " #fname); \ + } + +#define hll_export(fname) &_hllstruct_ ## fname + +#define hll_deflib(lname, ...) \ + static struct hll_function *_lib_ ## lname[]; \ + struct library lib_ ## lname = { \ + .name = #lname, \ + .functions = _lib_ ## lname \ + }; \ + static struct hll_function *_lib_ ## lname[] = + +#define hll_return(v) stack_push(vm_value_cast(v)) + +static inline union vm_value hll_arg(int n) +{ + return stack[stack_ptr + n]; +} + +#endif /* SYSTEM4_HLL_H */ diff --git a/hll/math.c b/hll/math.c new file mode 100644 index 0000000..40e570d --- /dev/null +++ b/hll/math.c @@ -0,0 +1,136 @@ +/* Copyright (C) 2019 Nunuhara Cabbage + * + * 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 . + */ + +#include +#include + +#include "../system4.h" +#include "hll.h" + +static inline float deg2rad(float deg) +{ + return deg * (M_PI / 180.0); +} + +hll_defun(Cos) { + hll_return(cosf(deg2rad(hll_arg(0).f))); +} + +hll_defun(Sin) { + hll_return(sinf(deg2rad(hll_arg(0).f))); +} + +hll_defun(Sqrt) { + hll_return(sqrtf(hll_arg(0).f)); +} + +hll_defun(Atan) { + hll_return(atanf(hll_arg(0).f)); +} + +hll_defun(Atan2) { + hll_return(atan2f(hll_arg(0).f, hll_arg(1).f)); +} + +hll_defun(Abs) { + hll_return(abs(hll_arg(0).i)); +} + +hll_defun(AbsF) { + hll_return(fabsf(hll_arg(0).f)); +} + +hll_defun(Pow) { + hll_return(powf(hll_arg(0).f, hll_arg(1).f)); +} + +hll_defun(SetSeed) { + srand(hll_arg(0).i); + hll_return(0); +} + +hll_unimplemented(SetRandMode) + +hll_defun(Rand) { + hll_return(rand()); +} + +// TODO +hll_unimplemented(RandF) +hll_unimplemented(RandTableInit) +hll_unimplemented(RandTable) +hll_unimplemented(RandTable2Init) +hll_unimplemented(RandTable2) + +hll_defun(Min) { + int a = hll_arg(0).i, b = hll_arg(1).i; + hll_return(a < b ? a : b); +} + +hll_defun(MinF) { + float a = hll_arg(0).f, b = hll_arg(1).f; + hll_return(a < b ? a : b); +} + +hll_defun(Max) { + int a = hll_arg(0).i, b = hll_arg(1).i; + hll_return(a > b ? a : b); +} + +hll_defun(MaxF) { + float a = hll_arg(0).f, b = hll_arg(1).f; + hll_return(a > b ? a : b); +} + +hll_defun(Swap) { + int tmp = *hll_arg(0).iref; + *hll_arg(0).iref = *hll_arg(1).iref; + *hll_arg(1).iref = tmp; +} + +hll_defun(SwapF) { + float tmp = *hll_arg(0).fref; + *hll_arg(0).fref = *hll_arg(1).fref; + *hll_arg(1).fref = tmp; +} + +hll_deflib(Math) { + hll_export(Cos), + hll_export(Sin), + hll_export(Sqrt), + hll_export(Atan), + hll_export(Atan2), + hll_export(Abs), + hll_export(AbsF), + hll_export(Pow), + hll_export(SetSeed), + hll_export(SetRandMode), + hll_export(Rand), + hll_export(RandF), + hll_export(RandTableInit), + hll_export(RandTable), + hll_export(RandTable2Init), + hll_export(RandTable2), + hll_export(Min), + hll_export(MinF), + hll_export(Max), + hll_export(MaxF), + hll_export(Swap), + hll_export(SwapF), + NULL +}; diff --git a/instructions.c b/instructions.c index e0cc999..c4b6c90 100644 --- a/instructions.c +++ b/instructions.c @@ -136,7 +136,7 @@ const struct instruction instructions[NR_OPCODES] = { OP(C_REF, 0), OP(C_ASSIGN, 0), OP(MSG, 0), // TODO - OP(CALLHLL, 0), // TODO + OP(CALLHLL, 2, INSTR_INT, INSTR_INT), OP(PUSHSTRUCTPAGE, 0), JMP(CALLMETHOD, 1, INSTR_FUN), OP(SH_GLOBALREF, 1, INSTR_INT), diff --git a/meson.build b/meson.build index c856e91..b841ccc 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,7 @@ project('system4', 'c', default_options : ['c_std=c11']) -system4 = ['system4.c', 'ain.c', 'instructions.c', 'vm.c', 'string.c', 'page.c', 'utfsjis.c'] +system4 = ['system4.c', 'ain.c', 'instructions.c', 'vm.c', 'string.c', 'page.c', 'utfsjis.c', 'hll/math.c'] + zdep = dependency('zlib') -executable('system4', system4, dependencies: zdep) +mdep = meson.get_compiler('c').find_library('m', required: false) + +executable('system4', system4, dependencies: [mdep, zdep]) diff --git a/page.h b/page.h index 98465e9..f7d9ae6 100644 --- a/page.h +++ b/page.h @@ -59,8 +59,19 @@ struct page { union vm_value values[]; }; +static inline union vm_value page_get(int pageno, int varno) +{ + return heap[pageno].page->values[varno]; +} + +static inline void page_set(int pageno, int varno, union vm_value v) +{ + heap[pageno].page->values[varno] = v; +} + // variables union vm_value variable_initval(enum ain_data_type type); +void variable_fini(union vm_value v, enum ain_data_type type); enum ain_data_type variable_type(struct page *page, int varno, enum ain_data_type *struct_type); // pages diff --git a/test/.gitignore b/test/.gitignore index 8464f0b..165b766 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,4 +1,5 @@ OBJ/ Run/ +Source/System/Etc/DLL/Math.dll Source/System/System42/System40.exe -Source/System/System42/DLL/Sys42VM.dll \ No newline at end of file +Source/System/System42/DLL/Sys42VM.dll diff --git a/test/Source/System/Etc/Etc.inc b/test/Source/System/Etc/Etc.inc new file mode 100755 index 0000000..ba93512 --- /dev/null +++ b/test/Source/System/Etc/Etc.inc @@ -0,0 +1,12 @@ + +SystemSource = { + "HLL\Math.hll", "Math", +} + +CopyToRun = { + "SACT_DLL.inc", +} + +CopyToDLL = { + "DLL\Math.dll", +} diff --git a/test/Source/System/Etc/HLL/Math.hll b/test/Source/System/Etc/HLL/Math.hll new file mode 100755 index 0000000..10b64a1 --- /dev/null +++ b/test/Source/System/Etc/HLL/Math.hll @@ -0,0 +1,50 @@ +//////////////////////////////////////////////////////////// +// Math.hll + +//////////////////////////////////////////////////////////// +// 数学 +float Cos(float fDeg); // コサイン(角度) +float Sin(float fDeg); // サイン(角度) + +float Sqrt(float fValue); // 平方根 + +float Atan(float x); // アークタンジェント +float Atan2(float y, float x); // アークタンジェント y/x + +int Abs(int nValue); // 絶対値を得る +float AbsF(float fValue); // 絶対値float + +float Pow(float fBase, float fExponent); // fBaseのfExponet乗を返します + + +//////////////////////////////////////////////////////////// +// 乱数 +void SetSeed(int nSeed); // 乱数の種セット +void SetRandMode(int nMode); // 乱数のモードをセット(0:標準(default) 1:正規分布 2:1/f分布) +int Rand(void); // 0〜0x7FFFFFFFの範囲で乱数生成 +float RandF(void); // 0〜1の範囲で乱数生成 + +// テーブルを作成して、その中身をシャッフルすることで +// ランダムを取り出す関数、使う前に、SetSeedは必要。 +void RandTableInit(int nNum, int nSize); // nSize個のランダムテーブルを作成、0〜nSize-1 までの数値が入る +int RandTable(int nNum); // テーブルの数値を取得 nNumはテーブル番号 + +// ランダムテーブル、VM内の配列を使う版 ※配列の要素数が、そのままテーブルの大きさになります。 +// pArrayはテーブル配列を指定(Allocはスクリプトで確保すること) +void RandTable2Init(int nNum, array@int pArray); + +// テーブルの数値を取得 nNumはテーブル番号 +int RandTable2(int nNum); + + +//////////////////////////////////////////////////////////// +// その他 +int Min(int x, int y); +float MinF(float x, float y); +int Max(int x, int y); +float MaxF(float x, float y); + +void Swap(intp x, intp y); // 入れ替え +void SwapF(floatp x, floatp y); + + diff --git a/test/Source/System/Etc/SACT_DLL.inc b/test/Source/System/Etc/SACT_DLL.inc new file mode 100755 index 0000000..5f640f0 --- /dev/null +++ b/test/Source/System/Etc/SACT_DLL.inc @@ -0,0 +1,3 @@ +LoadDLL = { + "Math.dll", +} diff --git a/test/Source/System/README.md b/test/Source/System/README.md new file mode 100755 index 0000000..01c31c8 --- /dev/null +++ b/test/Source/System/README.md @@ -0,0 +1,18 @@ +Note +---- + +I am not 100% sure about the legality of distributing System40.exe and related +DLLs here, so I've left them out. They can be found in the System 4 SDK, which +which you'll want to have anyways to compile these tests. + +Once you've copied the files, this directory should contain the following: + + Etc/DLL/Math.dll (not included) + Etc/HLL/Math.hll + Etc/Etc.inc + Etc/SACT_DLL.inc + System42/DLL/Sys42VM.dll (not included) + System42/System40.exe (not included) + System42/System40.ini + System42/System42.inc + System.inc diff --git a/test/Source/System/System.inc b/test/Source/System/System.inc index bf04922..593fe4e 100755 --- a/test/Source/System/System.inc +++ b/test/Source/System/System.inc @@ -1,3 +1,4 @@ Source = { "System42\System42.inc", + "Etc\Etc.inc", } diff --git a/test/Source/System/System42/README.md b/test/Source/System/System42/README.md deleted file mode 100755 index 90e11ef..0000000 --- a/test/Source/System/System42/README.md +++ /dev/null @@ -1,12 +0,0 @@ -Note ----- - -I am not 100% sure about the legality of distributing System40.exe and -Sys42VM.dll here, so I've left them out. They can be found in the System 4 SDK, -which you'll want to have anyways to compile these tests. - -Once you've copied the files, this directory should contain the following: - - System40.exe - System40.ini - DLL\Sys42VM.dll diff --git a/test/Source/System/System42/System40.ini b/test/Source/System/System42/System40.ini index 0bfdf94..93669fb 100755 --- a/test/Source/System/System42/System40.ini +++ b/test/Source/System/System42/System40.ini @@ -26,6 +26,11 @@ SaveFolder = "SaveData" ; DLLフォルダ設定 DLL = "DLL" +; DLLファイル設定 +LoadDLL = { + "SACT_DLL.inc", +} + ; スキップ名設定 MsgSkipName = "既読スキップ" diff --git a/test/Source/System/System42/System42.inc b/test/Source/System/System42/System42.inc index 1d95380..6b83820 100755 --- a/test/Source/System/System42/System42.inc +++ b/test/Source/System/System42/System42.inc @@ -1,7 +1,7 @@ CopyToRun = { "System40.exe", - "System40.ini" + "System40.ini", } CopyToDLL = { diff --git a/test/Source/main.jaf b/test/Source/main.jaf index baf9ee4..411d234 100755 --- a/test/Source/main.jaf +++ b/test/Source/main.jaf @@ -31,6 +31,17 @@ void test_equal(string msg, int actual, int expected) } } +void test_float(string msg, float actual, float expected) +{ + float d = actual - expected; + if (d > 0.001 || d < -0.001) { + system.Output("FAIL: " + msg + " (expected " + string(expected) + "; got " + string(actual) + ")\n"); + tests_failed++; + } else { + tests_passed++; + } +} + void test_bool(string msg, bool actual, bool expected) { if (actual != expected) { @@ -73,6 +84,10 @@ int main(void) test_arrays(); test_end("arrays"); + test_start("Math.dll"); + test_math(); + test_end("Math.dll"); + if (total_failed > 0) { system.Output(string(total_failed) + " tests failed.\n"); } else { diff --git a/test/Source/math.jaf b/test/Source/math.jaf new file mode 100755 index 0000000..f938953 --- /dev/null +++ b/test/Source/math.jaf @@ -0,0 +1,32 @@ +// -*-mode: C; coding: sjis; -*- + +void test_math(void) +{ + int ia, ib; + float fa, fb; + test_float("Math.Cos(40)", Math.Cos(40), 0.766044); + test_float("Math.Sin(40)", Math.Sin(40), 0.642788); + test_float("Math.Atan(40)", Math.Atan(40), 1.545802); + test_float("Math.Atan2(20, 40)", Math.Atan2(20, 40), 0.463648); + test_float("Math.Pow(0.5, 1.5)", Math.Pow(0.5, 1.5), 0.353553); + test_equal("Math.Abs(-3)", Math.Abs(-3), 3); + test_float("Math.AbsF(-3.5)", Math.Abs(-3.5), 3.0); // XXX: why not 3.5? + test_equal("Math.Min(1, 2)", Math.Min(1, 2), 1); + test_equal("Math.Min(2, 1)", Math.Min(2, 1), 1); + test_float("Math.MinF(1.0, 2.0)", Math.MinF(1.0, 2.0), 1.0); + test_float("Math.MinF(2.0, 1.0)", Math.MinF(2.0, 1.0), 1.0); + test_equal("Math.Max(1, 2)", Math.Max(1, 2), 2); + test_equal("Math.Max(2, 1)", Math.Max(2, 1), 2); + test_float("Math.MaxF(1.0, 2.0)", Math.MaxF(1.0, 2.0), 2.0); + test_float("Math.MaxF(2.0, 1.0)", Math.MaxF(2.0, 1.0), 2.0); + + ia = 1, ib = 2; + Math.Swap(ia, ib); + test_equal("Math.Swap(a, b), a", ia, 2); + test_equal("Math.Swap(a, b), b", ib, 1); + + fa = 1.0, fb = 2.0; + Math.SwapF(fa, fb); + test_float("Math.SwapF(a, b), a", fa, 2.0); + test_float("Math.SwapF(a, b), b", fb, 1.0); +} diff --git a/test/Source/test.inc b/test/Source/test.inc index 2805686..5ae260a 100755 --- a/test/Source/test.inc +++ b/test/Source/test.inc @@ -4,6 +4,7 @@ Source = { "arithmetic.jaf", "strings.jaf", "structs.jaf", -"arrays.jaf" +"arrays.jaf", +"math.jaf" } diff --git a/vm.c b/vm.c index 1f44724..671370b 100644 --- a/vm.c +++ b/vm.c @@ -305,6 +305,49 @@ void vm_call(int fno, int struct_page) instr_ptr = saved_ip; } +static void hll_call(int libno, int fno) +{ + struct ain_hll_function *f = &ain->libraries[libno].functions[fno]; + if (!f->fun) + EXECUTION_ERROR("Unimplemented HLL function: %s", f->name); + + // convert non-heap ref types to pointers + for (int i = f->nr_arguments - 1; i >= 0; i--) { + switch(f->arguments[i].data_type) { + case AIN_REF_INT: + case AIN_REF_BOOL: + case AIN_REF_FLOAT: + stack_ptr -= 2; + break; + default: + stack_ptr--; + } + } + for (int i = 0, src = 0; i < f->nr_arguments; i++, src++) { + int pageno, varno; + switch (f->arguments[i].data_type) { + case AIN_REF_INT: + case AIN_REF_BOOL: + pageno = stack[stack_ptr+src].i; + varno = stack[stack_ptr+src+1].i; + stack[stack_ptr+i].iref = &heap[pageno].page->values[varno].i; + src++; + break; + case AIN_REF_FLOAT: + pageno = stack[stack_ptr+src].i; + varno = stack[stack_ptr+src+1].i; + stack[stack_ptr+i].fref = &heap[pageno].page->values[varno].f; + src++; + break; + } + } + + f->fun(); + for (int i = 0; i < f->nr_arguments; i++) { + variable_fini(stack[stack_ptr + i], f->arguments[i].data_type); + } +} + static void function_return(void) { call_stack_ptr--; @@ -522,6 +565,9 @@ static void execute_instruction(int16_t opcode) case CALLMETHOD: method_call(get_argument(0), instr_ptr + instruction_width(CALLMETHOD)); break; + case CALLHLL: + hll_call(get_argument(0), get_argument(1)); + break; case RETURN: function_return(); break; @@ -1028,6 +1074,44 @@ static void vm_execute(void) } } +extern struct library lib_Math; + +struct library *libraries[] = { + &lib_Math, + NULL +}; + +static void link_library(struct ain_library *ainlib, struct library *lib) +{ + for (int i = 0; i < ainlib->nr_functions; i++) { + bool linked = false; + for (int j = 0; lib->functions[j]; j++) { + if (!strcmp(ainlib->functions[i].name, lib->functions[j]->name)) { + ainlib->functions[i].fun = lib->functions[j]->fun; + linked = true; + break; + } + } + if (!linked) + WARNING("Unimplemented library function: %s", ainlib->functions[i].name); + } +} + +static void link_libraries(void) +{ + for (int i = 0; i < ain->nr_libraries; i++) { + bool linked = false; + for (int j = 0; libraries[j]; j++) { + if (!strcmp(ain->libraries[i].name, libraries[j]->name)) { + link_library(&ain->libraries[i], libraries[j]); + linked = true; + break; + } + } + if (!linked) + WARNING("Unimplemented library: %s", ain->libraries[i].name); + } +} void vm_execute_ain(struct ain *program) { @@ -1077,6 +1161,7 @@ void vm_execute_ain(struct ain *program) } } + link_libraries(); vm_call(ain->main, -1); } diff --git a/vm.h b/vm.h index e22713b..0268578 100644 --- a/vm.h +++ b/vm.h @@ -25,6 +25,9 @@ union vm_value { int32_t i; int64_t i64; float f; + // HLL only + int *iref; + float *fref; }; enum vm_pointer_type { @@ -48,6 +51,16 @@ struct vm_pointer { }; }; +struct hll_function { + char *name; + void (*fun)(void); +}; + +struct library { + char *name; + struct hll_function **functions; +}; + struct ain; struct vm_pointer *heap;