From 4f537d4a7169667624ed12a3fa4d4c85fa9befd5 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Tue, 16 Jan 2024 19:24:02 -0500 Subject: [PATCH] [jak3] Set up ckernel (#3308) This sets up the C Kernel for Jak 3, and makes it possible to build and load code built with `goalc --jak3`. There's not too much interesting here, other than they switched to a system where symbol IDs (unique numbers less than 2^14) are generated at compile time, and those get included in the object file itself. This is kind of annoying, since it means all tools that produce a GOAL object file need to work together to assign unique symbol IDs. And since the symbol IDs can't conflict, and are only a number between 0 and 2^14, you can't just hash and hope for no collisions. We work around this by ignoring the IDs and re-assigning our own. I think this is very similar to what the C Kernel did on early builds of Jak 3 which supported loading old format level files, which didn't have the IDs included. As far as I can tell, this shouldn't cause any problems. It defeats all of their fancy tricks to save memory by not storing the symbol string, but we don't care. --- common/formatter/formatter.cpp | 2 +- common/formatter/rules/rule_config.cpp | 2 +- common/goal_constants.h | 8 +- common/link_types.h | 15 + common/symbols.h | 174 ++ common/versions/versions.h | 1 + .../ObjectFile/LinkedObjectFileCreation.cpp | 2 +- decompiler/ObjectFile/ObjectFileDB_IR2.cpp | 2 +- decompiler/config/jak3/all-types.gc | 258 +-- decompiler/config/jak3/ntsc_v1/hacks.jsonc | 1 + .../config/jak3/ntsc_v1/type_casts.jsonc | 38 +- decompiler/util/type_utils.h | 4 +- game/CMakeLists.txt | 15 +- game/common/dgo_rpc_types.h | 17 +- game/common/game_common_types.h | 2 +- game/common/loader_rpc_types.h | 3 +- game/common/play_rpc_types.h | 3 +- game/common/player_rpc_types.h | 3 +- game/common/ramdisk_rpc_types.h | 3 +- game/common/str_rpc_types.h | 3 +- game/kernel/common/Ptr.h | 1 + game/kernel/common/Symbol4.h | 8 +- game/kernel/common/fileio.cpp | 1 + game/kernel/common/fileio.h | 1 + game/kernel/common/kdgo.cpp | 80 +- game/kernel/common/kdgo.h | 4 - game/kernel/common/klink.cpp | 10 +- game/kernel/common/klink.h | 29 +- game/kernel/common/kmachine.cpp | 7 - game/kernel/common/kmalloc.cpp | 29 + game/kernel/common/kmalloc.h | 1 + game/kernel/common/kprint.cpp | 3 + game/kernel/common/memory_layout.h | 4 + game/kernel/jak1/kdgo.cpp | 81 + game/kernel/jak1/kdgo.h | 5 + game/kernel/jak1/klink.cpp | 6 +- game/kernel/jak1/kmachine.cpp | 6 - game/kernel/jak2/fileio.cpp | 2 +- game/kernel/jak2/kdgo.cpp | 81 + game/kernel/jak2/kdgo.h | 6 + game/kernel/jak2/klink.cpp | 6 +- game/kernel/jak2/kmachine.cpp | 9 +- game/kernel/jak2/kscheme.cpp | 17 +- game/kernel/jak2/kscheme.h | 6 + game/kernel/jak2/ksound.cpp | 1 - game/kernel/jak3/fileio.cpp | 254 +++ game/kernel/jak3/fileio.h | 6 + game/kernel/jak3/kboot.cpp | 163 ++ game/kernel/jak3/kboot.h | 11 + game/kernel/jak3/kdgo.cpp | 210 ++ game/kernel/jak3/kdgo.h | 20 + game/kernel/jak3/kdsnetm.cpp | 3 + game/kernel/jak3/kdsnetm.h | 3 + game/kernel/jak3/klink.cpp | 656 ++++++ game/kernel/jak3/klink.h | 20 + game/kernel/jak3/klisten.cpp | 118 + game/kernel/jak3/klisten.h | 20 + game/kernel/jak3/kmachine.cpp | 513 +++++ game/kernel/jak3/kmachine.h | 8 + game/kernel/jak3/kmalloc.cpp | 16 + game/kernel/jak3/kmalloc.h | 9 + game/kernel/jak3/kmemcard.cpp | 3 + game/kernel/jak3/kmemcard.h | 3 + game/kernel/jak3/kprint.cpp | 564 +++++ game/kernel/jak3/kprint.h | 12 + game/kernel/jak3/kscheme.cpp | 1986 +++++++++++++++++ game/kernel/jak3/kscheme.h | 74 + game/kernel/jak3/ksocket.cpp | 3 + game/kernel/jak3/ksocket.h | 3 + game/kernel/jak3/ksound.cpp | 15 + game/kernel/jak3/ksound.h | 5 + game/kernel/todo.txt | 29 - game/main.cpp | 5 +- game/overlord/jak2/vag.cpp | 4 +- game/runtime.cpp | 51 +- game/sound/989snd/loader.cpp | 20 +- game/system/hid/input_bindings.cpp | 4 +- game/system/vm/dmac.cpp | 32 - game/system/vm/dmac.h | 73 - game/system/vm/vm.cpp | 140 -- game/system/vm/vm.h | 32 - goal_src/jak3/compiler-setup.gc | 2 +- goal_src/jak3/kernel-defs.gc | 2 + goal_src/jak3/kernel/gcommon.gc | 1325 +++++++++++ goal_src/jak3/kernel/gkernel-h.gc | 612 +++++ goal_src/jak3/kernel/gkernel.gc | 21 + goalc/emitter/ObjectGenerator.cpp | 5 +- test/goalc/test_type_consistency.cpp | 2 +- 88 files changed, 7364 insertions(+), 653 deletions(-) create mode 100644 game/kernel/jak3/fileio.cpp create mode 100644 game/kernel/jak3/fileio.h create mode 100644 game/kernel/jak3/kboot.cpp create mode 100644 game/kernel/jak3/kboot.h create mode 100644 game/kernel/jak3/kdgo.cpp create mode 100644 game/kernel/jak3/kdgo.h create mode 100644 game/kernel/jak3/kdsnetm.cpp create mode 100644 game/kernel/jak3/kdsnetm.h create mode 100644 game/kernel/jak3/klink.cpp create mode 100644 game/kernel/jak3/klink.h create mode 100644 game/kernel/jak3/klisten.cpp create mode 100644 game/kernel/jak3/klisten.h create mode 100644 game/kernel/jak3/kmachine.cpp create mode 100644 game/kernel/jak3/kmachine.h create mode 100644 game/kernel/jak3/kmalloc.cpp create mode 100644 game/kernel/jak3/kmalloc.h create mode 100644 game/kernel/jak3/kmemcard.cpp create mode 100644 game/kernel/jak3/kmemcard.h create mode 100644 game/kernel/jak3/kprint.cpp create mode 100644 game/kernel/jak3/kprint.h create mode 100644 game/kernel/jak3/kscheme.cpp create mode 100644 game/kernel/jak3/kscheme.h create mode 100644 game/kernel/jak3/ksocket.cpp create mode 100644 game/kernel/jak3/ksocket.h create mode 100644 game/kernel/jak3/ksound.cpp create mode 100644 game/kernel/jak3/ksound.h delete mode 100644 game/kernel/todo.txt delete mode 100644 game/system/vm/dmac.cpp delete mode 100644 game/system/vm/dmac.h delete mode 100644 game/system/vm/vm.cpp delete mode 100644 game/system/vm/vm.h diff --git a/common/formatter/formatter.cpp b/common/formatter/formatter.cpp index 46202deb97..24dd9c8e94 100644 --- a/common/formatter/formatter.cpp +++ b/common/formatter/formatter.cpp @@ -191,7 +191,7 @@ bool can_node_be_inlined(const FormatterTreeNode& curr_node, int cursor_pos) { } std::vector apply_formatting(const FormatterTreeNode& curr_node, - std::vector output = {}, + std::vector /*output*/ = {}, int cursor_pos = 0) { using namespace formatter_rules; if (!curr_node.token && curr_node.refs.empty()) { diff --git a/common/formatter/rules/rule_config.cpp b/common/formatter/rules/rule_config.cpp index 4b57816559..e8aa1e15dc 100644 --- a/common/formatter/rules/rule_config.cpp +++ b/common/formatter/rules/rule_config.cpp @@ -9,7 +9,7 @@ namespace config { FormFormattingConfig new_flow_rule(int start_index) { FormFormattingConfig cfg; cfg.hang_forms = false; - cfg.inline_until_index = [start_index](const std::vector& curr_lines) { + cfg.inline_until_index = [start_index](const std::vector& /*curr_lines*/) { return start_index; }; return cfg; diff --git a/common/goal_constants.h b/common/goal_constants.h index 6c85878213..22ba953275 100644 --- a/common/goal_constants.h +++ b/common/goal_constants.h @@ -62,15 +62,9 @@ constexpr int LEVEL_MAX = 6; constexpr int LEVEL_TOTAL = LEVEL_MAX + 1; } // namespace jak2 -// TODO copypaste from jak 2 for now namespace jak3 { // for now, we don't have the ability to extend the size of the symbol table constexpr s32 GOAL_MAX_SYMBOLS = 0x4000; -constexpr s32 SYM_TABLE_MEM_SIZE = 0x30000; -// from the "off-by-one" symbol pointer -constexpr int SYM_TO_STRING_OFFSET = 0xff37; -constexpr int SYM_TO_HASH_OFFSET = 0x1fe6f; - // amount of levels in level heap constexpr int LEVEL_MAX = 10; // total amount of levels, including ones outside level heap (default-level) @@ -84,7 +78,7 @@ constexpr s32 max_symbols(GameVersion version) { case GameVersion::Jak2: return jak2::GOAL_MAX_SYMBOLS; case GameVersion::Jak3: - return jak2::GOAL_MAX_SYMBOLS; + return jak3::GOAL_MAX_SYMBOLS; } } diff --git a/common/link_types.h b/common/link_types.h index d1b9647985..4feae73d8e 100644 --- a/common/link_types.h +++ b/common/link_types.h @@ -54,6 +54,21 @@ struct LinkHeaderV4 { uint32_t code_size; // length of object data before link data starts }; +struct LinkHeaderV5Core { + uint32_t length_to_get_to_code; // 4 length.. of link data? + uint16_t version; // 8 + uint16_t unknown; // 10 + uint32_t length_to_get_to_link; // 12 + uint32_t link_length; // 16 + uint8_t n_segments; // 20 + char name[59]; // 21 (really??) +}; + +struct LinkHeaderV5 { + uint32_t type_tag; // 0 always 0? + LinkHeaderV5Core core; +}; + // when a u32/s32 symbol link contains this value, (s7 + ) should be a 4-byte aligned address, // not including the 1 byte symbol offset. (no effect in jak 1). constexpr u32 LINK_SYM_NO_OFFSET_FLAG = 0xbadbeef; \ No newline at end of file diff --git a/common/symbols.h b/common/symbols.h index 13f527f112..7877b518cd 100644 --- a/common/symbols.h +++ b/common/symbols.h @@ -150,6 +150,180 @@ constexpr int FIX_FIXED_SYM_END_OFFSET = 0xec; } // namespace jak2_symbols +namespace jak3_symbols { + +constexpr int FIX_SYM_EMPTY_CAR = -0x8; +constexpr int S7_OFF_FIX_SYM_EMPTY_PAIR = -0x6 - 1; +constexpr int FIX_SYM_EMPTY_CDR = -0x4; +constexpr int FIX_SYM_FALSE = 0x0; // GOAL boolean #f (note that this is equal to the $s7 register) +constexpr int FIX_SYM_TRUE = 0x4; // GOAL boolean #t +constexpr int FIX_SYM_FUNCTION_TYPE = 0x8; +constexpr int FIX_SYM_BASIC = 0xc; +constexpr int FIX_SYM_STRING_TYPE = 0x10; +constexpr int FIX_SYM_SYMBOL_TYPE = 0x14; +constexpr int FIX_SYM_TYPE_TYPE = 0x18; +constexpr int FIX_SYM_OBJECT_TYPE = 0x1c; +constexpr int FIX_SYM_LINK_BLOCK = 0x20; +constexpr int FIX_SYM_INTEGER = 0x24; +constexpr int FIX_SYM_SINTEGER = 0x28; +constexpr int FIX_SYM_UINTEGER = 0x2c; +constexpr int FIX_SYM_BINTEGER = 0x30; +constexpr int FIX_SYM_INT8 = 0x34; +constexpr int FIX_SYM_INT16 = 0x38; +constexpr int FIX_SYM_INT32 = 0x3c; +constexpr int FIX_SYM_INT64 = 0x40; +constexpr int FIX_SYM_INT128 = 0x44; +constexpr int FIX_SYM_UINT8 = 0x48; +constexpr int FIX_SYM_UINT16 = 0x4c; +constexpr int FIX_SYM_UINT32 = 0x50; +constexpr int FIX_SYM_UINT64 = 0x54; +constexpr int FIX_SYM_UINT128 = 0x58; +constexpr int FIX_SYM_FLOAT = 0x5c; +constexpr int FIX_SYM_PROCESS_TREE = 0x60; +constexpr int FIX_SYM_PROCESS_TYPE = 0x64; +constexpr int FIX_SYM_THREAD = 0x68; +constexpr int FIX_SYM_STRUCTURE = 0x6c; +constexpr int FIX_SYM_PAIR_TYPE = 0x70; +constexpr int FIX_SYM_POINTER = 0x74; +constexpr int FIX_SYM_NUMBER = 0x78; +constexpr int FIX_SYM_ARRAY = 0x7c; +constexpr int FIX_SYM_VU_FUNCTION = 0x80; +constexpr int FIX_SYM_CONNECTABLE = 0x84; +constexpr int FIX_SYM_STACK_FRAME = 0x88; +constexpr int FIX_SYM_FILE_STREAM = 0x8c; +constexpr int FIX_SYM_HEAP = 0x90; +constexpr int FIX_SYM_NOTHING_FUNC = 0x94; +constexpr int FIX_SYM_DELETE_BASIC = 0x98; +constexpr int FIX_SYM_STATIC = 0x9c; +constexpr int FIX_SYM_GLOBAL_HEAP = 0xa0; +constexpr int FIX_SYM_DEBUG = 0xa4; +constexpr int FIX_SYM_LOADING_LEVEL = 0xa8; +constexpr int FIX_SYM_LOADING_PACKAGE = 0xac; +constexpr int FIX_SYM_PROCESS_LEVEL_HEAP = 0xb0; +constexpr int FIX_SYM_STACK = 0xb4; +constexpr int FIX_SYM_SCRATCH = 0xb8; +constexpr int FIX_SYM_SCRATCH_TOP = 0xbc; +constexpr int FIX_SYM_ZERO_FUNC = 0xc0; +constexpr int FIX_SYM_ASIZE_OF_BASIC_FUNC = 0xc4; +constexpr int FIX_SYM_COPY_BASIC_FUNC = 0xc8; // bugged name +constexpr int FIX_SYM_LEVEL = 0xcc; +constexpr int FIX_SYM_ART_GROUP = 0xd0; +constexpr int FIX_SYM_TEXTURE_PAGE_DIR = 0xd4; +constexpr int FIX_SYM_TEXTURE_PAGE = 0xd8; +constexpr int FIX_SYM_SOUND = 0xdc; +constexpr int FIX_SYM_DGO = 0xe0; +constexpr int FIX_SYM_TOP_LEVEL = 0xe4; +constexpr int FIX_SYM_QUOTE = 0xe8; +constexpr int FIX_SYM_LISTENER_LINK_BLOCK = 0xec; +constexpr int FIX_SYM_LISTENER_FUNCTION = 0xf0; +constexpr int FIX_SYM_STACK_TOP = 0xf4; +constexpr int FIX_SYM_STACK_BASE = 0xf8; +constexpr int FIX_SYM_STACK_SIZE = 0xfc; +constexpr int FIX_SYM_KERNEL_FUNCTION = 0x100; +constexpr int FIX_SYM_KERNEL_PACKAGES = 0x104; +constexpr int FIX_SYM_KERNEL_BOOT_MESSAGE = 0x108; +constexpr int FIX_SYM_KERNEL_BOOT_MODE = 0x10c; +constexpr int FIX_SYM_KERNEL_BOOT_LEVEL = 0x110; +constexpr int FIX_SYM_KERNEL_BOOT_ART_GROUP = 0x114; +constexpr int FIX_SYM_KERNEL_DEBUG = 0x118; +constexpr int FIX_SYM_KERNEL_VERSION = 0x11c; +constexpr int FIX_SYM_KERNEL_DISPATCHER = 0x120; +constexpr int FIX_SYM_SYNC_DISPATCHER = 0x124; +constexpr int FIX_SYM_PRINT_COLLUMN = 0x128; +constexpr int FIX_SYM_DEBUG_SEGMENT = 300; +constexpr int FIX_SYM_ENABLE_METHOD_SET = 0x130; +constexpr int FIX_SYM_SQL_RESULT = 0x134; +constexpr int FIX_SYM_COLLAPSE_QUOTE = 0x138; +constexpr int FIX_SYM_LEVEL_TYPE_LIST = 0x13C; +constexpr int FIX_SYM_DECI_COUNT = 0x140; +constexpr int FIX_SYM_USER = 0x144; +constexpr int FIX_SYM_VIDEO_MODE = 0x148; +constexpr int FIX_SYM_BOOT_VIDEO_MODE = 0x14C; +constexpr int FIX_SYM_BOOT = 0x150; +constexpr int FIX_SYM_DEMO = 0x154; +constexpr int FIX_SYM_DEMO_SHARED = 0x158; +constexpr int FIX_SYM_PREVIEW = 0x15C; +constexpr int FIX_SYM_KIOSK = 0x160; +constexpr int FIX_SYM_PLAY_BOOT = 0x164; +constexpr int FIX_SYM_SIN = 0x168; +constexpr int FIX_SYM_COS = 0x16C; +constexpr int FIX_SYM_PUT_DISPLAY_ENV = 0x170; +constexpr int FIX_SYM_SYNCV = 0x174; +constexpr int FIX_SYM_SYNC_PATH = 0x178; +constexpr int FIX_SYM_RESET_PATH = 0x17C; +constexpr int FIX_SYM_RESET_GRAPH = 0x180; +constexpr int FIX_SYM_DMA_SYNC = 0x184; +constexpr int FIX_SYM_GS_PUT_IMR = 0x188; +constexpr int FIX_SYM_GS_GET_IMR = 0x18C; +constexpr int FIX_SYM_GS_STORE_IMAGE = 400; +constexpr int FIX_SYM_FLUSH_CACHE = 0x194; +constexpr int FIX_SYM_CPAD_OPEN = 0x198; +constexpr int FIX_SYM_CPAD_GET_DATA = 0x19C; +constexpr int FIX_SYM_MOUSE_GET_DATA = 0x1A0; +constexpr int FIX_SYM_KEYBD_GET_DATA = 0x1A4; +constexpr int FIX_SYM_INSTALL_HANDLER = 0x1A8; +constexpr int FIX_SYM_INSTALL_DEBUG_HANDLER = 0x1AC; +constexpr int FIX_SYM_FILE_STREAM_OPEN = 0x1B0; +constexpr int FIX_SYM_FILE_STREAM_CLOSE = 0x1B4; +constexpr int FIX_SYM_FILE_STREAM_LENGTH = 0x1B8; +constexpr int FIX_SYM_FILE_STREAM_SEEK = 0x1BC; +constexpr int FIX_SYM_FILE_STREAM_READ = 0x1C0; +constexpr int FIX_SYM_FILE_STREAM_WRITE = 0x1C4; +constexpr int FIX_SYM_SCF_GET_LANGUAGE = 0x1C8; +constexpr int FIX_SYM_SCF_GET_TIME = 0x1CC; +constexpr int FIX_SYM_SCF_GET_ASPECT = 0x1D0; +constexpr int FIX_SYM_SCF_GET_VOLUME = 0x1D4; +constexpr int FIX_SYM_SCF_GET_TERRITORY = 0x1D8; +constexpr int FIX_SYM_SCF_GET_TIMEOUT = 0x1DC; +constexpr int FIX_SYM_SCF_GET_INACTIVE_TIMEOUT = 0x1E0; +constexpr int FIX_SYM_DMA_TO_IOP = 0x1E4; +constexpr int FIX_SYM_KERNEL_SHUTDOWN = 0x1E8; +constexpr int FIX_SYM_AYBABTU = 0x1EC; +constexpr int FIX_SYM_STRING_TO_SYMBOL = 0x1F0; +constexpr int FIX_SYM_SYMBOL_TO_STRING = 500; +constexpr int FIX_SYM_PRINT = 0x1F8; +constexpr int FIX_SYM_INSPECT = 0x1FC; +constexpr int FIX_SYM_LOAD = 0x200; +constexpr int FIX_SYM_LOADB = 0x204; +constexpr int FIX_SYM_LOADO = 0x208; +constexpr int FIX_SYM_UNLOAD = 0x20C; +constexpr int FIX_SYM_FORMAT = 0x210; +constexpr int FIX_SYM_MALLOC = 0x214; +constexpr int FIX_SYM_KMALLOC = 0x218; +constexpr int FIX_SYM_KMEMOPEN = 0x21C; +constexpr int FIX_SYM_KMEMCLOSE = 0x220; +constexpr int FIX_SYM_NEW_DYNAMIC_STRUCTURE = 0x224; +constexpr int FIX_SYM_METHOD_SET = 0x228; +constexpr int FIX_SYM_LINK = 0x22C; +constexpr int FIX_SYM_LINK_BUSY = 0x230; +constexpr int FIX_SYM_LINK_RESET = 0x234; +constexpr int FIX_SYM_LINK_BEGIN = 0x238; +constexpr int FIX_SYM_LINK_RESUME = 0x23C; +constexpr int FIX_SYM_DGO_LOAD = 0x240; +constexpr int FIX_SYM_SQL_QUERY = 0x244; +constexpr int FIX_SYM_MC_RUN = 0x248; +constexpr int FIX_SYM_MC_FORMAT = 0x24C; +constexpr int FIX_SYM_MC_UNFORMAT = 0x250; +constexpr int FIX_SYM_MC_CREATE_FILE = 0x254; +constexpr int FIX_SYM_MC_SAVE = 600; +constexpr int FIX_SYM_MC_LOAD = 0x25C; +constexpr int FIX_SYM_MC_CHECK_RESULT = 0x260; +constexpr int FIX_SYM_MC_GET_SLOT_INFO = 0x264; +constexpr int FIX_SYM_MC_MAKEFILE = 0x268; +constexpr int FIX_SYM_KSET_LANGUAGE = 0x26C; +constexpr int FIX_SYM_RPC_CALL = 0x270; +constexpr int FIX_SYM_RPC_BUSY = 0x274; +constexpr int FIX_SYM_TEST_LOAD_DGO_C = 0x278; +constexpr int FIX_SYM_SYMLINK2 = 0x27c; +constexpr int FIX_SYM_SYMLINK3 = 0x280; +constexpr int FIX_SYM_ULTIMATE_MEMCPY = 0x284; +constexpr int FIX_SYM_PLAY = 0x288; +constexpr int FIX_SYM_SYMBOL_STRING = 0x28c; +constexpr int FIX_SYM_KERNEL_SYMBOL_WARNINGS = 0x290; +constexpr int FIX_SYM_NETWORK_BOOTSTRAP = 0x294; + +} // namespace jak3_symbols + constexpr int false_symbol_offset() { return jak1_symbols::FIX_SYM_FALSE; } diff --git a/common/versions/versions.h b/common/versions/versions.h index 17f35c6db9..7c58c6f23a 100644 --- a/common/versions/versions.h +++ b/common/versions/versions.h @@ -34,6 +34,7 @@ constexpr u32 TX_PAGE_VERSION = 8; namespace jak3 { constexpr u32 ART_FILE_VERSION = 8; constexpr u32 LEVEL_FILE_VERSION = 36; +constexpr u32 DGO_FILE_VERSION = 1; constexpr u32 TX_PAGE_VERSION = 8; } // namespace jak3 diff --git a/decompiler/ObjectFile/LinkedObjectFileCreation.cpp b/decompiler/ObjectFile/LinkedObjectFileCreation.cpp index f10f89899b..d0a5dfc24b 100644 --- a/decompiler/ObjectFile/LinkedObjectFileCreation.cpp +++ b/decompiler/ObjectFile/LinkedObjectFileCreation.cpp @@ -472,7 +472,7 @@ static void link_v5(LinkedObjectFile& f, for (int i = 0; i < n_segs; i++) { segment_data_offsets[i] = header->length_to_get_to_code + seg_info_array[i].data; segment_link_offsets[i] = header->length_to_get_to_link + seg_info_array[i].relocs; - ASSERT(seg_info_array[i].magic == 1); + ASSERT(seg_info_array[i].magic == 1); // if set, always use symlink2. } // check that the data region is filled diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 8cb0e4f29f..c961510a42 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -367,7 +367,7 @@ void ObjectFileDB::ir2_analyze_all_types(const fs::path& output_file, for (const auto& [obj_name, type_names] : all_type_names) { for (const auto& type_name : type_names) { if (str_util::starts_with(sym_name, type_name) && - type_name.length() > longest_match_length) { + (int)type_name.length() > longest_match_length) { longest_match_length = type_name.length(); longest_match = type_name; longest_match_object_name = obj_name; diff --git a/decompiler/config/jak3/all-types.gc b/decompiler/config/jak3/all-types.gc index 107b822544..9c4f2da9fa 100644 --- a/decompiler/config/jak3/all-types.gc +++ b/decompiler/config/jak3/all-types.gc @@ -1132,38 +1132,40 @@ (defenum process-mask :type uint32 :bitfield #t - (execute 0) + (execute 0) ;; 1 (freeze 1) (pause 2) (menu 3) (progress 4) - (actor-pause 5) - (sleep 6) - (sleep-code 7) - (process-tree 8) - (heap-shrunk 9) - (going 10) - (kernel-run 11) - (no-kill 12) - (movie 13) - (dark-effect 14) - (target 15) - (sidekick 16) - (crate 17) - (bit18 18) ;; unused? - (enemy 19) - (camera 20) - (platform 21) - (ambient 22) - (entity 23) - (projectile 24) - (bot 25) - (collectable 26) - (death 27) - (no-track 28) - (guard 29) - (vehicle 30) - (civilian 31)) + (actor-pause 5) ;; 32 + (sleep 6) ;; 64 + (sleep-code 7) ;; 128 + (process-tree 8) ;; 256 + (heap-shrunk 9) ;; 512 + (going 10) ;; 1024 + (kernel-run 11) ;; 2048 + (no-kill 12) ;; 4096 + (movie 13) ;; 8192 + (dark-effect 14) ;; 0x4000 + (target 15) ;; 0x8000 + + (sidekick 16) ;; 0x1'0000 + (crate 17) ;; 0x2'0000 + (collectable 18) ;; 0x4'0000 + (enemy 19) ;; 0x8'0000 + (camera 20) ;; 0x10'0000 + (platform 21) ;; 0x20'0000 + (ambient 22) ;; 0x40'0000 + (entity 23) ;; 0x80'0000 + (projectile 24) ;; 0x100'0000 + (bot 25) ;; 0x200'0000 + (death 26) ;; 0x400'0000 + (guard 27) ;; 0x800'0000 + (vehicle 28) ;; 0x1000'0000 + (civilian 29) ;; 0x2000'0000 + (kg-robot 30) ;; 0x4000'0000 + (metalhead 31) ;; 0x8000'0000 + ) (deftype process-tree (basic) ((name string :offset-assert 4) @@ -1180,11 +1182,11 @@ :flag-assert #xe00000024 :no-runtime-type (:methods - ;; (new (symbol type string) _type_ 0) - (process-tree-method-9 () none) ;; 9 ;; (activate (_type_ process-tree basic pointer) process-tree 9) - (process-tree-method-10 () none) ;; 10 ;; (deactivate (_type_) none 10) - (process-tree-method-11 () none) ;; 11 ;; (init-from-entity! (_type_ entity-actor) none 11) - (process-tree-method-12 () none) ;; 12 ;; (run-logic? (_type_) symbol 12) + (new (symbol type string) _type_) + (activate (_type_ process-tree basic pointer) process-tree) + (deactivate (_type_) none) + (init-from-entity! (_type_ entity-actor) none) + (run-logic? (_type_) symbol) (process-tree-method-13 () none) ;; 13 ;; (process-tree-method-13 () none 13) ) ) @@ -1210,7 +1212,7 @@ :method-count-assert 9 :size-assert #x40 :flag-assert #x900000040 - ;; field relocating-level uses ~A with a signed load. + ;; field relocating-level uses ~A with a signed load. ) |# @@ -1239,22 +1241,22 @@ :size-assert #x5c :flag-assert #x180000005c (:methods - ;; (new (symbol type int) _type_ 0) - (clock-method-9 () none) ;; 9 ;; (update-rates! (_type_ float) float 9) + (new (symbol type int) _type_) + (update-rates! (_type_ float) float) (clock-method-10 () none) ;; 10 ;; (advance-by! (_type_ float) clock 10) (clock-method-11 () none) ;; 11 ;; (tick! (_type_) clock 11) (clock-method-12 () none) ;; 12 ;; (save! (_type_ (pointer uint64)) int 12) (clock-method-13 () none) ;; 13 ;; (load! (_type_ (pointer uint64)) int 13) (clock-method-14 () none) ;; 14 ;; (reset! (_type_) none 14) (clock-method-15 () none) ;; 15 - (clock-method-16 () none) ;; 16 - (clock-method-17 () none) ;; 17 - (clock-method-18 () none) ;; 18 - (clock-method-19 () none) ;; 19 - (clock-method-20 () none) ;; 20 - (clock-method-21 () none) ;; 21 - (clock-method-22 () none) ;; 22 - (clock-method-23 () none) ;; 23 + (frame-mask-2 (_type_ int) symbol) + (frame-mask-4 (_type_ int) symbol) + (frame-mask-8 (_type_ int) symbol) + (frame-mask-16 (_type_ int) symbol) + (frame-mask-32 (_type_ int) symbol) + (frame-mask-64 (_type_ int) symbol) + (frame-mask-128 (_type_ int) symbol) + (frame-mask-256 (_type_ int) symbol) ) ) @@ -4067,7 +4069,7 @@ :method-count-assert 209 :size-assert #x4e0 :flag-assert #xd1046004e0 - ;; field gekko-flag is likely a value type. + ;; field gekko-flag is likely a value type. (:methods (gekko-method-202 () none) ;; 202 (gekko-method-203 () none) ;; 203 @@ -4793,7 +4795,7 @@ :method-count-assert 13 :size-assert #x30 :flag-assert #xd00000030 - ;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load. + ;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load. (:methods ;; (new (symbol type) _type_ 0) (editable-region-method-9 () none) ;; 9 ;; (editable-region-method-9 (_type_ editable-array int int) symbol 9) @@ -5256,7 +5258,7 @@ :method-count-assert 9 :size-assert #x28 :flag-assert #x900000028 - ;; field textures uses ~A with a signed load. + ;; field textures uses ~A with a signed load. ) |# @@ -6665,7 +6667,7 @@ :method-count-assert 9 :size-assert #x20 :flag-assert #x900000020 - ;; field speech-type-flag is likely a value type. + ;; field speech-type-flag is likely a value type. ) |# @@ -6699,7 +6701,7 @@ :method-count-assert 15 :size-assert #x64 :flag-assert #xf00000064 - ;; field speech-channel-flag is likely a value type. + ;; field speech-channel-flag is likely a value type. (:methods (speech-channel-method-9 () none) ;; 9 ;; (speech-channel-method-9 (_type_ process-drawable speech-type) none 9) (speech-channel-method-10 () none) ;; 10 ;; (speech-channel-method-10 (_type_ handle) none 10) @@ -6905,7 +6907,7 @@ :method-count-assert 216 :size-assert #x404 :flag-assert #xd803900404 - ;; field citizen-flag is likely a value type. + ;; field citizen-flag is likely a value type. (:methods (citizen-method-190 () none) ;; 190 ;; (citizen-method-190 (_type_ vector) none 190) (citizen-method-191 () none) ;; 191 ;; (gen-clear-path (_type_) nav-segment 191) @@ -7322,7 +7324,7 @@ :method-count-assert 155 :size-assert #x228 :flag-assert #x9b01b00228 - ;; field enemy-flag is likely a value type. field on-notice uses ~A with a signed load. field on-active uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-death uses ~A with a signed load. + ;; field enemy-flag is likely a value type. field on-notice uses ~A with a signed load. field on-active uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-death uses ~A with a signed load. (:methods (enemy-method-28 () none) ;; 28 ;; (dormant-aware () _type_ :state 28) (enemy-method-29 () none) ;; 29 ;; (hit () _type_ :state 29) @@ -8202,7 +8204,7 @@ :method-count-assert 12 :size-assert #x28 :flag-assert #xc00000028 - ;; field out uses ~A with a signed load. + ;; field out uses ~A with a signed load. (:methods ;; (new (symbol type uint) _type_ 0) (history-iterator-method-9 () none) ;; 9 ;; (frame-counter-delta (_type_ history-elt) time-frame 9) @@ -10303,7 +10305,7 @@ :method-count-assert 12 :size-assert #xa8 :flag-assert #xc000000a8 - ;; field handle is likely a value type. + ;; field handle is likely a value type. (:methods (attack-info-method-9 () none) ;; 9 ;; (attack-info-method-9 (_type_ attack-info process-drawable process-drawable) none 9) (attack-info-method-10 () none) ;; 10 ;; (compute-intersect-info (_type_ object process-drawable process touching-shapes-entry) attack-info 10) @@ -10647,7 +10649,7 @@ :method-count-assert 9 :size-assert #x20 :flag-assert #x900000020 - ;; field check-too-far uses ~A with a signed load. + ;; field check-too-far uses ~A with a signed load. ) |# @@ -10670,7 +10672,7 @@ :method-count-assert 9 :size-assert #x30 :flag-assert #x900000030 - ;; field default-check-too-far uses ~A with a signed load. + ;; field default-check-too-far uses ~A with a signed load. ) |# @@ -11359,7 +11361,7 @@ :method-count-assert 11 :size-assert #xa0 :flag-assert #xb000000a0 - ;; field extra-sound-bank uses ~A with a signed load. + ;; field extra-sound-bank uses ~A with a signed load. (:methods (level-load-info-method-9 () none) ;; 9 (level-load-info-method-10 () none) ;; 10 @@ -12400,7 +12402,7 @@ :method-count-assert 14 :size-assert #x20 :flag-assert #xe00000020 - ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load. + ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load. (:methods (connection-method-9 () none) ;; 9 ;; (get-engine (connection) engine 9) (connection-method-10 () none) ;; 10 ;; (get-process (connection) process 10) @@ -12468,7 +12470,7 @@ :method-count-assert 9 :size-assert #x20 :flag-assert #x900000020 - ;; field key uses ~A with a signed load. + ;; field key uses ~A with a signed load. ) |# @@ -12823,7 +12825,7 @@ :method-count-assert 9 :size-assert #x50 :flag-assert #x900000050 - ;; field key uses ~A with a signed load. + ;; field key uses ~A with a signed load. ) |# @@ -13152,7 +13154,7 @@ :method-count-assert 50 :size-assert #x70 :flag-assert #x3200000070 - ;; field nav-mesh-flag is likely a value type. + ;; field nav-mesh-flag is likely a value type. (:methods (nav-mesh-method-9 () none) ;; 9 ;; (debug-draw (_type_) none 9) (nav-mesh-method-10 () none) ;; 10 ;; (nav-mesh-method-10 (_type_ vector vector nav-poly) nav-poly 10) @@ -13488,7 +13490,7 @@ :method-count-assert 11 :size-assert #x30c :flag-assert #xb0000030c - ;; field cam-slave-options is likely a value type. field cam-master-options is likely a value type. + ;; field cam-slave-options is likely a value type. field cam-master-options is likely a value type. (:methods (cam-setting-data-method-9 () none) ;; 9 ;; (cam-setting-data-method-9 (_type_ engine engine-pers engine) _type_ 9) (cam-setting-data-method-10 () none) ;; 10 ;; (cam-setting-data-method-10 (_type_ object (pointer process) float int) _type_ 10) @@ -13643,7 +13645,7 @@ :method-count-assert 30 :size-assert #xa00 :flag-assert #x1e09800a00 - ;; field mode-param2 uses ~A with a 64-bit load. field mode-param3 uses ~A with a 64-bit load. + ;; field mode-param2 uses ~A with a 64-bit load. field mode-param3 uses ~A with a 64-bit load. (:methods (target-method-28 () none) ;; 28 ;; (init-target (_type_ continue-point symbol) none 28) (target-method-29 () none) ;; 29 @@ -15664,7 +15666,7 @@ :method-count-assert 10 :size-assert #x20 :flag-assert #xa00000020 - ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. + ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. (:methods ;; (new (symbol type drawable) _type_ 0) (cspace-method-9 () none) ;; 9 ;; (reset-and-assign-geo! (_type_ drawable) _type_ 9) @@ -15907,7 +15909,7 @@ :method-count-assert 17 :size-assert #x20 :flag-assert #x1100000020 - ;; field distance is a float printed as hex? + ;; field distance is a float printed as hex? ) |# @@ -16071,7 +16073,7 @@ :method-count-assert 52 :size-assert #x1a0 :flag-assert #x34012001a0 - ;; field on-activate uses ~A with a signed load. field on-deactivate uses ~A with a signed load. field on-up uses ~A with a signed load. field on-down uses ~A with a signed load. field on-running uses ~A with a signed load. field on-notice uses ~A with a signed load. field on-wait uses ~A with a signed load. field activate-test uses ~A with a signed load. + ;; field on-activate uses ~A with a signed load. field on-deactivate uses ~A with a signed load. field on-up uses ~A with a signed load. field on-down uses ~A with a signed load. field on-running uses ~A with a signed load. field on-notice uses ~A with a signed load. field on-wait uses ~A with a signed load. field activate-test uses ~A with a signed load. (:methods (elevator-method-39 () none) ;; 39 ;; (calc-dist-between-points! (_type_ int int) none 39) (elevator-method-41 () none) ;; 41 ;; (init-defaults! (_type_) none 41) @@ -16232,7 +16234,7 @@ :method-count-assert 10 :size-assert #x10 :flag-assert #xa00000010 - ;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load. + ;; field on-enter uses ~A with a signed load. field on-inside uses ~A with a signed load. field on-exit uses ~A with a signed load. (:methods (region-method-9 () none) ;; 9 ;; (region-method-9 (_type_ vector) symbol 9) ) @@ -17081,7 +17083,7 @@ :method-count-assert 9 :size-assert #x10 :flag-assert #x900000010 - ;; field execute uses ~A with a signed load. + ;; field execute uses ~A with a signed load. ) |# @@ -17098,7 +17100,7 @@ :method-count-assert 9 :size-assert #x30 :flag-assert #x900000030 - ;; field resetter-flag is likely a value type. + ;; field resetter-flag is likely a value type. ) |# @@ -17125,7 +17127,7 @@ :method-count-assert 9 :size-assert #x3c :flag-assert #x90000003c - ;; field handle is likely a value type. field resolution-scene uses ~A with a signed load. field on-complete uses ~A with a signed load. field on-fail uses ~A with a signed load. + ;; field handle is likely a value type. field resolution-scene uses ~A with a signed load. field on-complete uses ~A with a signed load. field on-fail uses ~A with a signed load. ) |# @@ -17170,7 +17172,7 @@ :method-count-assert 14 :size-assert #x50 :flag-assert #xe00000050 - ;; field on-open uses ~A with a signed load. field on-close uses ~A with a signed load. + ;; field on-open uses ~A with a signed load. field on-close uses ~A with a signed load. (:methods (game-task-node-info-method-9 () none) ;; 9 ;; (close! (_type_ symbol) int 9) (game-task-node-info-method-10 () none) ;; 10 ;; (open! (_type_ symbol) int 10) @@ -17195,7 +17197,7 @@ :method-count-assert 10 :size-assert #x20 :flag-assert #xa00000020 - ;; field kiosk-play-continue uses ~A with a signed load. + ;; field kiosk-play-continue uses ~A with a signed load. (:methods (game-task-info-method-9 () none) ;; 9 ) @@ -18025,7 +18027,7 @@ :method-count-assert 14 :size-assert #x3c :flag-assert #xe0000003c - ;; field blend-shape-anim uses ~A with a signed load. + ;; field blend-shape-anim uses ~A with a signed load. ) |# @@ -18201,7 +18203,7 @@ :method-count-assert 16 :size-assert #xdc :flag-assert #x10000000dc - ;; field draw-control-status is likely a value type. field draw-control-global-effect is likely a value type. + ;; field draw-control-status is likely a value type. field draw-control-global-effect is likely a value type. (:methods ;; (new (symbol type process symbol) _type_ 0) (draw-control-method-9 () none) ;; 9 ;; (get-skeleton-origin (_type_) vector 9) @@ -18255,7 +18257,7 @@ :method-count-assert 9 :size-assert #x1c :flag-assert #x90000001c - ;; field parm0 uses ~A with a signed load. field parm1 uses ~A with a signed load. + ;; field parm0 uses ~A with a signed load. field parm1 uses ~A with a signed load. ) |# @@ -18283,7 +18285,7 @@ :method-count-assert 9 :size-assert #x1c :flag-assert #x90000001c - ;; field track-val uses ~A with a signed load. field val-parm0 uses ~A with a signed load. field val-parm1 uses ~A with a signed load. + ;; field track-val uses ~A with a signed load. field val-parm0 uses ~A with a signed load. field val-parm1 uses ~A with a signed load. ) |# @@ -18788,7 +18790,7 @@ :method-count-assert 9 :size-assert #x80 :flag-assert #x900000080 - ;; field xyz-scale is a float printed as hex? + ;; field xyz-scale is a float printed as hex? ) |# @@ -19826,7 +19828,7 @@ :method-count-assert 12 :size-assert #x68 :flag-assert #xc00000068 - ;; field on-goto uses ~A with a signed load. + ;; field on-goto uses ~A with a signed load. (:methods (continue-point-method-9 () none) ;; 9 ;; (debug-draw (_type_) int 9) (continue-point-method-10 () none) ;; 10 ;; (continue-point-method-10 (_type_ load-state) continue-point 10) @@ -20181,7 +20183,7 @@ :method-count-assert 14 :size-assert #x30 :flag-assert #xe00000030 - ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load. + ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load. ) |# @@ -20470,7 +20472,7 @@ :method-count-assert 14 :size-assert #x20 :flag-assert #xe00000020 - ;; field on-close uses ~A with a signed load. + ;; field on-close uses ~A with a signed load. (:methods (talker-speech-class-method-9 () none) ;; 9 ;; (talker-speech-class-method-9 (_type_) symbol 9) (talker-speech-class-method-10 () none) ;; 10 ;; (play-communicator-speech! (_type_) none 10) @@ -21138,7 +21140,7 @@ :method-count-assert 13 :size-assert #x40 :flag-assert #xd00000040 - ;; field joint-control-status is likely a value type. + ;; field joint-control-status is likely a value type. (:methods ;; (new (symbol type int) _type_ 0) (joint-control-method-9 () none) ;; 9 ;; (current-cycle-distance (_type_) float 9) @@ -21498,7 +21500,7 @@ :method-count-assert 14 :size-assert #xb0 :flag-assert #xe000000b0 - ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load. + ;; field param1 uses ~A with a signed load. field param2 uses ~A with a signed load. field param3 uses ~A with a signed load. ) |# @@ -21568,7 +21570,7 @@ :method-count-assert 22 :size-assert #x20 :flag-assert #x1600000020 - ;; field extra uses ~A with a signed load. + ;; field extra uses ~A with a signed load. (:methods ;; (new (symbol type int int) _type_ 0) (res-lump-method-9 () none) ;; 9 ;; (get-property-data (_type_ symbol symbol float pointer (pointer res-tag) pointer) pointer 9) @@ -21680,7 +21682,7 @@ :method-count-assert 45 :size-assert #x15c :flag-assert #x2d00e0015c - ;; field on-activate uses ~A with a signed load. + ;; field on-activate uses ~A with a signed load. (:methods (wstd-arena-plat-method-43 () none) ;; 43 (wstd-arena-plat-method-44 () none) ;; 44 @@ -22226,7 +22228,7 @@ :method-count-assert 12 :size-assert #x28 :flag-assert #xc00000028 - ;; field actor-option is likely a value type. + ;; field actor-option is likely a value type. (:methods ;; (new (symbol type process pickup-type float) _type_ 0) (fact-info-method-9 () none) ;; 9 ;; (drop-pickup (_type_ symbol process-tree fact-info int) (pointer process) 9) @@ -22287,7 +22289,7 @@ :method-count-assert 13 :size-assert #x138 :flag-assert #xd00000138 - ;; field actor-option is likely a value type. + ;; field actor-option is likely a value type. (:methods ;; (new (symbol type process-drawable pickup-type float) _type_ 0) (fact-info-target-method-12 () none) ;; 12 ;; (get-gun-ammo (_type_) float 12) @@ -22318,7 +22320,7 @@ :method-count-assert 13 :size-assert #x53 :flag-assert #xd00000053 - ;; field actor-option is likely a value type. + ;; field actor-option is likely a value type. (:methods ;; (new (symbol type process (pointer float) pickup-type float) _type_ 0) (fact-info-enemy-method-12 () none) ;; 12 ;; (clear-mask-bits (_type_ int) none 12) @@ -22338,7 +22340,7 @@ :method-count-assert 12 :size-assert #x2c :flag-assert #xc0000002c - ;; field actor-option is likely a value type. + ;; field actor-option is likely a value type. (:methods ;; (new (symbol type process pickup-type float) _type_ 0) ) @@ -22443,7 +22445,7 @@ :method-count-assert 12 :size-assert #xa0 :flag-assert #xc000000a0 - ;; field key uses ~A with a signed load. field expr uses ~A with a signed load. + ;; field key uses ~A with a signed load. field expr uses ~A with a signed load. (:methods ;; (new (symbol type object process vector) _type_ 0) (script-context-method-9 () none) ;; 9 ;; (eval! (_type_ pair) object 9) @@ -22486,7 +22488,7 @@ :method-count-assert 9 :size-assert #x14 :flag-assert #x900000014 - ;; field info uses ~A with a signed load. + ;; field info uses ~A with a signed load. ) |# @@ -22735,7 +22737,7 @@ :method-count-assert 18 :size-assert #x84 :flag-assert #x1200000084 - ;; field on-running uses ~A with a signed load. field on-complete uses ~A with a signed load. + ;; field on-running uses ~A with a signed load. field on-complete uses ~A with a signed load. (:methods (scene-method-16 () none) ;; 16 ;; (scene-method-16 (_type_) _type_ 16) (scene-method-17 () none) ;; 17 @@ -22778,7 +22780,7 @@ :method-count-assert 26 :size-assert #x198 :flag-assert #x1a01200198 - ;; field user-data uses ~A with a 64-bit load. + ;; field user-data uses ~A with a 64-bit load. (:methods (scene-player-method-20 () none) ;; 20 ;; (wait (symbol) _type_ :state 20) (scene-player-method-21 () none) ;; 21 ;; (release () _type_ :state 21) @@ -23545,7 +23547,7 @@ :method-count-assert 23 :size-assert #xe0 :flag-assert #x17006000e0 - ;; field open-test uses ~A with a signed load. + ;; field open-test uses ~A with a signed load. (:methods (precur-door-a-method-22 () none) ;; 22 ) @@ -24069,7 +24071,7 @@ :method-count-assert 16 :size-assert #xac :flag-assert #x10000000ac - ;; field track-mode is likely a value type. + ;; field track-mode is likely a value type. (:methods ;; (new (symbol type joint-mod-mode process-drawable int) _type_ 0) (joint-mod-method-9 () none) ;; 9 ;; (mode-set! (_type_ joint-mod-mode) none 9) @@ -24875,7 +24877,7 @@ :method-count-assert 55 :size-assert #xc8 :flag-assert #x37000000c8 - ;; field penetrate is likely a value type. field penetrate is likely a value type. + ;; field penetrate is likely a value type. field penetrate is likely a value type. (:methods ;; (new (symbol type process-drawable collide-list-enum) _type_ 0) (collide-shape-method-28 () none) ;; 28 ;; (move-by-vector! (_type_ vector) none 28) @@ -24938,7 +24940,7 @@ :method-count-assert 68 :size-assert #x1dc :flag-assert #x44000001dc - ;; field penetrate is likely a value type. field penetrate is likely a value type. + ;; field penetrate is likely a value type. field penetrate is likely a value type. (:methods ;; (new (symbol type process-drawable collide-list-enum) _type_ 0) (collide-shape-moving-method-55 () none) ;; 55 ;; (find-ground (_type_ collide-query collide-spec float float float) symbol 55) @@ -25026,7 +25028,7 @@ :method-count-assert 18 :size-assert #x15c :flag-assert #x1200e0015c - ;; field userdata uses ~A with a 64-bit load. + ;; field userdata uses ~A with a 64-bit load. (:methods (part-tracker-method-14 () none) ;; 14 ;; (active () _type_ :state 14) (part-tracker-method-15 () none) ;; 15 ;; (notify-parent-of-death (_type_) none 15) @@ -25048,7 +25050,7 @@ :method-count-assert 9 :size-assert #x20 :flag-assert #x900000020 - ;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load. + ;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load. ) |# @@ -25065,7 +25067,7 @@ :method-count-assert 9 :size-assert #x24 :flag-assert #x900000024 - ;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load. + ;; field userdata uses ~A with a 64-bit load. field mat-joint uses ~A with a signed load. ) |# @@ -25101,7 +25103,7 @@ :method-count-assert 17 :size-assert #x130 :flag-assert #x1100b00130 - ;; field userdata uses ~A with a 64-bit load. + ;; field userdata uses ~A with a 64-bit load. (:methods (lightning-tracker-method-14 () none) ;; 14 ;; (active () _type_ :state 14) (lightning-tracker-method-15 () none) ;; 15 ;; (notify-parent-of-death (_type_) none 15) @@ -29521,7 +29523,7 @@ :method-count-assert 9 :size-assert #x10 :flag-assert #x900000010 - ;; field object uses ~A with a signed load. + ;; field object uses ~A with a signed load. ) |# @@ -29934,7 +29936,7 @@ :method-count-assert 9 :size-assert #x8c :flag-assert #x90000008c - ;; field user-object uses ~A with a signed load. field key uses ~A with a signed load. + ;; field user-object uses ~A with a signed load. field key uses ~A with a signed load. ) |# @@ -32403,7 +32405,7 @@ :method-count-assert 56 :size-assert #x120 :flag-assert #x3800a00120 - ;; field rigid-body-object-flag is likely a value type. + ;; field rigid-body-object-flag is likely a value type. (:methods (rigid-body-object-method-28 () none) ;; 28 ;; (active () _type_ :state 28) (rigid-body-object-method-29 () none) ;; 29 ;; (rigid-body-object-method-29 (_type_ float) none 29) @@ -32815,7 +32817,7 @@ :method-count-assert 30 :size-assert #x1b4 :flag-assert #x1e014001b4 - ;; field open-test uses ~A with a signed load. + ;; field open-test uses ~A with a signed load. (:state-methods open ;; 20 ) @@ -34707,7 +34709,7 @@ :method-count-assert 9 :size-assert #x14 :flag-assert #x900000014 - ;; field param2 uses ~A with a signed load. + ;; field param2 uses ~A with a signed load. ) |# @@ -35025,7 +35027,7 @@ :method-count-assert 207 :size-assert #x2e0 :flag-assert #xcf026002e0 - ;; field mantis-flag is likely a value type. + ;; field mantis-flag is likely a value type. (:methods (mantis-method-195 () none) ;; 195 (mantis-method-199 () none) ;; 199 @@ -36189,7 +36191,7 @@ :method-count-assert 9 :size-assert #x130 :flag-assert #x900000130 - ;; field ra uses ~A with a signed load. field dummy0 uses ~A with a signed load. field dummy1 uses ~A with a signed load. field b-spfic uses ~A with a signed load. field l-spfic uses ~A with a signed load. + ;; field ra uses ~A with a signed load. field dummy0 uses ~A with a signed load. field dummy1 uses ~A with a signed load. field b-spfic uses ~A with a signed load. field l-spfic uses ~A with a signed load. ) |# @@ -37780,7 +37782,7 @@ :method-count-assert 9 :size-assert #x44 :flag-assert #x900000044 - ;; field squad-target-flag is likely a value type. + ;; field squad-target-flag is likely a value type. ) |# @@ -37803,7 +37805,7 @@ :method-count-assert 10 :size-assert #x120 :flag-assert #xa00000120 - ;; field squad-alert-flag is likely a value type. + ;; field squad-alert-flag is likely a value type. (:methods (squad-alert-state-method-9 () none) ;; 9 ) @@ -39263,7 +39265,7 @@ :method-count-assert 38 :size-assert #xac :flag-assert #x26000000ac - ;; field cloth-flag is likely a value type. + ;; field cloth-flag is likely a value type. (:methods (cloth-system-method-16 () none) ;; 16 (cloth-system-method-17 () none) ;; 17 @@ -39301,7 +39303,7 @@ :method-count-assert 38 :size-assert #x110 :flag-assert #x2600000110 - ;; field cloth-flag is likely a value type. + ;; field cloth-flag is likely a value type. ) |# @@ -40731,7 +40733,7 @@ :method-count-assert 17 :size-assert #xb0 :flag-assert #x11000000b0 - ;; field carry-mode is likely a value type. + ;; field carry-mode is likely a value type. (:methods ;; (new (symbol type process-drawable int vector vector float) _type_ 0) (carry-info-method-9 () none) ;; 9 ;; (carry-info-method-9 (_type_) none 9) @@ -41211,7 +41213,7 @@ :method-count-assert 23 :size-assert #xe4 :flag-assert #x17007000e4 - ;; field open-test uses ~A with a signed load. + ;; field open-test uses ~A with a signed load. (:methods (precura-door-a-method-22 () none) ;; 22 ) @@ -43432,7 +43434,7 @@ :method-count-assert 26 :size-assert #x100 :flag-assert #x1a00800100 - ;; field on-notice uses ~A with a signed load. field on-activate uses ~A with a signed load. field on-close uses ~A with a signed load. + ;; field on-notice uses ~A with a signed load. field on-activate uses ~A with a signed load. field on-close uses ~A with a signed load. (:methods (warp-gate-method-23 () none) ;; 23 ;; (init-skel-and-collide (_type_) none 23) (warp-gate-method-24 () none) ;; 24 ;; (setup-fields (_type_) none 24) @@ -44792,7 +44794,7 @@ :method-count-assert 15 :size-assert #xa0 :flag-assert #xf002000a0 - ;; field activate-script uses ~A with a signed load. field enter-script uses ~A with a signed load. field exit-script uses ~A with a signed load. + ;; field activate-script uses ~A with a signed load. field enter-script uses ~A with a signed load. field exit-script uses ~A with a signed load. (:state-methods active ;; 14 ) @@ -45605,7 +45607,7 @@ :method-count-assert 30 :size-assert #x1b0 :flag-assert #x1e013001b0 - ;; field level-name uses ~A with a signed load. field open-test uses ~A with a signed load. field on-running uses ~A with a signed load. + ;; field level-name uses ~A with a signed load. field open-test uses ~A with a signed load. field on-running uses ~A with a signed load. (:methods (com-airlock-method-22 () none) ;; 22 ;; (init-airlock! (_type_) _type_ 22) (com-airlock-method-23 () none) ;; 23 ;; (want-cross-airlock? (_type_) symbol 23) @@ -46875,7 +46877,7 @@ :method-count-assert 53 :size-assert #x110 :flag-assert #x3500900110 - ;; field on-notice uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-beaten uses ~A with a signed load. + ;; field on-notice uses ~A with a signed load. field on-hostile uses ~A with a signed load. field on-beaten uses ~A with a signed load. (:methods (battle-method-21 () none) ;; 21 ;; (battle-state-21 () _type_ :state 21) (battle-method-25 () none) ;; 25 ;; (spawner-blocked? (_type_ battle-spawner) symbol 25) @@ -47086,7 +47088,7 @@ :method-count-assert 9 :size-assert #x1c :flag-assert #x90000001c - ;; field net-path-node-status is likely a value type. + ;; field net-path-node-status is likely a value type. ) |# @@ -49010,7 +49012,7 @@ :method-count-assert 59 :size-assert #x224 :flag-assert #x3b01b00224 - ;; field pause-proc uses ~A with a signed load. + ;; field pause-proc uses ~A with a signed load. (:methods (target-turret-method-34 () none) ;; 34 (target-turret-method-35 () none) ;; 35 @@ -49372,7 +49374,7 @@ :method-count-assert 22 :size-assert #x20 :flag-assert #x1600000020 - ;; field nav-node-flag is likely a value type. + ;; field nav-node-flag is likely a value type. (:methods (nav-node-method-9 () none) ;; 9 ;; (debug-draw (_type_) none 9) (nav-node-method-10 () none) ;; 10 ;; (debug-print (_type_ symbol string) none 10) @@ -49942,7 +49944,7 @@ :method-count-assert 11 :size-assert #x20 :flag-assert #xb00000020 - ;; field vis-cell-flag is likely a value type. field vis-cell-flag is likely a value type. + ;; field vis-cell-flag is likely a value type. field vis-cell-flag is likely a value type. (:methods (vis-cell-method-9 () none) ;; 9 ;; (reset-segment-counts (_type_) none 9) (vis-cell-method-10 () none) ;; 10 ;; (debug-draw (_type_) none 10) @@ -51284,7 +51286,7 @@ :method-count-assert 18 :size-assert #x60 :flag-assert #x1200000060 - ;; field turret-flag is likely a value type. + ;; field turret-flag is likely a value type. (:methods (turret-control-method-9 () none) ;; 9 ;; (turret-control-method-9 (_type_ vehicle vector vector) none 9) (turret-control-method-10 () none) ;; 10 ;; (turret-control-method-10 (_type_ vehicle) none 10) @@ -51369,7 +51371,7 @@ :method-count-assert 68 :size-assert #x1dc :flag-assert #x44000001dc - ;; field penetrate is likely a value type. field penetrate is likely a value type. + ;; field penetrate is likely a value type. field penetrate is likely a value type. ) |# @@ -55094,7 +55096,7 @@ :method-count-assert 152 :size-assert #x2b8 :flag-assert #x98024002b8 - ;; field vehicle-flag is likely a value type. + ;; field vehicle-flag is likely a value type. (:methods (vehicle-method-56 () none) ;; 56 ;; (vehicle-state-56 () _type_ :state 56) (vehicle-method-57 () none) ;; 57 ;; (player-control () _type_ :state 57) @@ -55855,7 +55857,7 @@ :method-count-assert 31 :size-assert #x200 :flag-assert #x1f01800200 - ;; field on-start uses ~A with a signed load. field on-stop uses ~A with a signed load. field on-shutdown uses ~A with a signed load. field on-trigger uses ~A with a signed load. + ;; field on-start uses ~A with a signed load. field on-stop uses ~A with a signed load. field on-shutdown uses ~A with a signed load. field on-trigger uses ~A with a signed load. (:methods (elec-gate-method-24 () none) ;; 24 ;; (elec-gate-method-24 (_type_) none 24) (elec-gate-method-25 () none) ;; 25 ;; (set-palette! (_type_) none 25) @@ -57117,7 +57119,7 @@ :method-count-assert 10 :size-assert #xaa :flag-assert #xa000000aa - ;; field borrow uses ~A with a signed load. + ;; field borrow uses ~A with a signed load. (:methods (race-info-method-9 () none) ;; 9 ;; (initialize-mesh (_type_) none 9) ) @@ -58462,7 +58464,7 @@ :method-count-assert 9 :size-assert #x2c :flag-assert #x90000002c - ;; field vehicle-wheel-surface-flag is likely a value type. + ;; field vehicle-wheel-surface-flag is likely a value type. ) |# @@ -59361,7 +59363,7 @@ :method-count-assert 9 :size-assert #x38 :flag-assert #x900000038 - ;; field default-check-too-far uses ~A with a signed load. + ;; field default-check-too-far uses ~A with a signed load. ) |# @@ -66180,7 +66182,7 @@ :method-count-assert 9 :size-assert #x18 :flag-assert #x900000018 - ;; field param uses ~A with a signed load. + ;; field param uses ~A with a signed load. ) |# diff --git a/decompiler/config/jak3/ntsc_v1/hacks.jsonc b/decompiler/config/jak3/ntsc_v1/hacks.jsonc index 02e5d6ac66..0915f7110a 100644 --- a/decompiler/config/jak3/ntsc_v1/hacks.jsonc +++ b/decompiler/config/jak3/ntsc_v1/hacks.jsonc @@ -126,6 +126,7 @@ // will be less picky about types related to pairs. "pair_functions_by_name": [ "ref", + "ref&", "(method 4 pair)", "last", "member", diff --git a/decompiler/config/jak3/ntsc_v1/type_casts.jsonc b/decompiler/config/jak3/ntsc_v1/type_casts.jsonc index 0967ef424b..35ff179ab6 100644 --- a/decompiler/config/jak3/ntsc_v1/type_casts.jsonc +++ b/decompiler/config/jak3/ntsc_v1/type_casts.jsonc @@ -1 +1,37 @@ -{} +{ + "(method 2 array)": [ + [23, "gp", "(array int32)"], + [43, "gp", "(array uint32)"], + [63, "gp", "(array int64)"], + [83, "gp", "(array uint64)"], + [102, "gp", "(array int8)"], + [121, "gp", "(array uint8)"], + [141, "gp", "(array int16)"], + [161, "gp", "(array uint16)"], + [186, "gp", "(array uint128)"], + [204, "gp", "(array int32)"], + [223, "gp", "(array float)"], + [232, "gp", "(array float)"], + [249, "gp", "(array basic)"], + [258, "gp", "(array basic)"] + ], + "(method 3 array)": [ + [51, "gp", "(array int32)"], + [69, "gp", "(array uint32)"], + [87, "gp", "(array int64)"], + [105, "gp", "(array uint64)"], + [122, "gp", "(array int8)"], + [139, "gp", "(array int8)"], + [157, "gp", "(array int16)"], + [175, "gp", "(array uint16)"], + [198, "gp", "(array uint128)"], + [214, "gp", "(array int32)"], + [233, "gp", "(array float)"], + [250, "gp", "(array basic)"] + ], + "(method 0 cpu-thread)": [[[0, 35], "v0", "cpu-thread"]], + "(method 0 process)": [ + // [11, "a0", "int"], + // [[12, 45], "v0", "process"] + ] +} diff --git a/decompiler/util/type_utils.h b/decompiler/util/type_utils.h index 5554751e56..f258c10c57 100644 --- a/decompiler/util/type_utils.h +++ b/decompiler/util/type_utils.h @@ -13,7 +13,9 @@ namespace decompiler { bool allowable_base_type_for_symbol_to_string(const TypeSpec& ts); constexpr PerGameVersion SYMBOL_TO_STRING_MEM_OFFSET_DECOMP = { - 8167 * 8, jak2::SYM_TO_STRING_OFFSET, jak3::SYM_TO_STRING_OFFSET}; + 8167 * 8, jak2::SYM_TO_STRING_OFFSET, + -99999, // not supported this way! +}; constexpr PerGameVersion OFFSET_OF_NEXT_STATE_STORE = {72, 64, 68}; } // namespace decompiler diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 30e2778c36..30e159f1f9 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -121,6 +121,19 @@ set(RUNTIME_SOURCE kernel/jak2/kprint.cpp kernel/jak2/kscheme.cpp kernel/jak2/ksound.cpp + kernel/jak3/fileio.cpp + kernel/jak3/kboot.cpp + kernel/jak3/kdgo.cpp + kernel/jak3/kdsnetm.cpp + kernel/jak3/klink.cpp + kernel/jak3/klisten.cpp + kernel/jak3/kmachine.cpp + kernel/jak3/kmalloc.cpp + kernel/jak3/kmemcard.cpp + kernel/jak3/kprint.cpp + kernel/jak3/kscheme.cpp + kernel/jak3/ksocket.cpp + kernel/jak3/ksound.cpp mips2c/jak1_functions/bones.cpp mips2c/jak1_functions/collide_cache.cpp mips2c/jak1_functions/collide_edge_grab.cpp @@ -234,8 +247,6 @@ set(RUNTIME_SOURCE system/IOP_Kernel.cpp system/iop_thread.cpp system/SystemThread.cpp - system/vm/dmac.cpp - system/vm/vm.cpp tools/filter_menu/filter_menu.cpp tools/subtitle_editor/subtitle_editor_db.cpp tools/subtitle_editor/subtitle_editor_repl_client.cpp diff --git a/game/common/dgo_rpc_types.h b/game/common/dgo_rpc_types.h index e8bc8dbe6a..6ecf0364cd 100644 --- a/game/common/dgo_rpc_types.h +++ b/game/common/dgo_rpc_types.h @@ -8,8 +8,7 @@ #include "common/common_types.h" #include "common/versions/versions.h" -// TODO: jak 3 stub -constexpr PerGameVersion DGO_RPC_ID(0xdeb4, 0xfab3, 0x0); +constexpr PerGameVersion DGO_RPC_ID(0xdeb4, 0xfab3, 0xfab3); constexpr int DGO_RPC_CHANNEL = 3; constexpr int DGO_RPC_LOAD_FNO = 0; constexpr int DGO_RPC_LOAD_NEXT_FNO = 1; @@ -28,3 +27,17 @@ struct RPC_Dgo_Cmd { uint32_t buffer_heap_top; char name[16]; }; + +namespace jak3 { +struct RPC_Dgo_Cmd { + uint16_t rsvd; + uint16_t result; + uint32_t buffer1; + uint32_t buffer2; + uint32_t buffer_heap_top; + char name[16]; + uint16_t cgo_id; + uint8_t pad[30]; +}; +static_assert(sizeof(RPC_Dgo_Cmd) == 0x40); +} // namespace jak3 \ No newline at end of file diff --git a/game/common/game_common_types.h b/game/common/game_common_types.h index 9e77282437..f6f444ae2a 100644 --- a/game/common/game_common_types.h +++ b/game/common/game_common_types.h @@ -13,11 +13,11 @@ enum class Language { Japanese = 5, UK_English = 6, // uk english? + Portuguese = 9 }; struct GameLaunchOptions { GameVersion game_version = GameVersion::Jak1; bool disable_display = false; - bool disable_debug_vm = true; int server_port = DECI2_PORT; }; diff --git a/game/common/loader_rpc_types.h b/game/common/loader_rpc_types.h index 23960923ce..bd08921e3e 100644 --- a/game/common/loader_rpc_types.h +++ b/game/common/loader_rpc_types.h @@ -7,6 +7,5 @@ #include "common/versions/versions.h" -// TODO: jak 3 stub -constexpr PerGameVersion LOADER_RPC_ID(0xdeb2, 0xfab1, 0x0); +constexpr PerGameVersion LOADER_RPC_ID(0xdeb2, 0xfab1, 0xfab1); constexpr int LOADER_RPC_CHANNEL = 1; diff --git a/game/common/play_rpc_types.h b/game/common/play_rpc_types.h index 910a1ccf95..3ca16b94bd 100644 --- a/game/common/play_rpc_types.h +++ b/game/common/play_rpc_types.h @@ -7,6 +7,5 @@ */ #include "common/versions/versions.h" -// TODO: jak 3 stub -constexpr PerGameVersion PLAY_RPC_ID(0xdeb6, 0xfab5, 0x0); +constexpr PerGameVersion PLAY_RPC_ID(0xdeb6, 0xfab5, 0xfab5); constexpr int PLAY_RPC_CHANNEL = 5; diff --git a/game/common/player_rpc_types.h b/game/common/player_rpc_types.h index b386f0338d..bec6f429dd 100644 --- a/game/common/player_rpc_types.h +++ b/game/common/player_rpc_types.h @@ -7,6 +7,5 @@ */ #include "common/versions/versions.h" -// TODO: jak 3 stub -constexpr PerGameVersion PLAYER_RPC_ID(0xdeb1, 0xfab0, 0x0); +constexpr PerGameVersion PLAYER_RPC_ID(0xdeb1, 0xfab0, 0xfab0); constexpr int PLAYER_RPC_CHANNEL = 0; diff --git a/game/common/ramdisk_rpc_types.h b/game/common/ramdisk_rpc_types.h index 48d37db4a0..eefe980094 100644 --- a/game/common/ramdisk_rpc_types.h +++ b/game/common/ramdisk_rpc_types.h @@ -8,8 +8,7 @@ #include "common/common_types.h" #include "common/versions/versions.h" -// TODO: jak 3 stub -constexpr PerGameVersion RAMDISK_RPC_ID(0xdeb3, 0xfab2, 0x0); +constexpr PerGameVersion RAMDISK_RPC_ID(0xdeb3, 0xfab2, 0xfab2); constexpr int RAMDISK_RPC_CHANNEL = 2; constexpr int RAMDISK_GET_DATA_FNO = 0; constexpr int RAMDISK_RESET_AND_LOAD_FNO = 1; diff --git a/game/common/str_rpc_types.h b/game/common/str_rpc_types.h index 4a78a2dd8c..3aac5ac685 100644 --- a/game/common/str_rpc_types.h +++ b/game/common/str_rpc_types.h @@ -5,8 +5,7 @@ #include "game/common/overlord_common.h" -// TODO: jak 3 stub -constexpr PerGameVersion STR_RPC_ID(0xdeb5, 0xfab4, 0x0); +constexpr PerGameVersion STR_RPC_ID(0xdeb5, 0xfab4, 0xfab4); constexpr int STR_RPC_CHANNEL = 4; /* diff --git a/game/kernel/common/Ptr.h b/game/kernel/common/Ptr.h index 79f4fb8ec9..b55f4ee7bc 100644 --- a/game/kernel/common/Ptr.h +++ b/game/kernel/common/Ptr.h @@ -54,6 +54,7 @@ struct Ptr { s32 operator-(Ptr x) { return offset - x.offset; } Ptr operator-(s32 diff) { return Ptr(offset - diff); } bool operator==(const Ptr& x) { return offset == x.offset; } + bool operator!=(const Ptr& x) { return offset != x.offset; } /*! * Convert to a C pointer. diff --git a/game/kernel/common/Symbol4.h b/game/kernel/common/Symbol4.h index 57b95e7545..b91c58113c 100644 --- a/game/kernel/common/Symbol4.h +++ b/game/kernel/common/Symbol4.h @@ -6,13 +6,13 @@ #include "game/kernel/common/Ptr.h" +/*! + * This type is bit strange. The Ptr should have the same value as the original game's + * symbol address. The actual value in here is useless - the address of this thing is what matters. + */ template struct Symbol4 { u8 foo; T& value() { return *reinterpret_cast(&foo - 1); } const T& value() const { return *reinterpret_cast(&foo - 1); } - const char* name_cstr() const { - return (const char*)(g_ee_main_mem + 4 + - *reinterpret_cast(&foo + jak2::SYM_TO_STRING_OFFSET)); - } }; diff --git a/game/kernel/common/fileio.cpp b/game/kernel/common/fileio.cpp index 753313b393..11976b7784 100644 --- a/game/kernel/common/fileio.cpp +++ b/game/kernel/common/fileio.cpp @@ -333,6 +333,7 @@ s32 FileSave(char* name, u8* data, s32 size) { } if (size != 0) { + // in jak 3, this became a loop over smaller writes for some reason. s32 written = sceWrite(fd, data, size); if (written != size) { MsgErr("dkernel: can't write full file '%s'\n", name); diff --git a/game/kernel/common/fileio.h b/game/kernel/common/fileio.h index 55bc258a20..51b56f40a9 100644 --- a/game/kernel/common/fileio.h +++ b/game/kernel/common/fileio.h @@ -53,6 +53,7 @@ enum GoalFileType { VAG_FILE_TYPE = 0x3e, MISC_FILE_TYPE = 0x3f, // jak2 only MAP_FILE_TYPE = 0x40, // jak2 only + CL_FILE_TYPE = 0x41, // jak 3 cloth animation REFPLANT_FILE_TYPE = 0x301, // added this, allows access directly to out/iso from fileio. ISO_FILE_TYPE = 0x302 diff --git a/game/kernel/common/kdgo.cpp b/game/kernel/common/kdgo.cpp index 86039b6a75..c8e6568edb 100644 --- a/game/kernel/common/kdgo.cpp +++ b/game/kernel/common/kdgo.cpp @@ -18,15 +18,11 @@ ee::sceSifClientData cd[6]; //! client data for each IOP Remove Procedure Call. u32 sShowStallMsg; //! setting to show a "stalled on iop" message u16 x[8]; //! stupid temporary for storing a message u32 sMsgNum; //! Toggle for double buffered message sending. -RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP -RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers void kdgo_init_globals() { memset(x, 0, sizeof(x)); memset(cd, 0, sizeof(cd)); sShowStallMsg = 1; - sLastMsg = nullptr; - memset(sMsg, 0, sizeof(sMsg)); } /*! @@ -159,79 +155,6 @@ void StopIOP() { printf("DMA shut down\n"); } -/*! - * Send message to IOP to start loading a new DGO file - * Uses a double-buffered message buffer - * @param name: the name of the DGO file - * @param buffer1 : one of the two file loading buffers - * @param buffer2 : the other of the two file loading buffers - * @param currentHeap : the current heap (for loading directly into the heap). - * - * DONE, - * MODIFIED : Added print statement to indicate when DGO load starts. - */ -void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr currentHeap) { - u8 msgID = sMsgNum; - RPC_Dgo_Cmd* mess = sMsg + sMsgNum; - sMsgNum = sMsgNum ^ 1; // toggle message buffer. - RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished - - // put a dummy value here just to make sure the IOP overwrites it. - sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666 - - // inform IOP of buffers - sMsg[msgID].buffer1 = buffer1.offset; - sMsg[msgID].buffer2 = buffer2.offset; - - // also give a heap pointer so it can load the last object file directly into the heap to save the - // precious time. - sMsg[msgID].buffer_heap_top = currentHeap.offset; - - // file name - strcpy(sMsg[msgID].name, name); - lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset, - buffer2.offset, currentHeap.offset); - // this RPC will return once we have loaded the first object file. - // but we call async, so we don't block here. - RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess, - sizeof(RPC_Dgo_Cmd)); - sLastMsg = mess; -} - -/*! - * Get the next object in the DGO. Will block until something is loaded. - * @param lastObjectFlag: will get set to 1 if this is the last object. - * - * DONE, - * MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer) - */ -Ptr GetNextDGO(u32* lastObjectFlag) { - *lastObjectFlag = 1; - // Wait for RPC function to respond. This will happen once the first object file is loaded. - RpcSync(DGO_RPC_CHANNEL); - Ptr buffer(0); - if (sLastMsg) { - // if we got a good result, get pointer to object - if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) { - buffer.offset = - sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object. - } - - // not the last one, so don't set the flag. - if (sLastMsg->result == DGO_RPC_RESULT_MORE) { - *lastObjectFlag = 0; - } - - // no pending message. - sLastMsg = nullptr; - } else { - // I don't see how this case can happen unless there's a bug. The game does check for this and - // nothing in this case. (maybe from GOAL this can happen?) - printf("last message not set!\n"); - } - return buffer; -} - /*! * Load the TEST.DGO file. * Presumably used for debugging DGO loads. @@ -242,6 +165,8 @@ Ptr GetNextDGO(u32* lastObjectFlag) { * UNUSED */ void LoadDGOTest() { + ASSERT_NOT_REACHED(); // no longer supported. + /* u32 lastObject = 0; // backup show stall message and set it to false @@ -273,4 +198,5 @@ void LoadDGOTest() { } sShowStallMsg = lastShowStall; + */ } \ No newline at end of file diff --git a/game/kernel/common/kdgo.h b/game/kernel/common/kdgo.h index 9e6a5b4e12..b8f8899a9c 100644 --- a/game/kernel/common/kdgo.h +++ b/game/kernel/common/kdgo.h @@ -13,8 +13,6 @@ s32 RpcCall(s32 rpcChannel, s32 sendSize, void* recvBuff, s32 recvSize); -void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr currentHeap); -Ptr GetNextDGO(u32* lastObjectFlag); u64 RpcCall_wrapper(void* _args); u32 RpcBusy(s32 channel); void RpcSync(s32 channel); @@ -24,5 +22,3 @@ u32 InitRPC(); void StopIOP(); extern u32 sShowStallMsg; -extern RPC_Dgo_Cmd sMsg[2]; -extern RPC_Dgo_Cmd* sLastMsg; \ No newline at end of file diff --git a/game/kernel/common/klink.cpp b/game/kernel/common/klink.cpp index 6017e69e5d..2a5034e03a 100644 --- a/game/kernel/common/klink.cpp +++ b/game/kernel/common/klink.cpp @@ -33,11 +33,11 @@ void klink_init_globals() { * Initialize the link control. * TODO: this hasn't been carefully checked for jak 2 differences. */ -void link_control::begin(Ptr object_file, - const char* name, - int32_t size, - Ptr heap, - uint32_t flags) { +void link_control::jak1_jak2_begin(Ptr object_file, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags) { if (is_opengoal_object(object_file.c())) { // save data from call to begin m_object_data = object_file; diff --git a/game/kernel/common/klink.h b/game/kernel/common/klink.h index 652b6c2f0f..00beb5a722 100644 --- a/game/kernel/common/klink.h +++ b/game/kernel/common/klink.h @@ -60,22 +60,41 @@ struct link_control { bool m_opengoal; bool m_busy; // only in jak2, but doesn't hurt to set it in jak 1. - void begin(Ptr object_file, - const char* name, - int32_t size, - Ptr heap, - uint32_t flags); + + // jak 3 new stuff + bool m_on_global_heap = false; + LinkHeaderV5Core* m_link_hdr = nullptr; + bool m_moved_link_block = false; + + void jak1_jak2_begin(Ptr object_file, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags); + + void jak3_begin(Ptr object_file, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags); // was originally "work" uint32_t jak1_work(); uint32_t jak2_work(); + uint32_t jak3_work(); uint32_t jak1_work_v3(); uint32_t jak1_work_v2(); + uint32_t jak2_work_v3(); uint32_t jak2_work_v2(); + + uint32_t jak3_work_v5(); + uint32_t jak3_work_opengoal(); + void jak1_finish(bool jump_from_c_to_goal); void jak2_finish(bool jump_from_c_to_goal); + void jak3_finish(bool jump_from_c_to_goal); void reset() { m_object_data.offset = 0; diff --git a/game/kernel/common/kmachine.cpp b/game/kernel/common/kmachine.cpp index e2fa629d3d..f161f10a4f 100644 --- a/game/kernel/common/kmachine.cpp +++ b/game/kernel/common/kmachine.cpp @@ -22,7 +22,6 @@ #include "game/sce/libpad.h" #include "game/sce/libscf.h" #include "game/sce/sif_ee.h" -#include "game/system/vm/vm.h" /*! * Where does OVERLORD load its data from? @@ -973,10 +972,4 @@ void init_common_pc_port_functions( // debugging tools make_func_symbol_func("pc-filter-debug-string?", (void*)pc_filter_debug_string); - - // init ps2 VM - if (VM::use) { - make_func_symbol_func("vm-ptr", (void*)VM::get_vm_ptr); - VM::vm_init(); - } } diff --git a/game/kernel/common/kmalloc.cpp b/game/kernel/common/kmalloc.cpp index 996f73545a..a7917d15d1 100644 --- a/game/kernel/common/kmalloc.cpp +++ b/game/kernel/common/kmalloc.cpp @@ -12,11 +12,25 @@ // global and debug kernel heaps Ptr kglobalheap; Ptr kdebugheap; +// if we should count the number of strings and types allocated on the global heap. +bool kheaplogging = false; +enum MemItemsCategory { + STRING = 0, + TYPE = 1, + NUM_CATEGORIES = 2, +}; +int MemItemsCount[NUM_CATEGORIES] = {0, 0}; +int MemItemsSize[NUM_CATEGORIES] = {0, 0}; void kmalloc_init_globals_common() { // _globalheap and _debugheap kglobalheap.offset = GLOBAL_HEAP_INFO_ADDR; kdebugheap.offset = DEBUG_HEAP_INFO_ADDR; + kheaplogging = false; + for (auto& x : MemItemsCount) + x = 0; + for (auto& x : MemItemsSize) + x = 0; } /*! @@ -75,6 +89,10 @@ Ptr kheapstatus(Ptr heap) { Msg(6, "\t %d bytes before stack\n", GLOBAL_HEAP_END - heap->current.offset); } + for (int i = 0; i < NUM_CATEGORIES; i++) { + printf(" %d: %d %d\n", i, MemItemsCount[i], MemItemsSize[i]); + } + // might not have returned heap in jak 1 return heap; } @@ -171,6 +189,17 @@ Ptr kmalloc(Ptr heap, s32 size, u32 flags, char const* name) { if (flags & KMALLOC_MEMSET) std::memset(Ptr(memstart).c(), 0, (size_t)size); + + // this logging was added in Jak 3, but we port it back to all games: + if ((heap == kglobalheap) && (kheaplogging != 0)) { + if (strcmp(name, "string") == 0) { + MemItemsCount[STRING]++; + MemItemsSize[STRING] += size; + } else if (strcmp(name, "type") == 0) { + MemItemsCount[TYPE]++; + MemItemsSize[TYPE] += size; + } + } return Ptr(memstart); } } diff --git a/game/kernel/common/kmalloc.h b/game/kernel/common/kmalloc.h index 464fc4cbc9..874b9920cf 100644 --- a/game/kernel/common/kmalloc.h +++ b/game/kernel/common/kmalloc.h @@ -17,6 +17,7 @@ struct kheapinfo { // Kernel heaps extern Ptr kglobalheap; extern Ptr kdebugheap; +extern bool kheaplogging; // flags for kmalloc/ksmalloc constexpr u32 KMALLOC_TOP = 0x2000; //! Flag to allocate temporary memory from heap top diff --git a/game/kernel/common/kprint.cpp b/game/kernel/common/kprint.cpp index f53ed0536d..44d3d37762 100644 --- a/game/kernel/common/kprint.cpp +++ b/game/kernel/common/kprint.cpp @@ -68,6 +68,9 @@ void init_output() { case GameVersion::Jak2: use_debug = MasterDebug && DebugSegment; break; + case GameVersion::Jak3: + use_debug = MasterDebug || DebugSegment; + break; default: ASSERT(false); } diff --git a/game/kernel/common/memory_layout.h b/game/kernel/common/memory_layout.h index 2a808d7f85..73777a9f81 100644 --- a/game/kernel/common/memory_layout.h +++ b/game/kernel/common/memory_layout.h @@ -28,4 +28,8 @@ constexpr u32 DEBUG_HEAP_START = 0x5000000; namespace jak2 { constexpr u32 DEBUG_HEAP_SIZE = 0x2f00000; +} + +namespace jak3 { +constexpr u32 DEBUG_HEAP_SIZE = 0x2f00000; } \ No newline at end of file diff --git a/game/kernel/jak1/kdgo.cpp b/game/kernel/jak1/kdgo.cpp index c08b8c5b97..a89d9ac0dd 100644 --- a/game/kernel/jak1/kdgo.cpp +++ b/game/kernel/jak1/kdgo.cpp @@ -12,6 +12,87 @@ namespace jak1 { +RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP +RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers + +void kdgo_init_globals() { + sLastMsg = nullptr; + memset(sMsg, 0, sizeof(sMsg)); +} + +/*! + * Send message to IOP to start loading a new DGO file + * Uses a double-buffered message buffer + * @param name: the name of the DGO file + * @param buffer1 : one of the two file loading buffers + * @param buffer2 : the other of the two file loading buffers + * @param currentHeap : the current heap (for loading directly into the heap). + * + * DONE, + * MODIFIED : Added print statement to indicate when DGO load starts. + */ +void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr currentHeap) { + u8 msgID = sMsgNum; + RPC_Dgo_Cmd* mess = sMsg + sMsgNum; + sMsgNum = sMsgNum ^ 1; // toggle message buffer. + RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished + + // put a dummy value here just to make sure the IOP overwrites it. + sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666 + + // inform IOP of buffers + sMsg[msgID].buffer1 = buffer1.offset; + sMsg[msgID].buffer2 = buffer2.offset; + + // also give a heap pointer so it can load the last object file directly into the heap to save the + // precious time. + sMsg[msgID].buffer_heap_top = currentHeap.offset; + + // file name + strcpy(sMsg[msgID].name, name); + lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset, + buffer2.offset, currentHeap.offset); + // this RPC will return once we have loaded the first object file. + // but we call async, so we don't block here. + RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess, + sizeof(RPC_Dgo_Cmd)); + sLastMsg = mess; +} + +/*! + * Get the next object in the DGO. Will block until something is loaded. + * @param lastObjectFlag: will get set to 1 if this is the last object. + * + * DONE, + * MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer) + */ +Ptr GetNextDGO(u32* lastObjectFlag) { + *lastObjectFlag = 1; + // Wait for RPC function to respond. This will happen once the first object file is loaded. + RpcSync(DGO_RPC_CHANNEL); + Ptr buffer(0); + if (sLastMsg) { + // if we got a good result, get pointer to object + if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) { + buffer.offset = + sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object. + } + + // not the last one, so don't set the flag. + if (sLastMsg->result == DGO_RPC_RESULT_MORE) { + *lastObjectFlag = 0; + } + + // no pending message. + sLastMsg = nullptr; + } else { + // I don't see how this case can happen unless there's a bug. The game does check for this and + // nothing in this case. (maybe from GOAL this can happen?) + printf("last message not set!\n"); + } + return buffer; +} + /*! * Instruct the IOP to continue loading the next object. * Only should be called once it is safe to overwrite the previous. diff --git a/game/kernel/jak1/kdgo.h b/game/kernel/jak1/kdgo.h index 65b38c0af6..11f175e0b2 100644 --- a/game/kernel/jak1/kdgo.h +++ b/game/kernel/jak1/kdgo.h @@ -1,8 +1,10 @@ #pragma once #include "common/common_types.h" +#include "game/common/dgo_rpc_types.h" #include "game/kernel/common/Ptr.h" #include "game/kernel/common/kmalloc.h" + namespace jak1 { void load_and_link_dgo_from_c(const char* name, Ptr heap, @@ -10,4 +12,7 @@ void load_and_link_dgo_from_c(const char* name, s32 bufferSize, bool jump_from_c_to_goal); void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size); +void kdgo_init_globals(); +extern RPC_Dgo_Cmd sMsg[2]; +extern RPC_Dgo_Cmd* sLastMsg; } // namespace jak1 \ No newline at end of file diff --git a/game/kernel/jak1/klink.cpp b/game/kernel/jak1/klink.cpp index 5b21d65d22..b43be8bb30 100644 --- a/game/kernel/jak1/klink.cpp +++ b/game/kernel/jak1/klink.cpp @@ -582,7 +582,7 @@ Ptr link_and_exec(Ptr data, uint32_t flags, bool jump_from_c_to_goal) { link_control lc; - lc.begin(data, name, size, heap, flags); + lc.jak1_jak2_begin(data, name, size, heap, flags); uint32_t done; do { done = lc.jak1_work(); @@ -608,8 +608,8 @@ u64 link_and_exec_wrapper(u64* args) { */ uint64_t link_begin(u64* args) { // object data, name size, heap flags - saved_link_control.begin(Ptr(args[0]), Ptr(args[1]).c(), args[2], - Ptr(args[3]), args[4]); + saved_link_control.jak1_jak2_begin(Ptr(args[0]), Ptr(args[1]).c(), args[2], + Ptr(args[3]), args[4]); auto work_result = saved_link_control.jak1_work(); // if we managed to finish in one shot, take care of calling finish if (work_result) { diff --git a/game/kernel/jak1/kmachine.cpp b/game/kernel/jak1/kmachine.cpp index 2961e3ece7..72da1d757a 100644 --- a/game/kernel/jak1/kmachine.cpp +++ b/game/kernel/jak1/kmachine.cpp @@ -43,7 +43,6 @@ #include "game/sce/libgraph.h" #include "game/sce/sif_ee.h" #include "game/sce/stubs.h" -#include "game/system/vm/vm.h" using namespace ee; @@ -378,11 +377,6 @@ int ShutdownMachine() { ShutdownSound(); ShutdownGoalProto(); - // OpenGOAL only - kill ps2 VM - if (VM::use) { - VM::vm_kill(); - } - Msg(6, "kernel: machine shutdown\n"); return 0; } diff --git a/game/kernel/jak2/fileio.cpp b/game/kernel/jak2/fileio.cpp index aac888109f..459667f180 100644 --- a/game/kernel/jak2/fileio.cpp +++ b/game/kernel/jak2/fileio.cpp @@ -161,7 +161,7 @@ char* MakeFileName(int type, const char* name, int new_string) { // game CGO file (unused, all DGO/CGOs loaded through IOP) sprintf(buf, "game/dgo%d/%s.cgo", DGO_FILE_VERSION, name); } else if (type == CNT_FILE_TYPE) { - // game cnt file (continue point?) + // game cnt file. leftover from jak 1. sprintf(buf, "%sfinal/res%d/game-cnt.go", prefix, 1); } else if (type == RES_FILE_TYPE) { // RES go file? diff --git a/game/kernel/jak2/kdgo.cpp b/game/kernel/jak2/kdgo.cpp index f4bf9bf5ab..a7f6b13d5f 100644 --- a/game/kernel/jak2/kdgo.cpp +++ b/game/kernel/jak2/kdgo.cpp @@ -15,6 +15,87 @@ namespace jak2 { +RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP +RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers + +void kdgo_init_globals() { + sLastMsg = nullptr; + memset(sMsg, 0, sizeof(sMsg)); +} + +/*! + * Send message to IOP to start loading a new DGO file + * Uses a double-buffered message buffer + * @param name: the name of the DGO file + * @param buffer1 : one of the two file loading buffers + * @param buffer2 : the other of the two file loading buffers + * @param currentHeap : the current heap (for loading directly into the heap). + * + * DONE, + * MODIFIED : Added print statement to indicate when DGO load starts. + */ +void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr currentHeap) { + u8 msgID = sMsgNum; + RPC_Dgo_Cmd* mess = sMsg + sMsgNum; + sMsgNum = sMsgNum ^ 1; // toggle message buffer. + RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished + + // put a dummy value here just to make sure the IOP overwrites it. + sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666 + + // inform IOP of buffers + sMsg[msgID].buffer1 = buffer1.offset; + sMsg[msgID].buffer2 = buffer2.offset; + + // also give a heap pointer so it can load the last object file directly into the heap to save the + // precious time. + sMsg[msgID].buffer_heap_top = currentHeap.offset; + + // file name + strcpy(sMsg[msgID].name, name); + lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset, + buffer2.offset, currentHeap.offset); + // this RPC will return once we have loaded the first object file. + // but we call async, so we don't block here. + RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess, + sizeof(RPC_Dgo_Cmd)); + sLastMsg = mess; +} + +/*! + * Get the next object in the DGO. Will block until something is loaded. + * @param lastObjectFlag: will get set to 1 if this is the last object. + * + * DONE, + * MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer) + */ +Ptr GetNextDGO(u32* lastObjectFlag) { + *lastObjectFlag = 1; + // Wait for RPC function to respond. This will happen once the first object file is loaded. + RpcSync(DGO_RPC_CHANNEL); + Ptr buffer(0); + if (sLastMsg) { + // if we got a good result, get pointer to object + if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) { + buffer.offset = + sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object. + } + + // not the last one, so don't set the flag. + if (sLastMsg->result == DGO_RPC_RESULT_MORE) { + *lastObjectFlag = 0; + } + + // no pending message. + sLastMsg = nullptr; + } else { + // I don't see how this case can happen unless there's a bug. The game does check for this and + // nothing in this case. (maybe from GOAL this can happen?) + printf("last message not set!\n"); + } + return buffer; +} + /*! * Instruct the IOP to continue loading the next object. * Only should be called once it is safe to overwrite the previous. diff --git a/game/kernel/jak2/kdgo.h b/game/kernel/jak2/kdgo.h index f8cebd9a6c..54904a1c46 100644 --- a/game/kernel/jak2/kdgo.h +++ b/game/kernel/jak2/kdgo.h @@ -1,8 +1,10 @@ #pragma once #include "common/common_types.h" +#include "game/common/dgo_rpc_types.h" #include "game/kernel/common/Ptr.h" #include "game/kernel/common/kmalloc.h" + namespace jak2 { void load_and_link_dgo_from_c(const char* name, Ptr heap, @@ -14,4 +16,8 @@ void load_and_link_dgo_from_c_fast(const char* name, Ptr heap, u32 linkFlag, s32 bufferSize); +void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size); +void kdgo_init_globals(); +extern RPC_Dgo_Cmd sMsg[2]; +extern RPC_Dgo_Cmd* sLastMsg; } // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/klink.cpp b/game/kernel/jak2/klink.cpp index ecf06e2583..911e760900 100644 --- a/game/kernel/jak2/klink.cpp +++ b/game/kernel/jak2/klink.cpp @@ -609,7 +609,7 @@ Ptr link_and_exec(Ptr data, // probably won't end well... } link_control lc; - lc.begin(data, name, size, heap, flags); + lc.jak1_jak2_begin(data, name, size, heap, flags); uint32_t done; do { done = lc.jak2_work(); @@ -635,8 +635,8 @@ u64 link_and_exec_wrapper(u64* args) { */ uint64_t link_begin(u64* args) { // object data, name size, heap flags - saved_link_control.begin(Ptr(args[0]), Ptr(args[1]).c(), args[2], - Ptr(args[3]), args[4]); + saved_link_control.jak1_jak2_begin(Ptr(args[0]), Ptr(args[1]).c(), args[2], + Ptr(args[3]), args[4]); auto work_result = saved_link_control.jak2_work(); // if we managed to finish in one shot, take care of calling finish if (work_result) { diff --git a/game/kernel/jak2/kmachine.cpp b/game/kernel/jak2/kmachine.cpp index f086ff3f1c..12be15af00 100644 --- a/game/kernel/jak2/kmachine.cpp +++ b/game/kernel/jak2/kmachine.cpp @@ -42,7 +42,6 @@ #include "game/sce/libgraph.h" #include "game/sce/sif_ee.h" #include "game/sce/stubs.h" -#include "game/system/vm/vm.h" using namespace ee; @@ -434,10 +433,6 @@ int ShutdownMachine() { ShutdownGoalProto(); - // OpenGOAL only - kill ps2 VM - if (VM::use) { - VM::vm_kill(); - } return 0; } @@ -514,10 +509,10 @@ u64 kopen(u64 fs, u64 name, u64 mode) { char buffer[128]; // sprintf(buffer, "host:%s", Ptr(name)->data()); sprintf(buffer, "%s", Ptr(name)->data()); - if (!strcmp(Ptr>(mode)->name_cstr(), "read")) { + if (!strcmp(symbol_name_cstr(*Ptr>(mode)), "read")) { // 0x1 file_stream->file = sceOpen(buffer, SCE_RDONLY); - } else if (!strcmp(Ptr>(mode)->name_cstr(), "append")) { + } else if (!strcmp(symbol_name_cstr(*Ptr>(mode)), "append")) { // new in jak 2! // 0x202 file_stream->file = sceOpen(buffer, SCE_CREAT | SCE_WRONLY); diff --git a/game/kernel/jak2/kscheme.cpp b/game/kernel/jak2/kscheme.cpp index 3ae7a614ac..37946a4120 100644 --- a/game/kernel/jak2/kscheme.cpp +++ b/game/kernel/jak2/kscheme.cpp @@ -49,10 +49,15 @@ void kscheme_init_globals() { u64 new_illegal(u32 allocation, u32 type) { (void)allocation; MsgErr("dkernel: illegal attempt to call new method of static object type %s\n", - Ptr(type)->symbol->name_cstr()); + symbol_name_cstr(*Ptr(type)->symbol)); return s7.offset; } +u32 u32_in_fixed_sym(u32 offset) { + return Ptr>(s7.offset + offset)->value(); +} + +namespace { template Ptr> sym_to_string_ptr(Ptr> in) { return Ptr>(in.offset + SYM_TO_STRING_OFFSET); @@ -68,13 +73,10 @@ Ptr sym_to_hash(Ptr> in) { return Ptr(in.offset + SYM_TO_HASH_OFFSET); } -u32 u32_in_fixed_sym(u32 offset) { - return Ptr>(s7.offset + offset)->value(); -} - void fixed_sym_set(u32 offset, u32 value) { Ptr>(s7.offset + offset)->value() = value; } +} // namespace u64 alloc_from_heap(u32 heap_symbol, u32 type, s32 size, u32 pp) { using namespace jak2_symbols; @@ -1409,7 +1411,6 @@ u64 inspect_type(u32 obj) { } else { auto typ = Ptr(obj); auto sym = typ->symbol; - ; cprintf("[%8x] type\n\tname: %s\n\tparent: ", obj, sym_to_string(sym)->data()); print_object(typ->parent.offset); @@ -1462,11 +1463,11 @@ u64 inspect_link_block(u32 ob) { return ob; } +namespace { Ptr>> get_fixed_type_symbol(u32 offset) { return (s7 + offset).cast>>(); } -namespace { u64 pack_type_flag(u64 methods, u64 heap_base, u64 size) { return (methods << 32) + (heap_base << 16) + (size); } @@ -1799,7 +1800,6 @@ u64 loadc(const char* /*file_name*/, kheapinfo* /*heap*/, u32 /*flags*/) { return 0; } -// NOTE: copied from jak 1 u64 loado(u32 file_name_in, u32 heap_in) { char loadName[272]; Ptr file_name(file_name_in); @@ -1823,7 +1823,6 @@ u64 unload(u32 name) { return 0; } -// NOTE: copied from jak 1 s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags) { (void)filename; s32 sz; diff --git a/game/kernel/jak2/kscheme.h b/game/kernel/jak2/kscheme.h index 043d0af055..f7eb05df12 100644 --- a/game/kernel/jak2/kscheme.h +++ b/game/kernel/jak2/kscheme.h @@ -63,4 +63,10 @@ u64 call_goal_function_by_name(const char* name); u64 alloc_heap_memory(u32 heap, u32 size); u64 alloc_heap_object(u32 heap, u32 type, u32 size, u32 pp); u32 u32_in_fixed_sym(u32 offset); + +template +const char* symbol_name_cstr(const Symbol4& sym) { + return (const char*)(g_ee_main_mem + 4 + + *reinterpret_cast(&sym.foo + jak2::SYM_TO_STRING_OFFSET)); +} } // namespace jak2 diff --git a/game/kernel/jak2/ksound.cpp b/game/kernel/jak2/ksound.cpp index e5604224fb..1ec43e4579 100644 --- a/game/kernel/jak2/ksound.cpp +++ b/game/kernel/jak2/ksound.cpp @@ -9,7 +9,6 @@ namespace jak2 { * Set up some functions which are somewhat related to sound. */ void InitSoundScheme() { - make_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper); make_function_symbol_from_c("rpc-busy?", (void*)RpcBusy); make_function_symbol_from_c("test-load-dgo-c", (void*)LoadDGOTest); make_stack_arg_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper); diff --git a/game/kernel/jak3/fileio.cpp b/game/kernel/jak3/fileio.cpp new file mode 100644 index 0000000000..4d4445b721 --- /dev/null +++ b/game/kernel/jak3/fileio.cpp @@ -0,0 +1,254 @@ +#include "fileio.h" + +#include + +#include "game/kernel/common/fileio.h" + +namespace jak3 { + +// This file naming system was used only in development, as it loads files from the development PC +// connected to the PS2 dev-kit. +// My theory is that the developers would use this to debug their level/art tools. They could use +// these file names to quickly load in new files and see if they worked correctly with the renderer, +// without needing to create/load entire new DGO files. +// They've been adding to this file over all 3 games, so I believe it is more than just a leftover +// from early jak 1. + +/*! + * Convert a file-name like $CODE/thing to the appropriate file path on the development computer. + */ +char* DecodeFileName(const char* name) { + char* result; + + if (name[0] == '$') { + if (!strncmp(name, "$TEXTURE/", 9)) { + result = MakeFileName(TX_PAGE_FILE_TYPE, name + 9, 0); + } else if (!strncmp(name, "$ART_GROUP/", 0xb)) { + result = MakeFileName(ART_GROUP_FILE_TYPE, name + 0xb, 0); + } else if (!strncmp(name, "$LEVEL/", 7)) { + int len = (int)strlen(name); + if (name[len - 4] == '.') { + result = MakeFileName(LEVEL_WITH_EXTENSION_FILE_TYPE, name + 7, 0); + } else { + // level files can omit a file type if desired + result = MakeFileName(LEVEL_FILE_TYPE, name + 7, 0); + } + } else if (!strncmp(name, "$FINAL/", 6)) { // in jak2, this is FINAL instead of DATA + result = MakeFileName(DATA_FILE_TYPE, name + 6, 0); + } else if (!strncmp(name, "$CODE/", 6)) { + result = MakeFileName(CODE_FILE_TYPE, name + 6, 0); + } else if (!strncmp(name, "$RES/", 5)) { + result = MakeFileName(RES_FILE_TYPE, name + 5, 0); + } else if (!strncmp(name, "$MISC/", 6)) { + result = MakeFileName(MISC_FILE_TYPE, name + 6, 0); + } else if (!strncmp(name, "$MAP/", 5)) { + result = MakeFileName(MAP_FILE_TYPE, name + 5, 0); + } else if (!strncmp(name, "$ISO/", 5)) { + result = MakeFileName(ISO_FILE_TYPE, name + 5, 0); + } else { + printf("[ERROR] DecodeFileName: UNKNOWN FILE NAME %s\n", name); + result = nullptr; + } + } else { + // no prefix. Treat this as a code file + return MakeFileName(CODE_FILE_TYPE, name, 0); + } + return result; +} + +/*! + * Create a file name that looks in the appropriate folder in ND's development environment. + * This is a bit of dumping ground for all possible files they'd load. + */ +char* MakeFileName(int type, const char* name, int new_string) { + using namespace versions::jak3; + // start with network filesystem + // kstrcpy(buffer_633, "host:"); + kstrcpy(buffer_633, ""); + char* buf = strend(buffer_633); + + // prefix to build directory + char prefix[64]; + kstrcpy(prefix, FOLDER_PREFIX); + + switch (type) { + // Unused files that could be used to exchange data between the dev PS2 and the GOAL compiler. + case LISTENER_TO_KERNEL_FILE_TYPE: + kstrcpy(buf, "kernel/LISTENERTOKERNEL"); + break; + case KERNEL_TO_LISTENER_FILE_TYPE: + kstrcpy(buf, "kernel/KERNELTOLISTENER"); + break; + + // A GOAL object file containing code built from the GOAL compiler. + case CODE_FILE_TYPE: + sprintf(buf, "game/obj/%s.o", name); + break; + + // Unused, opening the gamepad as a file. + case GAMEPAD_FILE_TYPE: + sprintf(buffer_633, "pad:0"); + break; + + // Locks for the unused kernel/listener interface. (funny that they added this after...) + case LISTENER_TO_KERNEL_LOCK_FILE_TYPE: + kstrcpy(buf, "kernel/LISTENERTOKERNEL_LOCK"); + break; + case KERNEL_TO_LISTENER_LOCK_FILE_TYPE: + kstrcpy(buf, "kernel/KERNELTOLISTENER_LOCK"); + break; + + // Host0 IOP modules (stored on the linux SBC inside the dev ps2 itself!) + case IOP_MODULE_FILE_TYPE: // 8 + sprintf(buffer_633, "host0:/usr/local/sce/iop/modules/%s.irx", name); + break; + + // plain GOAL data object file + case DATA_FILE_TYPE: // 0x20 + sprintf(buf, "%sfinal/%s.go", prefix, name); + break; + + // texture page + case TX_PAGE_FILE_TYPE: // 0x21 + sprintf(buf, "%sdata/texture-page%d/%s.go", prefix, TX_PAGE_VERSION, name); + break; + + // joint animation + case JA_FILE_TYPE: // 0x22 + sprintf(buf, "%sdb/artdata%d/%s-ja.go", prefix, ART_FILE_VERSION, name); + break; + + // joint geo (skeleton) + case JG_FILE_TYPE: // 0x23 + sprintf(buf, "%sdb/artdata%d/%s-jg.go", prefix, ART_FILE_VERSION, name); + break; + + // mesh animation (unused) + case MA_FILE_TYPE: // 0x24 + sprintf(buf, "%sdb/artdata%d/%s-ma.go", prefix, ART_FILE_VERSION, name); + break; + + // likely art-mesh-geo, and unused. Maybe was used before MERC? + case MG_FILE_TYPE: // 0x25 + sprintf(buf, "%sdb/artdata%d/%s-mg.go", prefix, ART_FILE_VERSION, name); + break; + + // text group perhaps? + case TG_FILE_TYPE: + sprintf(buf, "%sdb/%s-tg.go", prefix, name); + break; + + // level file + case LEVEL_FILE_TYPE: // 0x27 + sprintf(buf, "%sdb/level%d/%s-bt.go", prefix, LEVEL_FILE_VERSION, name); + break; + + // Everybody's favorite "art group" file. Container of different art. + case ART_GROUP_FILE_TYPE: // 0x30 + sprintf(buf, "%sfinal/art-group%d/%s-ag.go", prefix, ART_FILE_VERSION, name); + break; + + // GOAL data object file containing visibility data. This likely contained the visibility data + // that's included in the BSP file. + case VS_FILE_TYPE: // 0x31 + sprintf(buf, "%sfinal/level%d/%s-vs.go", prefix, LEVEL_FILE_VERSION, name); + break; + + // GOAL data object file containing text. Likely the same format as the .TXT in final ISOs. + case TX_FILE_TYPE: // 0x32 + sprintf(buf, "%sfinal/res%d/%s-tx.go", prefix, 1, name); + break; + + // Binary format visibility. Likely the format of Jak 1's .VIS files. + case VS_BIN_FILE_TYPE: // 0x33 + sprintf(buf, "%sfinal/level%d/%s-vs.bin", prefix, LEVEL_FILE_VERSION, name); + break; + + // DGO description files. These contain a list of files inside each DGO. + case DGO_TXT_FILE_TYPE: // 0x34 + sprintf(buf, "%sfinal/dgo%d/%s.txt", prefix, DGO_FILE_VERSION, name); + break; + + // Level file! but you have to provide the extension. + case LEVEL_WITH_EXTENSION_FILE_TYPE: // 0x35 + sprintf(buf, "%sfinal/level%d/%s", prefix, LEVEL_FILE_VERSION, name); + break; + + // DGO and CGO files. These can exist in either final/ or game/ + case DATA_DGO_FILE_TYPE: // 0x36 + sprintf(buf, "%sfinal/dgo%d/%s.dgo", prefix, DGO_FILE_VERSION, name); + break; + case GAME_DGO_FILE_TYPE: // 0x37 + sprintf(buf, "game/dgo%d/%s.dgo", DGO_FILE_VERSION, name); + break; + case DATA_CGO_FILE_TYPE: // 0x38 + sprintf(buf, "%sfinal/dgo%d/%s.cgo", prefix, DGO_FILE_VERSION, name); + break; + case GAME_CGO_FILE_TYPE: // 0x39 + sprintf(buf, "game/dgo%d/%s.cgo", DGO_FILE_VERSION, name); + break; + + // Jak 1 had a weird game-cnt.gco file containing the total number of orbs/cells. + case CNT_FILE_TYPE: // 0x3a + sprintf(buf, "%sfinal/res%d/game-cnt.go", prefix, 1); + break; + + // Any res file with .go extension. + case RES_FILE_TYPE: // 0x3b + sprintf(buf, "%sfinal/res%d/%s.go", prefix, 1, name); + break; + + // sound bank (sound effects) + case SND_BNK_FILE_TYPE: // 0x3c + sprintf(buf, "%sfinal/sound%d/%s.bnk", prefix, 1, name); // v1 + break; + + // music file + case MUSIC_BNK_FILE_TYPE: // 0x3d + sprintf(buf, "%sfinal/music%d/%s.bnk", prefix, 1, name); // v1 + break; + + // vag file, but it probably doesn't work due to the file extension. + case VAG_FILE_TYPE: // 0x3e + // interestingly, jak 2 used vagwad2, but jak 3 doesn't. But the memory bug is still there. + sprintf(buf, "%sfinal/vagwad/%s.%s", prefix, name, ""); // v1, memory bug here + break; + + // whatever you want. + case MISC_FILE_TYPE: // 0x3f + sprintf(buf, "%sfinal/misc/%s", prefix, name); + break; + + // possible minimap/bigmap data + case MAP_FILE_TYPE: + sprintf(buf, "%sfinal/map%d/%s", prefix, 1, name); // v1 + break; + + // jak 3 cloth animation file. + case CL_FILE_TYPE: // 0x41 + sprintf(buf, "%sdb/artdata%d/%s-cl.go", prefix, ART_FILE_VERSION, name); + break; + + // no idea + case REFPLANT_FILE_TYPE: // 0x301 + sprintf(buf, "%sdb/refplant/%s", prefix, name); + break; + default: + printf("UNKNOWN FILE TYPE %d\n", type); + } + + char* result; + if (!new_string) { + // return pointer to static filename buffer + result = buffer_633; + } else { + // or create a new string on the global heap. + int l = (int)strlen(buffer_633); + result = (char*)kmalloc(kglobalheap, l + 1, 0, "filename").c(); + kstrcpy(result, buffer_633); + } + + return result; +} + +} // namespace jak3 diff --git a/game/kernel/jak3/fileio.h b/game/kernel/jak3/fileio.h new file mode 100644 index 0000000000..a564320bca --- /dev/null +++ b/game/kernel/jak3/fileio.h @@ -0,0 +1,6 @@ +#pragma once + +namespace jak3 { +char* MakeFileName(int type, const char* name, int new_string); +char* DecodeFileName(const char* name); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kboot.cpp b/game/kernel/jak3/kboot.cpp new file mode 100644 index 0000000000..4da57ffcee --- /dev/null +++ b/game/kernel/jak3/kboot.cpp @@ -0,0 +1,163 @@ +#include "kboot.h" + +#include + +#include "common/repl/util.h" +#include "common/util/Timer.h" + +#include "game/common/game_common_types.h" +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/Symbol4.h" +#include "game/kernel/common/kboot.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/common/ksocket.h" +#include "game/kernel/jak3/klisten.h" +#include "game/kernel/jak3/kmachine.h" +#include "game/sce/libscf.h" + +// KernelDispatch__3ndiPFv_x +// KernelCheckAndDispatch__3ndiv +// KernelShutdown__3ndii +// main + +namespace jak3 { +void KernelCheckAndDispatch(); + +char DebugBootUser[64]; +char DebugBootArtGroup[64]; + +void kboot_init_globals() { + memset(DebugBootUser, 0, sizeof(DebugBootUser)); + memset(DebugBootArtGroup, 0, sizeof(DebugBootArtGroup)); + // strcpy(DebugBootUser, "unknown"); + // CHANGED : let's just try to find the username automatically by default! + // the default is still "unknown" + auto username = REPL::find_repl_username(); + strcpy(DebugBootUser, username.c_str()); +} + +s32 goal_main(int argc, const char* const* argv) { + // only in PC port + InitParms(argc, argv); + + masterConfig.aspect = ee::sceScfGetAspect(); + auto sony_language = ee::sceScfGetLanguage(); + masterConfig.inactive_timeout = 0; + masterConfig.volume = 100; + masterConfig.timeout = 0; + switch (sony_language) { + case SCE_JAPANESE_LANGUAGE: + masterConfig.language = 6; // ??? + break; + case SCE_FRENCH_LANGUAGE: // 2 -> 1 + masterConfig.language = (u16)Language::French; + break; + case SCE_SPANISH_LANGUAGE: // 3 -> 3 + masterConfig.language = (u16)Language::Spanish; + break; + case SCE_GERMAN_LANGUAGE: // 4 -> 2 + masterConfig.language = (u16)Language::German; + break; + case SCE_ITALIAN_LANGUAGE: // 5 -> 4 + masterConfig.language = (u16)Language::Italian; + break; + case SCE_PORTUGUESE_LANGUAGE: + masterConfig.language = (u16)Language::Portuguese; + break; + default: + masterConfig.language = (u16)Language::English; + break; + } + // Set up aspect ratio override in demo + if (!strcmp(DebugBootMessage, "demo") || !strcmp(DebugBootMessage, "demo-shared")) { + masterConfig.aspect = SCE_ASPECT_FULL; + } + // removed in PC port + // DiskBoot = 1; + // MasterDebug = 0; + // DebugSegment = 0; + + // Launch GOAL! + if (InitMachine() >= 0) { // init kernel + KernelCheckAndDispatch(); // run kernel + ShutdownMachine(); // kernel died, we should too. + // movie playback stuff removed. + } else { + fprintf(stderr, "InitMachine failed\n"); + exit(1); + } + return 0; +} + +void KernelDispatch(u32 dispatcher_func) { + // place our stack at the end of EE memory + u64 goal_stack = u64(g_ee_main_mem) + EE_MAIN_MEM_SIZE - 8; + + // try to get a message from the listener, and process it if needed + Ptr new_message = WaitForMessageAndAck(); + if (new_message.offset) { + ProcessListenerMessage(new_message); + } + + // remember the old listener + auto old_listener_function = ListenerFunction->value(); + + // run the kernel! + Timer dispatch_timer; + if (MasterUseKernel) { + call_goal_on_stack(Ptr(dispatcher_func), goal_stack, s7.offset, g_ee_main_mem); + } else { + // added, just calls the listener function + if (ListenerFunction->value() != s7.offset) { + auto result = call_goal_on_stack(Ptr(ListenerFunction->value()), goal_stack, + s7.offset, g_ee_main_mem); +#ifdef __linux__ + cprintf("%ld\n", result); +#else + cprintf("%lld\n", result); +#endif + ListenerFunction->value() = s7.offset; + } + } + + float time_ms = dispatch_timer.getMs(); + if (time_ms > 50) { + printf("Kernel dispatch time: %.3f ms\n", time_ms); + } + + // flush stdout + ClearPending(); + + // now run the extra "kernel function" + auto bonus_function = KernelFunction->value(); + if (bonus_function != s7.offset) { + // clear the pending kernel function + KernelFunction->value() = s7.offset; + // and run + call_goal_on_stack(Ptr(bonus_function), goal_stack, s7.offset, g_ee_main_mem); + } + + // send ack to indicate that the listener function has been processed and the result printed + if (MasterDebug && ListenerFunction->value() != old_listener_function) { + SendAck(); + } + + // prevent crazy spinning if we're not vsyncing (added) + if (time_ms < 4) { + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + } +} + +void KernelShutdown(u32 reason) { + MasterExit = (RuntimeExitStatus)reason; +} + +void KernelCheckAndDispatch() { + while (MasterExit == RuntimeExitStatus::RUNNING) { + KernelDispatch(kernel_dispatcher->value()); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kboot.h b/game/kernel/jak3/kboot.h new file mode 100644 index 0000000000..fd9231d4bb --- /dev/null +++ b/game/kernel/jak3/kboot.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +extern char DebugBootUser[64]; +extern char DebugBootArtGroup[64]; +void kboot_init_globals(); +void KernelShutdown(u32 reason); +s32 goal_main(int argc, const char* const* argv); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kdgo.cpp b/game/kernel/jak3/kdgo.cpp new file mode 100644 index 0000000000..23711ac74a --- /dev/null +++ b/game/kernel/jak3/kdgo.cpp @@ -0,0 +1,210 @@ +#include "kdgo.h" + +#include "common/global_profiler/GlobalProfiler.h" +#include "common/log/log.h" +#include "common/util/Timer.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/jak3/klink.h" + +namespace jak3 { + +jak3::RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP +jak3::RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers +uint16_t cgo_id = 10; + +void kdgo_init_globals() { + sLastMsg = nullptr; + memset(sMsg, 0, sizeof(sMsg)); + cgo_id = 10; +} + +/*! + * Send message to IOP to start loading a new DGO file + * Uses a double-buffered message buffer + * @param name: the name of the DGO file + * @param buffer1 : one of the two file loading buffers + * @param buffer2 : the other of the two file loading buffers + * @param currentHeap : the current heap (for loading directly into the heap). + * + * DONE, + * MODIFIED : Added print statement to indicate when DGO load starts. + */ +void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr currentHeap) { + u8 msgID = sMsgNum; + RPC_Dgo_Cmd* mess = sMsg + sMsgNum; + sMsgNum = sMsgNum ^ 1; // toggle message buffer. + RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished + + // put a dummy value here just to make sure the IOP overwrites it. + sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666 + + // inform IOP of buffers + sMsg[msgID].buffer1 = buffer1.offset; + sMsg[msgID].buffer2 = buffer2.offset; + + // also give a heap pointer so it can load the last object file directly into the heap to save the + // precious time. + sMsg[msgID].buffer_heap_top = currentHeap.offset; + + // new for Jak 3: a unique ID. + sMsg[msgID].cgo_id = cgo_id; + cgo_id++; + + // file name + strcpy(sMsg[msgID].name, name); + lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset, + buffer2.offset, currentHeap.offset); + // this RPC will return once we have loaded the first object file. + // but we call async, so we don't block here. + RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess, + sizeof(RPC_Dgo_Cmd)); + sLastMsg = mess; +} + +/*! + * Get the next object in the DGO. Will block until something is loaded. + * @param lastObjectFlag: will get set to 1 if this is the last object. + * + * DONE, + * MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer) + */ +Ptr GetNextDGO(u32* lastObjectFlag) { + *lastObjectFlag = 1; + // Wait for RPC function to respond. This will happen once the first object file is loaded. + RpcSync(DGO_RPC_CHANNEL); + Ptr buffer(0); + if (sLastMsg) { + // if we got a good result, get pointer to object + if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) { + buffer.offset = + sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object. + } + + // not the last one, so don't set the flag. + if (sLastMsg->result == DGO_RPC_RESULT_MORE) { + *lastObjectFlag = 0; + } + + // no pending message. + sLastMsg = nullptr; + } else { + // I don't see how this case can happen unless there's a bug. The game does check for this and + // nothing in this case. (maybe from GOAL this can happen?) + printf("last message not set!\n"); + } + return buffer; +} + +/*! + * Instruct the IOP to continue loading the next object. + * Only should be called once it is safe to overwrite the previous. + * @param heapPtr : pointer to heap so the IOP could try to load directly into a heap if it wants. + * This should be updated after each object file load to make sure the IOP knows the exact location + * of the end of the GOAL heap data. + * + * Unlike jak 1, we update buffer1 and buffer2 here for borrow heap loads. + */ +void ContinueLoadingDGO(Ptr b1, Ptr b2, Ptr heapPtr) { + u32 msgID = sMsgNum; + jak3::RPC_Dgo_Cmd* sendBuff = sMsg + sMsgNum; + sMsgNum = sMsgNum ^ 1; + sendBuff->result = DGO_RPC_RESULT_INIT; + sMsg[msgID].buffer1 = b1.offset; + sMsg[msgID].buffer2 = b2.offset; + sMsg[msgID].buffer_heap_top = heapPtr.offset; + // the IOP will wait for this RpcCall to continue the DGO state machine. + RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_NEXT_FNO, true, sendBuff, sizeof(jak3::RPC_Dgo_Cmd), + sendBuff, sizeof(jak3::RPC_Dgo_Cmd)); + // this async RPC call will complete when the next object is fully loaded. + sLastMsg = sendBuff; +} +/*! + * Load and link a DGO file. + * This does not use the mutli-threaded linker and will block until the entire file is done. + */ +void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size) { + auto name = Ptr(name_gstr + 4).c(); + auto heap = Ptr(heap_info); + load_and_link_dgo_from_c(name, heap, flag, buffer_size, false); +} + +/*! + * Load and link a DGO file. + * This does not use the mutli-threaded linker and will block until the entire file is done.e + */ +void load_and_link_dgo_from_c(const char* name, + Ptr heap, + u32 linkFlag, + s32 bufferSize, + bool jump_from_c_to_goal) { + Timer timer; + lg::debug("[Load and Link DGO From C] {}", name); + u32 oldShowStall = sShowStallMsg; + + // remember where the heap top point is so we can clear temporary allocations + auto oldHeapTop = heap->top; + + // allocate temporary buffers from top of the given heap + // align 64 for IOP DMA + // note: both buffers named dgo-buffer-2 + auto buffer2 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2"); + auto buffer1 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2"); + + // build filename. If no extension is given, default to CGO. + char fileName[16]; + kstrcpyup(fileName, name); + if (fileName[strlen(fileName) - 4] != '.') { + strcat(fileName, ".CGO"); + } + + // no stall messages, as this is a blocking load and when spending 100% CPU time on linking, + // the linker can beat the DVD drive. + sShowStallMsg = 0; + + // start load on IOP. + BeginLoadingDGO( + fileName, buffer1, buffer2, + Ptr((heap->current + 0x3f).offset & 0xffffffc0)); // 64-byte aligned for IOP DMA + + u32 lastObjectLoaded = 0; + while (!lastObjectLoaded) { + // check to see if next object is loaded (I believe it always is?) + auto dgoObj = GetNextDGO(&lastObjectLoaded); + if (!dgoObj.offset) { + continue; + } + + // if we're on the last object, it is loaded at cheap->current. So we can safely reset the two + // dgo-buffer allocations. We do this _before_ we link! This way, the last file loaded has more + // heap available, which is important when we need to use the entire memory. + if (lastObjectLoaded) { + heap->top = oldHeapTop; + } + + // determine the size and name of the object we got + auto obj = dgoObj + 0x40; // seek past dgo object header + u32 objSize = *(dgoObj.cast()); // size from object's link block + + char objName[64]; + strcpy(objName, (dgoObj + 4).cast().c()); // name from dgo object header + lg::debug("[link and exec] {:18s} {} {:6d} heap-use {:8d} {:8d}: 0x{:x}", objName, + lastObjectLoaded, objSize, kheapused(kglobalheap), + kdebugheap.offset ? kheapused(kdebugheap) : 0, kglobalheap->current.offset); + { + auto p = scoped_prof(fmt::format("link-{}", objName).c_str()); + link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now! + } + + // inform IOP we are done + if (!lastObjectLoaded) { + ContinueLoadingDGO(buffer1, buffer2, Ptr((heap->current + 0x3f).offset & 0xffffffc0)); + } + } + lg::info("load_and_link_dgo_from_c took {:.3f} s\n", timer.getSeconds()); + sShowStallMsg = oldShowStall; +} + +} // namespace jak3 diff --git a/game/kernel/jak3/kdgo.h b/game/kernel/jak3/kdgo.h new file mode 100644 index 0000000000..b7ff17cbf8 --- /dev/null +++ b/game/kernel/jak3/kdgo.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kmalloc.h" + +namespace jak3 { +void load_and_link_dgo_from_c(const char* name, + Ptr heap, + u32 linkFlag, + s32 bufferSize, + bool jump_from_c_to_goal); +void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size); +void load_and_link_dgo_from_c_fast(const char* name, + Ptr heap, + u32 linkFlag, + s32 bufferSize); +void kdgo_init_globals(); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kdsnetm.cpp b/game/kernel/jak3/kdsnetm.cpp new file mode 100644 index 0000000000..529874a3f6 --- /dev/null +++ b/game/kernel/jak3/kdsnetm.cpp @@ -0,0 +1,3 @@ + + +#include "kdsnetm.h" diff --git a/game/kernel/jak3/kdsnetm.h b/game/kernel/jak3/kdsnetm.h new file mode 100644 index 0000000000..ae16523cd1 --- /dev/null +++ b/game/kernel/jak3/kdsnetm.h @@ -0,0 +1,3 @@ +#pragma once + +namespace jak3 {} \ No newline at end of file diff --git a/game/kernel/jak3/klink.cpp b/game/kernel/jak3/klink.cpp new file mode 100644 index 0000000000..b336bc8832 --- /dev/null +++ b/game/kernel/jak3/klink.cpp @@ -0,0 +1,656 @@ +#include "klink.h" + +#include "common/common_types.h" +#include "common/goal_constants.h" +#include "common/symbols.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/jak3/kmalloc.h" +#include "game/kernel/jak3/kscheme.h" +#include "game/mips2c/mips2c_table.h" + +#include "third-party/fmt/core.h" + +namespace { +bool is_opengoal_object(void* data) { + u32 first_word; + memcpy(&first_word, data, 4); + return first_word != 0 && first_word != UINT32_MAX; +} +constexpr bool link_debug_printfs = false; +} // namespace + +void link_control::jak3_begin(Ptr object_file, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags) { + if (is_opengoal_object(object_file.c())) { + m_opengoal = true; + // save data from call to begin + m_object_data = object_file; + kstrcpy(m_object_name, name); + m_object_size = size; + m_heap = heap; + m_flags = flags; + + // initialize link control + m_entry.offset = 0; + m_heap_top = m_heap->top; + m_keep_debug = false; + m_opengoal = true; + m_busy = true; + + if (link_debug_printfs) { + char* goal_name = object_file.cast().c(); + printf("link %s\n", m_object_name); + printf("link_control::begin %c%c%c%c\n", goal_name[0], goal_name[1], goal_name[2], + goal_name[3]); + } + + // points to the beginning of the linking data + m_link_block_ptr = object_file + BASIC_OFFSET; + m_code_size = 0; + m_code_start = object_file; + m_state = 0; + m_segment_process = 0; + + ObjectFileHeader* ofh = m_link_block_ptr.cast().c(); + if (ofh->goal_version_major != versions::GOAL_VERSION_MAJOR) { + fprintf( + stderr, + "VERSION ERROR: C Kernel built from GOAL %d.%d, but object file %s is from GOAL %d.%d\n", + versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR, name, ofh->goal_version_major, + ofh->goal_version_minor); + ASSERT(false); + } + if (link_debug_printfs) { + printf("Object file header:\n"); + printf(" GOAL ver %d.%d obj %d len %d\n", ofh->goal_version_major, ofh->goal_version_minor, + ofh->object_file_version, ofh->link_block_length); + printf(" segment count %d\n", ofh->segment_count); + for (int i = 0; i < N_SEG; i++) { + printf(" seg %d link 0x%04x, 0x%04x data 0x%04x, 0x%04x\n", i, ofh->link_infos[i].offset, + ofh->link_infos[i].size, ofh->code_infos[i].offset, ofh->code_infos[i].size); + } + } + + m_version = ofh->object_file_version; + if (ofh->object_file_version < 4) { + // three segment file + + // seek past the header + m_object_data.offset += ofh->link_block_length; + // todo, set m_code_size + + if (m_link_block_ptr.offset < m_heap->base.offset || + m_link_block_ptr.offset >= m_heap->top.offset) { + // the link block is outside our heap, or in the top of our heap. It's somebody else's + // problem. + if (link_debug_printfs) { + printf("Link block somebody else's problem\n"); + } + + if (m_heap->base.offset <= m_object_data.offset && // above heap base + m_object_data.offset < m_heap->top.offset && // less than heap top (not needed?) + m_object_data.offset < m_heap->current.offset) { // less than heap current + if (link_debug_printfs) { + printf("Code block in the heap, kicking it out for copy into heap\n"); + } + m_heap->current = m_object_data; + } + } else { + // in our heap, we need to move it so we can free up its space later on + if (link_debug_printfs) { + printf("Link block needs to be moved!\n"); + } + + // allocate space for a new one + auto new_link_block = kmalloc(m_heap, ofh->link_block_length, KMALLOC_TOP, "link-block"); + auto old_link_block = m_link_block_ptr - BASIC_OFFSET; + + // copy it (was ultimate memcpy, but just use normal one to make it easier) + memmove(new_link_block.c(), old_link_block.c(), ofh->link_block_length); + m_link_block_ptr = new_link_block + BASIC_OFFSET; + + // if we can save some memory here + if (old_link_block.offset < m_heap->current.offset) { + if (link_debug_printfs) { + printf("Kick out old link block\n"); + } + m_heap->current = old_link_block; + } + } + } else { + ASSERT_MSG(false, "UNHANDLED OBJECT FILE VERSION"); + } + + if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) { + m_keep_debug = true; + } + } else { + m_opengoal = false; + if (heap == kglobalheap) { + jak3::kmemopen_from_c(heap, name); + m_on_global_heap = true; + } else { + m_on_global_heap = false; + } + m_object_data = object_file; + kstrcpy(this->m_object_name, name); + m_object_size = size; + // l_hdr = (LinkHdrWithType*)this->m_object_data; + LinkHeaderV5* l_hdr = (LinkHeaderV5*)m_object_data.c(); + m_flags = flags; + u16 version = l_hdr->core.version; + ASSERT(version == 5); // I think, since there's only a work v5. + m_heap_top = heap->top; + // this->unk_init1 = 1; TODO + m_busy = true; + m_heap = heap; + // this->m_unk_init0_0 = 0; TODO + m_keep_debug = false; + m_link_hdr = &l_hdr->core; // m_hdr_ptr + m_code_size = 0; + // this->m_ptr_2 = l_hdr; just used for cache flush, so skip it! not really the right thing?? + // this->m_unk_init0_3 = 0; TODO + // this->m_unk_init0_4 = 0; TODO + // this->m_unk_init0_5 = 0; TODO + if (version == 4) { + ASSERT_NOT_REACHED(); + } else { + m_object_data.offset = object_file.offset + l_hdr->core.length_to_get_to_code; + if (version == 5) { + static_assert(0x50 == sizeof(LinkHeaderV5)); + size = (size - l_hdr->core.link_length) - sizeof(LinkHeaderV5); + } else { + ASSERT_NOT_REACHED(); + } + m_code_size = size; + if ((u8*)m_link_hdr < m_heap->base.c() || (u8*)m_link_hdr >= m_heap->top.c()) { + // the link block is outside our heap, or in the allocated top part. + // so we ignore it, and leave it as somebody else's problem. + + // let's try to move the code part: + if (m_heap->base.offset <= m_object_data.offset && // above heap base + m_object_data.offset < m_heap->top.offset && // less than heap top (not needed?) + m_object_data.offset < m_heap->current.offset) { // less than heap current + if (link_debug_printfs) { + printf("Code block in the heap, kicking it out for copy into heap\n"); + } + m_heap->current = m_object_data; + } + } else { + m_moved_link_block = true; + if (m_link_hdr->version == 5) { + // the link block is inside our heap, but we'd like to avoid this. + // we'll copy the link block, and the header to the temporary part of our heap: + + // where we loaded the link data: + auto offset_to_link_data = m_link_hdr->length_to_get_to_link; + + // allocate memory for link data, and header + auto new_link_block_mem = kmalloc(m_heap, m_link_hdr->link_length + sizeof(LinkHeaderV5), + KMALLOC_TOP, "link-block"); + + // we'll place the header and link block back to back in the newly alloated block, + // so patch up the offset for this new layout before copying + m_link_hdr->length_to_get_to_link = sizeof(LinkHeaderV5); + + // move header! + memmove(new_link_block_mem.c(), object_file.c(), sizeof(LinkHeaderV5)); + + // move link data! + auto old_link_block = object_file.c() + offset_to_link_data; + memmove(new_link_block_mem.c() + sizeof(LinkHeaderV5), old_link_block, + m_link_hdr->link_length); + + // update our pointer to the link header core. + m_link_hdr = &((LinkHeaderV5*)new_link_block_mem.c())->core; + + // scary: update the heap to kick out all the link data (and likely the actual data too). + // we'll be relying on the linking process to copy the data as needed.l + if (old_link_block < m_heap->current.c()) { + if (link_debug_printfs) { + printf("Kick out old link block\n"); + } + m_heap->current.offset = old_link_block - g_ee_main_mem; + } + } else { + ASSERT_NOT_REACHED(); + } + } + } + if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) { + m_keep_debug = true; + } + } +} + +uint32_t link_control::jak3_work() { + auto old_debug_segment = DebugSegment; + if (m_keep_debug) { + DebugSegment = s7.offset + true_symbol_offset(g_game_version); + } + + // set type tag of link block + + uint32_t rv; + + if (m_version == 3) { + ASSERT(m_opengoal); + *((m_link_block_ptr - 4).cast()) = + *((s7 + jak3_symbols::FIX_SYM_LINK_BLOCK - 1).cast()); + rv = jak3_work_opengoal(); + } else if (m_version == 5) { + ASSERT(!m_opengoal); + *(u32*)(((u8*)m_link_hdr) - 4) = *((s7 + jak3_symbols::FIX_SYM_LINK_BLOCK - 1).cast()); + rv = jak3_work_v5(); + } else { + ASSERT_MSG(false, fmt::format("UNHANDLED OBJECT FILE VERSION {} IN WORK!", m_version)); + return 0; + } + + DebugSegment = old_debug_segment; + return rv; +} + +uint32_t link_control::jak3_work_v5() { + ASSERT_NOT_REACHED(); // save this for another day... + // TODO: there are some missing vars in begin. I just commented them out for now. +} + +namespace { +/*! + * Link a single relative offset (used for RIP) + */ +uint32_t cross_seg_dist_link_v3(Ptr link, + ObjectFileHeader* ofh, + int current_seg, + int size) { + // target seg, dist into mine, dist into target, patch loc in mine + uint8_t target_seg = *link; + ASSERT(target_seg < ofh->segment_count); + + uint32_t* link_data = (link + 1).cast().c(); + int32_t mine = link_data[0] + ofh->code_infos[current_seg].offset; + int32_t tgt = link_data[1] + ofh->code_infos[target_seg].offset; + int32_t diff = tgt - mine; + uint32_t offset_of_patch = link_data[2] + ofh->code_infos[current_seg].offset; + + // second debug segment case added for jak 2. + if (!ofh->code_infos[target_seg].offset || (!DebugSegment && target_seg == DEBUG_SEGMENT)) { + // we want to address GOAL 0. In the case where this is a rip-relative load or store, this + // will crash, which is what we want. If it's an lea and just getting an address, this will get + // us a nullptr. If you do a method-set! with a null pointer it does nothing, so it's safe to + // method-set! to things that are in unloaded segments and it'll just keep the old method. + diff = -mine; + } + // printf("link object in seg %d diff %d at %d (%d + %d)\n", target_seg, diff, offset_of_patch, + // link_data[2], ofh->code_infos[current_seg].offset); + + // both 32-bit and 64-bit pointer links are supported, though 64-bit ones are no longer in use. + // we still support it just in case we want to run ancient code. + if (size == 4) { + *Ptr(offset_of_patch).c() = diff; + } else if (size == 8) { + *Ptr(offset_of_patch).c() = diff; + } else { + ASSERT(false); + } + + return 1 + 3 * 4; +} + +uint32_t ptr_link_v3(Ptr link, ObjectFileHeader* ofh, int current_seg) { + auto* link_data = link.cast().c(); + u32 patch_loc = link_data[0] + ofh->code_infos[current_seg].offset; + u32 patch_value = link_data[1] + ofh->code_infos[current_seg].offset; + *Ptr(patch_loc).c() = patch_value; + return 8; +} + +/*! + * Link type pointers for a single type in "v3 equivalent" link data + * Returns a pointer to the link table data after the typelinking data. + */ +uint32_t typelink_v3(Ptr link, Ptr data) { + // get the name of the type + uint32_t seek = 0; + char sym_name[256]; + while (link.c()[seek]) { + sym_name[seek] = link.c()[seek]; + seek++; + ASSERT(seek < 256); + } + sym_name[seek] = 0; + seek++; + + // determine the number of methods + uint32_t method_count = link.c()[seek++]; + // jak2 special + method_count *= 4; + if (method_count) { + method_count += 3; + } + + // intern the GOAL type, creating the vtable if it doesn't exist. + auto type_ptr = jak3::intern_type_from_c(-1, 0, sym_name, method_count); + + // prepare to read the locations of the type pointers + Ptr offsets = link.cast() + seek; + uint32_t offset_count = *offsets; + offsets = offsets + 4; + seek += 4; + + // write the type pointers into memory + for (uint32_t i = 0; i < offset_count; i++) { + *(data + offsets.c()[i]).cast() = type_ptr.offset; + seek += 4; + } + + return seek; +} +/*! + * Link symbols (both offsets and pointers) in "v3 equivalent" link data. + * Returns a pointer to the link table data after the linking data for this symbol. + */ +uint32_t symlink_v3(Ptr link, Ptr data) { + // get the symbol name + uint32_t seek = 0; + char sym_name[256]; + while (link.c()[seek]) { + sym_name[seek] = link.c()[seek]; + seek++; + ASSERT(seek < 256); + } + sym_name[seek] = 0; + seek++; + + // intern + auto sym = jak3::intern_from_c(-1, 0, sym_name); + int32_t sym_offset = sym.cast() - s7; + uint32_t sym_addr = sym.cast().offset; + + // prepare to read locations of symbol links + Ptr offsets = link.cast() + seek; + uint32_t offset_count = *offsets; + offsets = offsets + 4; + seek += 4; + + for (uint32_t i = 0; i < offset_count; i++) { + uint32_t offset = offsets.c()[i]; + seek += 4; + auto data_ptr = (data + offset).cast(); + + if (*data_ptr == -1) { + // a "-1" indicates that we should store the address. + *(data + offset).cast() = sym_addr; + } else if (*(data_ptr.cast()) == LINK_SYM_NO_OFFSET_FLAG) { + *(data + offset).cast() = sym_offset - 1; + } else { + // otherwise store the offset to st. + *(data + offset).cast() = sym_offset; + } + } + + return seek; +} +} // namespace + +uint32_t link_control::jak3_work_opengoal() { + // note: I'm assuming that the allocation we used in jak2/jak1 will still work here. Once work_v5 + // is done, we could revisit this. + ObjectFileHeader* ofh = m_link_block_ptr.cast().c(); + if (m_state == 0) { + // state 0 <- copying data. + // the actual game does all copying in one shot. I assume this is ok because v3 files are just + // code and always small. Large data which takes too long to copy should use v2. + + // loop over segments + for (s32 seg_id = ofh->segment_count - 1; seg_id >= 0; seg_id--) { + // link the infos + ofh->link_infos[seg_id].offset += m_link_block_ptr.offset; + ofh->code_infos[seg_id].offset += m_object_data.offset; + + if (seg_id == DEBUG_SEGMENT) { + if (!DebugSegment) { + // clear code info if we aren't going to copy the debug segment. + ofh->code_infos[seg_id].offset = 0; + ofh->code_infos[seg_id].size = 0; + } else { + if (ofh->code_infos[seg_id].size == 0) { + // not actually present + ofh->code_infos[seg_id].offset = 0; + } else { + Ptr src(ofh->code_infos[seg_id].offset); + ofh->code_infos[seg_id].offset = + kmalloc(kdebugheap, ofh->code_infos[seg_id].size, 0, "debug-segment").offset; + if (ofh->code_infos[seg_id].offset == 0) { + MsgErr("dkernel: unable to malloc %d bytes for debug-segment\n", + ofh->code_infos[seg_id].size); + return 1; + } + memmove(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), + ofh->code_infos[seg_id].size); + } + } + } else if (seg_id == MAIN_SEGMENT) { + if (ofh->code_infos[seg_id].size == 0) { + ofh->code_infos[seg_id].offset = 0; + } else { + Ptr src(ofh->code_infos[seg_id].offset); + ofh->code_infos[seg_id].offset = + kmalloc(m_heap, ofh->code_infos[seg_id].size, 0, "main-segment").offset; + if (ofh->code_infos[seg_id].offset == 0) { + MsgErr("dkernel: unable to malloc %d bytes for main-segment\n", + ofh->code_infos[seg_id].size); + return 1; + } + memmove(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), + ofh->code_infos[seg_id].size); + } + } else if (seg_id == TOP_LEVEL_SEGMENT) { + if (ofh->code_infos[seg_id].size == 0) { + ofh->code_infos[seg_id].offset = 0; + } else { + Ptr src(ofh->code_infos[seg_id].offset); + ofh->code_infos[seg_id].offset = + kmalloc(m_heap, ofh->code_infos[seg_id].size, KMALLOC_TOP, "top-level-segment") + .offset; + if (ofh->code_infos[seg_id].offset == 0) { + MsgErr("dkernel: unable to malloc %d bytes for top-level-segment\n", + ofh->code_infos[seg_id].size); + return 1; + } + memmove(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), + ofh->code_infos[seg_id].size); + } + } else { + printf("UNHANDLED SEG ID IN WORK V3 STATE 1\n"); + } + } + + m_state = 1; + m_segment_process = 0; + return 0; + } else if (m_state == 1) { + // state 1: linking. For now all links are done at once. This is probably going to be fine on a + // modern computer. But the game broke this into multiple steps. + if (m_segment_process < ofh->segment_count) { + if (ofh->code_infos[m_segment_process].offset) { + Ptr lp(ofh->link_infos[m_segment_process].offset); + + while (*lp) { + switch (*lp) { + case LINK_TABLE_END: + break; + case LINK_SYMBOL_OFFSET: + lp = lp + 1; + lp = lp + symlink_v3(lp, Ptr(ofh->code_infos[m_segment_process].offset)); + break; + case LINK_TYPE_PTR: + lp = lp + 1; // seek past id + lp = lp + typelink_v3(lp, Ptr(ofh->code_infos[m_segment_process].offset)); + break; + case LINK_DISTANCE_TO_OTHER_SEG_64: + lp = lp + 1; + lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 8); + break; + case LINK_DISTANCE_TO_OTHER_SEG_32: + lp = lp + 1; + lp = lp + cross_seg_dist_link_v3(lp, ofh, m_segment_process, 4); + break; + case LINK_PTR: + lp = lp + 1; + lp = lp + ptr_link_v3(lp, ofh, m_segment_process); + break; + default: + ASSERT_MSG(false, fmt::format("unknown link table thing {}", *lp)); + break; + } + } + } + + m_segment_process++; + } else { + // all done, can set the entry point to the top-level. + m_entry = Ptr(ofh->code_infos[TOP_LEVEL_SEGMENT].offset) + 4; + return 1; + } + + return 0; + } + + else { + printf("WORK v3 INVALID STATE\n"); + return 1; + } +} + +void link_control::jak3_finish(bool jump_from_c_to_goal) { + // CacheFlush(this->m_ptr_2, this->m_code_size); + auto old_debug_segment = DebugSegment; + if (m_keep_debug) { + // note - this probably doesn't work because DebugSegment isn't *debug-segment*. + DebugSegment = s7.offset + jak3_symbols::FIX_SYM_TRUE; + } + if (m_flags & LINK_FLAG_FORCE_FAST_LINK) { + FastLink = 1; + } + + *EnableMethodSet = *EnableMethodSet + m_keep_debug; + + if (m_opengoal) { + // setup mips2c functions + const auto& it = Mips2C::gMips2CLinkCallbacks[GameVersion::Jak3].find(m_object_name); + if (it != Mips2C::gMips2CLinkCallbacks[GameVersion::Jak3].end()) { + for (auto& x : it->second) { + x(); + } + } + + // execute top level! + if (m_entry.offset && (m_flags & LINK_FLAG_EXECUTE)) { + if (jump_from_c_to_goal) { + u64 goal_stack = u64(g_ee_main_mem) + EE_MAIN_MEM_SIZE - 8; + call_goal_on_stack(m_entry.cast(), goal_stack, s7.offset, g_ee_main_mem); + } else { + call_goal(m_entry.cast(), 0, 0, 0, s7.offset, g_ee_main_mem); + } + } + + // inform compiler that we loaded. + if (m_flags & LINK_FLAG_OUTPUT_LOAD) { + output_segment_load(m_object_name, m_link_block_ptr, m_flags); + } + } else { + ASSERT_NOT_REACHED(); + } + + *EnableMethodSet = *EnableMethodSet - this->m_keep_debug; + FastLink = 0; + m_heap->top = m_heap_top; + DebugSegment = old_debug_segment; + + m_busy = false; + if (m_on_global_heap) { + jak3::kmemclose(); + } + return; +} + +namespace jak3 { + +Ptr link_and_exec(Ptr data, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags, + bool jump_from_c_to_goal) { + if (link_busy()) { + printf("-------------> saved link is busy\n"); + // probably won't end well... + } + link_control lc; + lc.jak3_begin(data, name, size, heap, flags); + uint32_t done; + do { + done = lc.jak3_work(); + } while (!done); + lc.jak3_finish(jump_from_c_to_goal); + return lc.m_entry; +} + +u64 link_and_exec_wrapper(u64* args) { + return link_and_exec(Ptr(args[0]), Ptr(args[1]).c(), args[2], Ptr(args[3]), + args[4], false) + .offset; +} + +u32 link_busy() { + return saved_link_control.m_busy; +} +void link_reset() { + saved_link_control.m_busy = 0; +} +uint64_t link_begin(u64* /*args*/) { + ASSERT_NOT_REACHED(); +} +uint64_t link_resume() { + ASSERT_NOT_REACHED(); +} + +// Note: update_goal_fns changed to skip the hashtable lookup since symlink2/symlink3 are now fixed +// symbols. + +/*! + * The ULTIMATE MEMORY COPY + * IT IS VERY FAST + * but it may use the scratchpad. It is implemented in GOAL, and falls back to normal C memcpy + * if GOAL isn't loaded, or if the alignment isn't good enough. + */ +void ultimate_memcpy(void* dst, void* src, uint32_t size) { + // only possible if alignment is good. + if (!(u64(dst) & 0xf) && !(u64(src) & 0xf) && !(u64(size) & 0xf) && size > 0xfff) { + if (!gfunc_774.offset) { + // GOAL function is unknown, lets see if its loaded: + auto sym_val = *((s7 + jak3_symbols::FIX_SYM_ULTIMATE_MEMCPY - 1).cast()); + if (sym_val == 0) { + memmove(dst, src, size); + return; + } + gfunc_774.offset = sym_val; + } + + Ptr(call_goal(gfunc_774, make_u8_ptr(dst).offset, make_u8_ptr(src).offset, size, s7.offset, + g_ee_main_mem)) + .c(); + } else { + memmove(dst, src, size); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/klink.h b/game/kernel/jak3/klink.h new file mode 100644 index 0000000000..ba09fd0b24 --- /dev/null +++ b/game/kernel/jak3/klink.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kmalloc.h" + +namespace jak3 { +Ptr link_and_exec(Ptr data, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags, + bool jump_from_c_to_goal); +u64 link_and_exec_wrapper(u64* args); +u32 link_busy(); +void link_reset(); +uint64_t link_begin(u64* args); +uint64_t link_resume(); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/klisten.cpp b/game/kernel/jak3/klisten.cpp new file mode 100644 index 0000000000..f9dde9ccd0 --- /dev/null +++ b/game/kernel/jak3/klisten.cpp @@ -0,0 +1,118 @@ +#include "klisten.h" + +#include "common/symbols.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/Symbol4.h" +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/jak3/klink.h" +#include "game/kernel/jak3/kscheme.h" + +namespace jak3 { + +using namespace jak3_symbols; + +Ptr> ListenerLinkBlock; +Ptr> ListenerFunction; +Ptr> KernelFunction; // new in jak2 +Ptr> kernel_dispatcher; +Ptr> kernel_packages; +Ptr> sync_dispatcher; + +void klisten_init_globals() { + ListenerLinkBlock.offset = 0; + ListenerFunction.offset = 0; + KernelFunction.offset = 0; + kernel_dispatcher.offset = 0; + kernel_packages.offset = 0; + sync_dispatcher.offset = 0; +} + +/*! + * Initialize the Listener by setting up symbols shared between GOAL and C for the listener. + * Also adds "kernel" to the kernel_packages list. + * There was an "ACK" message sent here, but this is removed because we don't need it. + */ +void InitListener() { + ListenerLinkBlock = intern_from_c(-1, 0, "*listener-link-block*"); + ListenerFunction = intern_from_c(-1, 0, "*listener-function*"); + KernelFunction = intern_from_c(-1, 0, "*kernel-function*"); + kernel_dispatcher = intern_from_c(-1, 0, "kernel-dispatcher"); + sync_dispatcher = intern_from_c(-1, 0, "sync-dispatcher"); + kernel_packages = intern_from_c(-1, 0, "*kernel-packages*"); + print_column = intern_from_c(-1, 0, "*print-column*").cast(); // this is wrong + ListenerLinkBlock->value() = s7.offset; + ListenerFunction->value() = s7.offset; + KernelFunction->value() = s7.offset; + + kernel_packages->value() = + new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast()), + make_string_from_c("kernel"), kernel_packages->value()); + // if(MasterDebug) { + // SendFromBufferD(MSG_ACK, 0, AckBufArea + sizeof(ListenerMessageHeader), 0); + // } +} + +/*! + * Handle an incoming listener message + */ +void ProcessListenerMessage(Ptr msg) { + // flag that the listener is connected! + ListenerStatus = 1; + switch (protoBlock.msg_kind) { + case LTT_MSG_POKE: + // just flush any pending stuff. + ClearPending(); + break; + case LTT_MSG_INSPECT: + inspect_object(atoi(msg.c())); + ClearPending(); + break; + case LTT_MSG_PRINT: + print_object(atoi(msg.c())); + ClearPending(); + break; + case LTT_MSG_PRINT_SYMBOLS: + printf("[ERROR] unsupported message kind LTT_MSG_PRINT_SYMBOLS (NYI)\n"); + break; + case LTT_MSG_RESET: + MasterExit = RuntimeExitStatus::RESTART_RUNTIME; + break; + case LTT_MSG_SHUTDOWN: + MasterExit = RuntimeExitStatus::EXIT; + break; + case LTT_MSG_CODE: { + auto buffer = kmalloc(kdebugheap, MessCount, 0, "listener-link-block"); + memcpy(buffer.c(), msg.c(), MessCount); + ListenerLinkBlock->value() = buffer.offset + 4; + // note - this will stash the linked code in the top level and free it. + // it will then be used-after-free, but this is OK because nobody else will allocate. + // the kernel dispatcher should immediately execute the listener function to avoid this + // getting squashed. + + // this setup allows listener function execution to clean up after itself. + + // we have added the LINK_FLAG_OUTPUT_LOAD + // jump from c to goal because this is called from the C++ stack. + ListenerFunction->value() = link_and_exec(buffer, "*listener*", 0, kdebugheap, + LINK_FLAG_FORCE_DEBUG | LINK_FLAG_OUTPUT_LOAD, true) + .offset; + return; // don't ack yet, this will happen after the function runs. + } break; + default: + MsgErr("dkernel: unknown message error: <%d> of %d bytes\n", protoBlock.msg_kind, MessCount); + break; + } + SendAck(); +} + +int sql_query_sync(Ptr /*string_in*/) { + ASSERT_NOT_REACHED(); +} + +} // namespace jak3 diff --git a/game/kernel/jak3/klisten.h b/game/kernel/jak3/klisten.h new file mode 100644 index 0000000000..f789975d77 --- /dev/null +++ b/game/kernel/jak3/klisten.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/Symbol4.h" +#include "game/kernel/common/kscheme.h" + +namespace jak3 { +extern Ptr> ListenerLinkBlock; +extern Ptr> ListenerFunction; +extern Ptr> KernelFunction; // new in jak2 +extern Ptr> kernel_dispatcher; +extern Ptr> sync_dispatcher; // new in jak2 +extern Ptr> kernel_packages; +void InitListener(); +void klisten_init_globals(); +void ProcessListenerMessage(Ptr msg); +int sql_query_sync(Ptr string_in); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kmachine.cpp b/game/kernel/jak3/kmachine.cpp new file mode 100644 index 0000000000..80b4b0983e --- /dev/null +++ b/game/kernel/jak3/kmachine.cpp @@ -0,0 +1,513 @@ +#include "kmachine.h" + +#include + +#include "common/symbols.h" + +#include "game/graphics/gfx.h" +#include "game/graphics/sceGraphicsInterface.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/kernel_types.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/kmachine.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/ksocket.h" +#include "game/kernel/common/ksound.h" +#include "game/kernel/common/memory_layout.h" +#include "game/kernel/jak3/kboot.h" +#include "game/kernel/jak3/kdgo.h" +#include "game/kernel/jak3/klisten.h" +#include "game/kernel/jak3/kmalloc.h" +#include "game/kernel/jak3/kscheme.h" +#include "game/kernel/jak3/ksound.h" +#include "game/sce/deci2.h" +#include "game/sce/libdma.h" +#include "game/sce/libgraph.h" +#include "game/sce/sif_ee.h" +#include "game/sce/stubs.h" + +namespace jak3 { + +using namespace ee; + +/*! + * Initialize global variables based on command line parameters. Not called in retail versions, + * but it is present in the ELF. + * DONE + * Modified to use std::string, and removed call to fflush. + */ +void InitParms(int argc, const char* const* argv) { + // Modified default settings to boot up the game like normal if no arguments are present. + if (argc == 1) { + DiskBoot = 1; + isodrv = fakeiso; + modsrc = 0; + reboot_iop = 0; + DebugSegment = 0; + MasterDebug = 0; + DebugSymbols = true; + } + + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + // DVD Settings + // ---------------------------- + + // the "cd" mode uses the DVD drive for everything. This is how the game runs in retail + if (arg == "-cd") { + Msg(6, "dkernel: cd mode\n"); + isodrv = iso_cd; // use the actual DVD drive for data files + modsrc = 1; // use the DVD drive data for IOP modules + reboot_iop = 1; // Reboot the IOP (load new IOP runtime) + } + + // the "cddata" uses the DVD drive for everything but IOP modules. + if (arg == "-cddata") { + Msg(6, "dkernel: cddata mode\n"); + isodrv = iso_cd; // tell IOP to use actual DVD drive for data files + modsrc = 0; // don't use DVD drive for IOP modules + reboot_iop = 0; // no need to reboot the IOP + } + + if (arg == "-demo") { + Msg(6, "dkernel: demo mode\n"); + kstrcpy(DebugBootMessage, "demo"); + } + + // new for jak 2 + if (arg == "-kiosk") { + Msg(6, "dkernel: kiosk mode\n"); + kstrcpy(DebugBootMessage, "kiosk"); + } + + // new for jak 2 + if (arg == "-preview") { + Msg(6, "dkernel: preview mode\n"); + kstrcpy(DebugBootMessage, "preview"); + } + + // the "deviso" mode is one of two modes for testing without the need for DVDs + if (arg == "-deviso") { + Msg(6, "dkernel: deviso mode\n"); + isodrv = deviso; // IOP deviso mode + modsrc = 2; // now 2 for Jak 2 + reboot_iop = 0; + } + // the "fakeiso" mode is the other of two modes for testing without the need for DVDs + if (arg == "-fakeiso") { + Msg(6, "dkernel: fakeiso mode\n"); + isodrv = fakeiso; // IOP fakeeiso mode + modsrc = 0; // no IOP module loading (there's no DVD to load from!) + reboot_iop = 0; + } + + // the "boot" mode is used to set GOAL up for running the game in retail mode + if (arg == "-boot") { + Msg(6, "dkernel: boot mode\n"); + MasterDebug = 0; + DiskBoot = 1; + DebugSegment = 0; + } + + // new for jak 2 + if (arg == "-debug-boot") { + Msg(6, "dkernel: debug-boot mode\n"); + MasterDebug = 0; + DebugSegment = 1; + DiskBoot = 1; + } + + // traditional debug mode + if (arg == "-debug") { + Msg(6, "dkernel: debug mode\n"); + MasterDebug = 1; + DebugSegment = 1; + } + + // the "debug-mem" mode is used to set up GOAL in debug mode, but not to load debug-segments + if (arg == "-debug-mem") { + Msg(6, "dkernel: debug-mem mode\n"); + MasterDebug = 1; + DebugSegment = 0; + } + + // TODO overlord 1 vs. 2 switch + + if (arg == "-debug-symbols") { + Msg(6, "dkernel: debug-symbols on\n"); + DebugSymbols = true; + } + + if (arg == "-no-debug-symbols") { + Msg(6, "dkernel: debug-symbols off\n"); + DebugSymbols = true; + } + + // the "-level [level-name]" mode is used to inform the game to boot a specific level + // the default level is "#f". + if (arg == "-level") { + i++; + std::string levelName = argv[i]; + Msg(6, "dkernel: level %s\n", levelName.c_str()); + kstrcpy(DebugBootLevel, levelName.c_str()); + ASSERT_NOT_REACHED(); // symbol ID junk + } + + // new for jak 2 + if (arg == "-user") { + i++; + std::string userName = argv[i]; + Msg(6, "dkernel: user %s\n", userName.c_str()); + kstrcpy(DebugBootUser, userName.c_str()); + } + + // new for jak 2 + if (arg == "-art") { + i++; + std::string artGroupName = argv[i]; + Msg(6, "dkernel: art-group %s\n", artGroupName.c_str()); + kstrcpy(DebugBootArtGroup, artGroupName.c_str()); + kstrcpy(DebugBootMessage, "art-group"); + } + + // an added mode to allow booting without a KERNEL.CGO for testing + if (arg == "-nokernel") { + Msg(6, "dkernel: no kernel mode\n"); + MasterUseKernel = false; + } + + // an added mode to allow booting without sound for testing + if (arg == "-nosound") { + Msg(6, "dkernel: no sound mode\n"); + masterConfig.disable_sound = true; + } + } +} + +/*! + * This is mostly copy-pasted from jak2 and very simplified until we have overlord 2. + */ +void InitIOP() { + Msg(6, "dkernel: boot:%d debug:%d mem:%d syms:%d dev:%d mod:%d\n", DiskBoot, MasterDebug, + DebugSegment, DebugSymbols, isodrv, modsrc); + sceSifInitRpc(0); + + // init cd if we need it + if (((isodrv == iso_cd) || (modsrc == 1)) || (reboot_iop == 1)) { + InitCD(); + } + + if ((isodrv == iso_cd) || (modsrc == 1)) { + InitCD(); + } + + char overlord_boot_command[256]; + char* cmd = overlord_boot_command; + kstrcpy(cmd, init_types[(int)isodrv]); + cmd = cmd + strlen(cmd) + 1; + if (!strncmp(DebugBootMessage, "demo", 4)) { + kstrcpy(cmd, "SCREEN1.DEM"); + } else { + kstrcpy(cmd, "SCREEN1.USA"); + } + cmd = cmd + strlen(cmd) + 1; + if (masterConfig.disable_sound) { + kstrcpy(cmd, "-nosound"); + cmd = cmd + strlen(cmd) + 1; + } + + int total_len = cmd - overlord_boot_command; + + if (modsrc == 0) { + printf("Initializing CD library in FAKEISO mode\n"); + if (sceSifLoadModule("host0:bin/overlord.irx", total_len, overlord_boot_command) < 0) { + MsgErr("loading overlord.irx <3> failed\n"); + exit(0); + } + } else { + ASSERT_NOT_REACHED(); + } + int rv = sceMcInit(); + if (rv < 0) { + MsgErr("MC driver init failed %d\n", rv); + exit(0); + } + printf("InitIOP OK\n"); +} + +int InitMachine() { + // uVar2 = FUN_00116ec8(0x10); + // heap_start = malloc(0x10); + + u32 global_heap_size = GLOBAL_HEAP_END - HEAP_START; + float size_mb = ((float)global_heap_size) / (float)(1 << 20); + lg::info("gkernel: global heap 0x{:08x} to 0x{:08x} (size {:.3f} MB)", HEAP_START, + GLOBAL_HEAP_END, size_mb); + kinitheap(kglobalheap, Ptr(HEAP_START), global_heap_size); + + kmemopen_from_c(kglobalheap, "global"); + kmemopen_from_c(kglobalheap, "scheme-globals"); + + if (!MasterDebug && !DebugSegment) { + // if no debug, we make the kheapinfo structure NULL so GOAL knows not to use it. + // note: either MasterDebug or DebugSegment is enough to give use the debug heap. + kdebugheap.offset = 0; + } else { + kinitheap(kdebugheap, Ptr(DEBUG_HEAP_START), jak3::DEBUG_HEAP_SIZE); + } + init_output(); + InitIOP(); + // sceGsResetPath(); + InitVideo(); + // FlushCache(0); + // FlushCache(2); + // sceGsSyncV(0); + // if (scePadInit(0) != 1) { + // MsgErr("dkernel: !init pad\n"); + // } + if (MasterDebug != 0) { + InitGoalProto(); + } else { + ee::sceDeci2Disable(); // added + } + printf("InitSound\n"); + InitSound(); + printf("InitRPC\n"); + InitRPC(); + reset_output(); + clear_print(); + auto status = InitHeapAndSymbol(); + if (status >= 0) { + printf("InitListenerConnect\n"); + InitListenerConnect(); + printf("InitCheckListener\n"); + InitCheckListener(); + Msg(6, "kernel: machine started\n"); + return 0; + } + return status; +} + +int ShutdownMachine() { + Msg(6, "kernel: machine shutdown (reason %d)\n", MasterExit); + + StopIOP(); + ShutdownSound(); + CloseListener(); + + ShutdownGoalProto(); + return 0; +} + +u32 KeybdGetData(u32 /*_mouse*/) { + ASSERT_NOT_REACHED(); +} + +u32 MouseGetData(u32 /*_mouse*/) { + ASSERT_NOT_REACHED(); +} + +/*! + * Open a file-stream. Name is a GOAL string. Mode is a GOAL symbol. Use 'read for readonly + * and anything else for write only. + */ +u64 kopen(u64 fs, u64 name, u64 mode) { + auto file_stream = Ptr(fs).c(); + file_stream->mode = mode; + file_stream->name = name; + file_stream->flags = 0; + printf("****** CALL TO kopen() ******\n"); + char buffer[128]; + // sprintf(buffer, "host:%s", Ptr(name)->data()); + sprintf(buffer, "%s", Ptr(name)->data()); + if (!strcmp(sym_to_cstring(Ptr>(mode)), "read")) { + // 0x1 + file_stream->file = ee::sceOpen(buffer, SCE_RDONLY); + } else if (!strcmp(sym_to_cstring(Ptr>(mode)), "append")) { + // new in jak 2! + // 0x202 + file_stream->file = ee::sceOpen(buffer, SCE_CREAT | SCE_WRONLY); + } else { + // 0x602 + file_stream->file = ee::sceOpen(buffer, SCE_TRUNC | SCE_CREAT | SCE_WRONLY); + } + + return fs; +} + +void PutDisplayEnv(u32 alp) { + // we can mostly ignore this, except for one value that sets the 'blackout' amount. + auto* renderer = Gfx::GetCurrentRenderer(); + if (renderer) { + renderer->set_pmode_alp(alp / 255.f); + } +} + +void aybabtu() {} + +void pc_set_levels(u32 lev_list) { + if (!Gfx::GetCurrentRenderer()) { + return; + } + std::vector levels; + for (int i = 0; i < LEVEL_MAX; i++) { + u32 lev = *Ptr(lev_list + i * 4); + std::string ls = Ptr(lev).c()->data(); + if (ls != "none" && ls != "#f" && ls != "") { + levels.push_back(ls); + } + } + + Gfx::GetCurrentRenderer()->set_levels(levels); +} + +void pc_set_active_levels(u32 lev_list) { + if (!Gfx::GetCurrentRenderer()) { + return; + } + std::vector levels; + for (int i = 0; i < LEVEL_MAX; i++) { + u32 lev = *Ptr(lev_list + i * 4); + std::string ls = Ptr(lev).c()->data(); + if (ls != "none" && ls != "#f" && ls != "") { + levels.push_back(ls); + } + } + + Gfx::GetCurrentRenderer()->set_active_levels(levels); +} + +//// PC Stuff +void InitMachine_PCPort() { + // PC Port added functions + init_common_pc_port_functions( + make_function_symbol_from_c, + [](const char* name) { + const auto result = intern_from_c(-1, 0, name); + InternFromCInfo info{}; + info.offset = result.offset; + return info; + }, + make_string_from_c); + + make_function_symbol_from_c("__pc-set-levels", (void*)pc_set_levels); + make_function_symbol_from_c("__pc-set-active-levels", (void*)pc_set_active_levels); + // make_function_symbol_from_c("__pc-get-tex-remap", (void*)lookup_jak2_texture_dest_offset); + // make_function_symbol_from_c("pc-init-autosplitter-struct", (void*)init_autosplit_struct); + // make_function_symbol_from_c("pc-encode-utf8-string", (void*)encode_utf8_string); + + // discord rich presence + // make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc); + + // debugging tools + // make_function_symbol_from_c("alloc-vagdir-names", (void*)alloc_vagdir_names); + + // external RPCs + /* + make_function_symbol_from_c("pc-fetch-external-speedrun-times", + (void*)pc_fetch_external_speedrun_times); + make_function_symbol_from_c("pc-fetch-external-race-times", (void*)pc_fetch_external_race_times); + make_function_symbol_from_c("pc-fetch-external-highscores", (void*)pc_fetch_external_highscores); + make_function_symbol_from_c("pc-get-external-speedrun-time", + (void*)pc_get_external_speedrun_time); + make_function_symbol_from_c("pc-get-external-race-time", (void*)pc_get_external_race_time); + make_function_symbol_from_c("pc-get-external-highscore", (void*)pc_get_external_highscore); + make_function_symbol_from_c("pc-get-num-external-speedrun-times", + (void*)pc_get_num_external_speedrun_times); + make_function_symbol_from_c("pc-get-num-external-race-times", + (void*)pc_get_num_external_race_times); + make_function_symbol_from_c("pc-get-num-external-highscores", + (void*)pc_get_num_external_highscores); + */ + + // setup string constants + auto user_dir_path = file_util::get_user_config_dir(); + intern_from_c(-1, 0, "*pc-user-dir-base-path*")->value() = + make_string_from_c(user_dir_path.string().c_str()); + auto settings_path = file_util::get_user_settings_dir(g_game_version); + intern_from_c(-1, 0, "*pc-settings-folder*")->value() = + make_string_from_c(settings_path.string().c_str()); + intern_from_c(-1, 0, "*pc-settings-built-sha*")->value() = + make_string_from_c(build_revision().c_str()); +} +// End PC Stuff + +void InitMachineScheme() { + make_function_symbol_from_c("put-display-env", (void*)PutDisplayEnv); + make_function_symbol_from_c("syncv", (void*)sceGsSyncV); + make_function_symbol_from_c("sync-path", (void*)sceGsSyncPath); + make_function_symbol_from_c("reset-path", (void*)sceGsResetPath); + make_function_symbol_from_c("reset-graph", (void*)sceGsResetGraph); + make_function_symbol_from_c("dma-sync", (void*)sceDmaSync); + make_function_symbol_from_c("gs-put-imr", (void*)sceGsPutIMR); + make_function_symbol_from_c("gs-get-imr", (void*)sceGsGetIMR); + make_function_symbol_from_c("gs-store-image", (void*)sceGsExecStoreImage); + make_function_symbol_from_c("flush-cache", (void*)FlushCache); + make_function_symbol_from_c("cpad-open", (void*)CPadOpen); + make_function_symbol_from_c("cpad-get-data", (void*)CPadGetData); + make_function_symbol_from_c("mouse-get-data", (void*)MouseGetData); + make_function_symbol_from_c("keybd-get-data", (void*)KeybdGetData); + make_function_symbol_from_c("install-handler", (void*)InstallHandler); + make_function_symbol_from_c("install-debug-handler", (void*)InstallDebugHandler); + make_function_symbol_from_c("file-stream-open", (void*)kopen); + make_function_symbol_from_c("file-stream-close", (void*)kclose); + make_function_symbol_from_c("file-stream-length", (void*)klength); + make_function_symbol_from_c("file-stream-seek", (void*)kseek); + make_function_symbol_from_c("file-stream-read", (void*)kread); + make_function_symbol_from_c("file-stream-write", (void*)kwrite); + make_function_symbol_from_c("scf-get-language", (void*)DecodeLanguage); + make_function_symbol_from_c("scf-get-time", (void*)DecodeTime); + make_function_symbol_from_c("scf-get-aspect", (void*)DecodeAspect); + make_function_symbol_from_c("scf-get-volume", (void*)DecodeVolume); + make_function_symbol_from_c("scf-get-territory", (void*)DecodeTerritory); + make_function_symbol_from_c("scf-get-timeout", (void*)DecodeTimeout); + make_function_symbol_from_c("scf-get-inactive-timeout", (void*)DecodeInactiveTimeout); + make_function_symbol_from_c("dma-to-iop", (void*)dma_to_iop); + make_function_symbol_from_c("kernel-shutdown", (void*)KernelShutdown); + make_function_symbol_from_c("aybabtu", (void*)aybabtu); // was nothing + + InitMachine_PCPort(); + + InitSoundScheme(); + intern_from_c(-1, 0, "*stack-top*")->value() = 0x7f00000; + intern_from_c(-1, 0, "*stack-base*")->value() = 0x7ffffff; + intern_from_c(-1, 0, "*stack-size*")->value() = 0x100000; + intern_from_c(-1, 0, "*kernel-boot-message*")->value() = + intern_from_c(-1, 0, DebugBootMessage).offset; + intern_from_c(-1, 0, "*user*")->value() = make_string_from_c(DebugBootUser); + if (DiskBoot) { + intern_from_c(-1, 0, "*kernel-boot-mode*")->value() = intern_from_c(-1, 0, "boot").offset; + } + if (strcmp(DebugBootLevel, "#f") == 0) { + intern_from_c(-1, 0, "*kernel-boot-level*")->value() = s7.offset; + } else { + ASSERT_NOT_REACHED(); + } + intern_from_c(-1, 0, "*kernel-boot-art-group*")->value() = make_string_from_c(DebugBootArtGroup); + + if (DiskBoot != 0) { + *EnableMethodSet = *EnableMethodSet + 1; + load_and_link_dgo_from_c("game", kglobalheap, + LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, + 0x400000, true); + *EnableMethodSet = *EnableMethodSet + -1; + using namespace jak3_symbols; + + kernel_packages->value() = + new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast()), + make_string_from_c("engine"), kernel_packages->value()); + kernel_packages->value() = + new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast()), + make_string_from_c("art"), kernel_packages->value()); + kernel_packages->value() = + new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE - 1).cast()), + make_string_from_c("common"), kernel_packages->value()); + printf("calling play-boot!\n"); + call_goal_function_by_name("play-boot"); // new function for jak2! + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kmachine.h b/game/kernel/jak3/kmachine.h new file mode 100644 index 0000000000..8407f9ed54 --- /dev/null +++ b/game/kernel/jak3/kmachine.h @@ -0,0 +1,8 @@ +#pragma once + +namespace jak3 { +void InitParms(int argc, const char* const* argv); +void InitMachineScheme(); +int InitMachine(); +int ShutdownMachine(); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kmalloc.cpp b/game/kernel/jak3/kmalloc.cpp new file mode 100644 index 0000000000..e117c510db --- /dev/null +++ b/game/kernel/jak3/kmalloc.cpp @@ -0,0 +1,16 @@ +#include "kmalloc.h" + +namespace jak3 { +// these functions are all stubs in all known copies of the ELF. +void kmemopen_from_c(Ptr heap, const char* name) { + (void)heap; + (void)name; +} + +void kmemopen(u32 heap, u32 name) { + (void)heap; + (void)name; +} + +void kmemclose() {} +} // namespace jak3 diff --git a/game/kernel/jak3/kmalloc.h b/game/kernel/jak3/kmalloc.h new file mode 100644 index 0000000000..46e1147f69 --- /dev/null +++ b/game/kernel/jak3/kmalloc.h @@ -0,0 +1,9 @@ +#pragma once + +#include "game/kernel/common/kmalloc.h" + +namespace jak3 { +void kmemopen_from_c(Ptr heap, const char* name); +void kmemopen(u32 heap, u32 name); +void kmemclose(); +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/kmemcard.cpp b/game/kernel/jak3/kmemcard.cpp new file mode 100644 index 0000000000..8deabc1a12 --- /dev/null +++ b/game/kernel/jak3/kmemcard.cpp @@ -0,0 +1,3 @@ + + +#include "kmemcard.h" diff --git a/game/kernel/jak3/kmemcard.h b/game/kernel/jak3/kmemcard.h new file mode 100644 index 0000000000..ae16523cd1 --- /dev/null +++ b/game/kernel/jak3/kmemcard.h @@ -0,0 +1,3 @@ +#pragma once + +namespace jak3 {} \ No newline at end of file diff --git a/game/kernel/jak3/kprint.cpp b/game/kernel/jak3/kprint.cpp new file mode 100644 index 0000000000..c115a39ea6 --- /dev/null +++ b/game/kernel/jak3/kprint.cpp @@ -0,0 +1,564 @@ +#include "kprint.h" + +#include +#include + +#include "common/goal_constants.h" +#include "common/listener_common.h" +#include "common/symbols.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kboot.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/jak3/kscheme.h" +#include "game/sce/sif_ee.h" + +/*! + * The GOAL "format" function. The actual function is named "format". However, GOAL's calling + * convention differs from x86-64, so GOAL cannot directly call format. There is an assembly + * function in format_wrapper.nasm named format. It takes the GOAL argument registers, stores them + * in an array on the stack, and calls this function with a pointer to that array. + * + * This function is a disaster. For now, it's copied from jak1 and then jak 2, with the obvious + * fixes made, but it's probably worth another pass. + */ +s32 format_impl_jak3(uint64_t* args) { + using namespace jak3_symbols; + using namespace jak3; + + // first two args are dest, format string + uint64_t* arg_regs = args + 2; + + // data for arguments in a format command + format_struct argument_data[8]; + + u32 arg_reg_idx = 0; + + // the gstring + char* format_gstring = Ptr(args[1]).c(); + + u32 original_dest = args[0]; + + // set up print pending + char* print_temp = PrintPending.cast().c(); + if (!PrintPending.offset) { + print_temp = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); + } + PrintPending = make_ptr(strend(print_temp)).cast(); + + // what we write to + char* output_ptr = PrintPending.cast().c(); + + // convert gstring to cstring + char* format_cstring = format_gstring + 4; + + // mysteries + char* PrintPendingLocal2 = PrintPending.cast().c(); + char* PrintPendingLocal3 = output_ptr; + + // start by computing indentation + u32 indentation = 0; + + // read goal binteger + if (print_column.offset) { + // added the if check so we can format even if the kernel didn't load right. + indentation = (*(print_column - 1)) >> 3; + } + + // which arg we're on + u32 arg_idx = 0; + + // if last char was newline and we have tabs, do tabs + if (indentation && output_ptr[-1] == '\n') { + for (u32 i = 0; i < indentation; i++) { + *output_ptr = ' '; + output_ptr++; + } + } + + // input pointer + char* format_ptr = format_cstring; + + // loop over the format string + while (*format_ptr) { + // got a command? + if (*format_ptr == '~') { + char* arg_start = format_ptr; + // get some arguments + arg_idx = 0; + u8 justify = 0; + for (auto& x : argument_data) { + x.reset(); + } + + // read arguments + while ((u8)(format_ptr[1] - '0') < 10 || // number 0 to 9 + format_ptr[1] == ',' || // comma + format_ptr[1] == '\'' || // quote + format_ptr[1] == '`' || // backtick + (argument_data[arg_idx].data[0] == -1 && + (format_ptr[1] == '-' || format_ptr[1] == '+') // flags1 == -1 && +/- + )) { + // here format_ptr[1] points to next unread character in argument + // format_ptr[0] is originally the ~ + // should exit loop with format_ptr[1] == the command character + char arg_char = format_ptr[1]; // gVar1 + + if (arg_char == ',') { + // advance to next argument + arg_idx++; // increment which argument we're on + format_ptr++; // increment past comma, and try again + continue; + } + + // character argument + if (arg_char == '\'') { // 0x27 + argument_data[arg_idx].data[0] = format_ptr[2]; + format_ptr += 2; + continue; + } + + // string argument + if (arg_char == '`') { // 0x60 + u32 i = 0; + format_ptr += 2; + // read string + while (*format_ptr != '`') { + argument_data[arg_idx].data[i] = *format_ptr; + i++; + format_ptr++; + } + // null terminate + argument_data[arg_idx].data[i] = 0; + continue; + } + + if (arg_char == '-') { // 0x2d + // negative flag + argument_data[arg_idx].data[1] = 1; + format_ptr++; + continue; + } + + if (arg_char == '+') { // 0x2b + // positive flag does nothing + format_ptr++; + continue; + } + + // otherwise: + + // null terminate if we got no args + if (argument_data[arg_idx].data[0] == -1) { + argument_data[arg_idx].data[0] = 0; + } + + // otherwise it's a number + argument_data[arg_idx].data[0] = argument_data[arg_idx].data[0] * 10 + arg_char - '0'; + format_ptr++; + } // end argument while + + // switch on command + switch (format_ptr[1]) { + // offset of 0x25 + + case '%': // newline + *output_ptr = '\n'; + output_ptr++; + // indent the next line if there is one + if (indentation && format_ptr[2]) { + for (u32 i = 0; i < indentation; i++) { + *output_ptr = ' '; + output_ptr++; + } + } + break; + + case '~': // tilde escape + *output_ptr = '~'; + output_ptr++; + break; + + // pass through arguments + case 'H': // 23 -> 48, H + case 'J': // 25 -> 4A, J + case 'K': // 26 -> 4B, K + case 'L': // 27 -> 4C, L + case 'N': // 29 -> 4E, N + case 'V': // 31 -> 56, V + case 'W': // 32 -> 57, W + case 'Y': // 34 -> 59, Y + case 'Z': // 35 -> 5A, Z + case 'h': + case 'j': + case 'k': + case 'l': + case 'n': + case 'v': + case 'w': + case 'y': + case 'z': + while (arg_start < format_ptr + 1) { + *output_ptr = *arg_start; + arg_start++; + output_ptr++; + } + *output_ptr = format_ptr[1]; + output_ptr++; + break; + + case 'G': // like %s, prints a C string + case 'g': { + *output_ptr = 0; + u32 in = arg_regs[arg_reg_idx++]; + kstrcat(output_ptr, Ptr(in).c()); + output_ptr = strend(output_ptr); + } break; + + case 'O': + case 'o': { + *output_ptr = '~'; + output_ptr++; + kitoa(output_ptr, arg_regs[arg_reg_idx++], 10, 0, ' ', 0); + output_ptr = strend(output_ptr); + *output_ptr = 'u'; + output_ptr++; + } break; + + case 'A': // print a boxed object + case 'a': // pad,padchar (like ) ~8,'0A + { + s8 arg0 = argument_data[0].data[0]; + s32 desired_length = arg0; + *output_ptr = 0; + u32 in = arg_regs[arg_reg_idx++]; + jak3::print_object(in); + if (desired_length != -1) { + s32 print_len = strlen(output_ptr); + if (desired_length < print_len) { + // too long! + if (desired_length > 1) { // mark with tilde that we will truncate + output_ptr[desired_length - 1] = '~'; + } + output_ptr[desired_length] = 0; // and truncate + } else if (print_len < desired_length) { + // too short + if (justify == 0) { + char pad = ' '; + if (argument_data[1].data[0] != -1) { + pad = argument_data[1].data[0]; + } + kstrinsert(output_ptr, pad, desired_length - print_len); + } else { + ASSERT(false); + // output_ptr = strend(output_ptr); + // while(0 < (desired_length - print_len)) { + // char pad = ' '; + // if(argument_data[0].data[1] != -1) { + // pad = argument_data[0].data[1]; + // } + // output_ptr[0] = pad; + // output_ptr++; + // + // } + // *output_ptr = 0; + } + } + } + output_ptr = strend(output_ptr); + + } break; + + case 'S': // like A, but strings are printed without quotes + case 's': { + s8 arg0 = argument_data[0].data[0]; + s32 desired_length = arg0; + *output_ptr = 0; + u32 in = arg_regs[arg_reg_idx++]; + + // if it's a string + if (((in & 0x7) == 0x4) && *Ptr(in - 4) == *(s7 + FIX_SYM_STRING_TYPE - 1)) { + cprintf("%s", Ptr(in).c() + 4); + } else { + jak3::print_object(in); + } + + if (desired_length != -1) { + s32 print_len = strlen(output_ptr); + if (desired_length < print_len) { + // too long! + if (desired_length > 1) { // mark with tilde that we will truncate + output_ptr[desired_length - 1] = '~'; + } + output_ptr[desired_length] = 0; // and truncate + } else if (print_len < desired_length) { + // too short + if (justify == 0) { + char pad = ' '; + if (argument_data[1].data[0] != -1) { + pad = argument_data[1].data[0]; + } + kstrinsert(output_ptr, pad, desired_length - print_len); + + } else { + ASSERT(false); + // output_ptr = strend(output_ptr); + // u32 l140 = 0; + // while(l140 < (desired_length - print_len)) { + // char* l108 = output_ptr; + // + // char pad = ' '; + // if(argument_data[0].data[1] != -1) { + // pad = argument_data[0].data[1]; + // } + // output_ptr[0] = pad; + // output_ptr++; + // } + // *output_ptr = 0; + } + } + } + output_ptr = strend(output_ptr); + } break; + + case 'C': // character + case 'c': + *output_ptr = arg_regs[arg_reg_idx++]; + output_ptr++; + break; + + case 'P': // like ~A, but can specify type explicitly + case 'p': { + *output_ptr = 0; + s8 arg0 = argument_data[0].data[0]; + u64 in = arg_regs[arg_reg_idx++]; + if (arg0 == -1) { + jak3::print_object(in); + } else { + auto sym = jak3::find_symbol_from_c(-1, argument_data[0].data); + if (sym.offset) { + Ptr type(sym->value()); + if (type.offset) { + call_method_of_type(in, type, GOAL_PRINT_METHOD); + } + } else { + ASSERT(false); // bad type. + } + } + output_ptr = strend(output_ptr); + } break; + + case 'I': // like ~P, but calls inpsect + case 'i': { + *output_ptr = 0; + s8 arg0 = argument_data[0].data[0]; + u64 in = arg_regs[arg_reg_idx++]; + if (arg0 == -1) { + inspect_object(in); + } else { + auto sym = find_symbol_from_c(-1, argument_data[0].data); + if (sym.offset) { + Ptr type(sym->value()); + if (type.offset) { + call_method_of_type(in, type, GOAL_INSPECT_METHOD); + } + } else { + ASSERT(false); // bad type + } + } + output_ptr = strend(output_ptr); + } break; + + case 'Q': // not yet implemented. hopefully andy gavin finishes this one soon. + case 'q': + ASSERT(false); + break; + + case 'X': // hex, 64 bit, pad padchar + case 'x': { + char pad = '0'; + if (argument_data[1].data[0] != -1) { + pad = argument_data[1].data[0]; + } + u64 in = arg_regs[arg_reg_idx++]; + kitoa(output_ptr, in, 16, argument_data[0].data[0], pad, 0); + output_ptr = strend(output_ptr); + } break; + + case 'D': // integer 64, pad padchar + case 'd': { + char pad = ' '; + if (argument_data[1].data[0] != -1) { + pad = argument_data[1].data[0]; + } + u64 in = arg_regs[arg_reg_idx++]; + kitoa(output_ptr, in, 10, argument_data[0].data[0], pad, 0); + output_ptr = strend(output_ptr); + } break; + + case 'B': // integer 64, pad padchar + case 'b': { + char pad = '0'; + if (argument_data[1].data[0] != -1) { + pad = argument_data[1].data[0]; + } + u64 in = arg_regs[arg_reg_idx++]; + kitoa(output_ptr, in, 2, argument_data[0].data[0], pad, 0); + output_ptr = strend(output_ptr); + } break; + + case 'F': // float 12 pad, 4 precision + { + float in = *(float*)&arg_regs[arg_reg_idx++]; + ftoa(output_ptr, in, 0xc, ' ', 4, 0); + output_ptr = strend(output_ptr); + } break; + + case 'f': // float with args + { + float in = *(float*)&arg_regs[arg_reg_idx++]; + s8 pad_length = argument_data[0].data[0]; + s8 pad_char = argument_data[1].data[0]; + if (pad_char == -1) + pad_char = ' '; + s8 precision = argument_data[2].data[0]; + if (precision == -1) + precision = 4; + ftoa(output_ptr, in, pad_length, pad_char, precision, 0); + output_ptr = strend(output_ptr); + } break; + + case 'R': // rotation degrees + case 'r': { + float in = *(float*)&arg_regs[arg_reg_idx++]; + s8 pad_length = argument_data[0].data[0]; + s8 pad_char = argument_data[1].data[0]; + if (pad_char == -1) + pad_char = ' '; + s8 precision = argument_data[2].data[0]; + if (precision == -1) + precision = 4; + ftoa(output_ptr, in * 360.f / 65536.f, pad_length, pad_char, precision, 0); + output_ptr = strend(output_ptr); + } break; + + case 'M': // distance meters + case 'm': { + float in = *(float*)&arg_regs[arg_reg_idx++]; + s8 pad_length = argument_data[0].data[0]; + s8 pad_char = argument_data[1].data[0]; + if (pad_char == -1) + pad_char = ' '; + s8 precision = argument_data[2].data[0]; + if (precision == -1) + precision = 4; + ftoa(output_ptr, in / 4096.f, pad_length, pad_char, precision, 0); + output_ptr = strend(output_ptr); + } break; + + case 'E': // time seconds + case 'e': { + s64 in = arg_regs[arg_reg_idx++]; + s8 pad_length = argument_data[0].data[0]; + s8 pad_char = argument_data[0].data[1]; + if (pad_char == -1) + pad_char = ' '; + s8 precision = argument_data[0].data[2]; + if (precision == -1) + precision = 4; + float value; + if (in < 0) { + ASSERT(false); // i don't get this one + } else { + value = in; + } + ftoa(output_ptr, value / 300.f, pad_length, pad_char, precision, 0); + output_ptr = strend(output_ptr); + } break; + + case 'T': + case 't': { + sprintf(output_ptr, "\t"); + output_ptr = strend(output_ptr); + } break; + + default: + MsgErr("format: unknown code 0x%02x\n", format_ptr[1]); + MsgErr("input was %s\n", format_cstring); + // ASSERT(false); + goto copy_char_hack; + break; + } + format_ptr++; + } else { + // got normal char, just copy it + copy_char_hack: // we goto here if we get a bad code for ~, which sort of backtracks and falls + // back to regular character copying + *output_ptr = *format_ptr; + output_ptr++; + } + format_ptr++; + } // end format string while + + // end + *output_ptr = 0; + output_ptr++; + + if (original_dest == s7.offset + FIX_SYM_TRUE) { + // #t means to put it in the print buffer + + // change for Jak 2: if we are disk-booting and do a (format #t, immediately flush to stdout. + // we'd get these eventually in ClearPending, but for some reason they flush these here. + // This is nicer because we may crash in between here and flushing the print buffer. + if (DiskBoot) { + // however, we are going to disable it anyway because it spams the console and is annoying + if (false) { + printf("%s", PrintPendingLocal3); + fflush(stdout); + } + PrintPending = make_ptr(PrintPendingLocal2).cast(); + // if we don't comment this line, our output gets cleared + // *PrintPendingLocal3 = 0; + } + + return 0; + } else if (original_dest == s7.offset + FIX_SYM_FALSE) { + // #f means print to new string + u32 string = make_string_from_c(PrintPendingLocal3); + PrintPending = make_ptr(PrintPendingLocal2).cast(); + *PrintPendingLocal3 = 0; + return string; + } else if (original_dest == 0) { + printf("%s", PrintPendingLocal3); + fflush(stdout); + PrintPending = make_ptr(PrintPendingLocal2).cast(); + *PrintPendingLocal3 = 0; + return 0; + } else { + if ((original_dest & OFFSET_MASK) == BASIC_OFFSET) { + Ptr type = *Ptr>(original_dest - 4); + if (type == *Ptr>(s7.offset + FIX_SYM_STRING_TYPE - 1)) { + u32 len = *Ptr(original_dest); + char* str = Ptr(original_dest + 4).c(); + kstrncat(str, PrintPendingLocal3, len); + PrintPending = make_ptr(PrintPendingLocal2).cast(); + *PrintPendingLocal3 = 0; + return 0; + } else if (type == *Ptr>(s7.offset + FIX_SYM_FILE_STREAM - 1)) { + size_t len = strlen(PrintPendingLocal3); + // sceWrite + ee::sceWrite(*Ptr(original_dest + 12), PrintPendingLocal3, len); + + PrintPending = make_ptr(PrintPendingLocal2).cast(); + *PrintPendingLocal3 = 0; + return 0; + } + } + ASSERT(false); // unknown destination + return 0; + } + + ASSERT(false); // ?????? + return 7; +} diff --git a/game/kernel/jak3/kprint.h b/game/kernel/jak3/kprint.h new file mode 100644 index 0000000000..7dd5046e01 --- /dev/null +++ b/game/kernel/jak3/kprint.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void output_sql_query(char* query_name); +} + +// todo, do we actually have to do this, now that we aren't calling it from asm? +extern "C" { +s32 format_impl_jak3(uint64_t* args); +} \ No newline at end of file diff --git a/game/kernel/jak3/kscheme.cpp b/game/kernel/jak3/kscheme.cpp new file mode 100644 index 0000000000..724026fd7a --- /dev/null +++ b/game/kernel/jak3/kscheme.cpp @@ -0,0 +1,1986 @@ +#include "kscheme.h" + +#include + +#include "common/common_types.h" +#include "common/goal_constants.h" +#include "common/log/log.h" +#include "common/symbols.h" + +#include "game/kernel/common/Symbol4.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kmemcard.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/jak3/fileio.h" +#include "game/kernel/jak3/kdgo.h" +#include "game/kernel/jak3/klink.h" +#include "game/kernel/jak3/klisten.h" +#include "game/kernel/jak3/kmachine.h" +#include "game/kernel/jak3/kmalloc.h" +#include "game/kernel/jak3/kprint.h" + +#define JAK3_HASH_TABLE + +namespace jak3 { + +using namespace jak3_symbols; + +Ptr SymbolString; +Ptr> CollapseQuote; +Ptr> LevelTypeList; +Ptr UnknownName; +bool DebugSymbols = false; +Ptr KernelDebug; +Ptr> SqlResult; + +#ifdef JAK3_HASH_TABLE +std::unordered_map g_symbol_hash_table; +#endif + +void kscheme_init_globals() { + LevelTypeList.offset = 0; + SymbolString.offset = 0; + CollapseQuote.offset = 0; + UnknownName.offset = 0; + DebugSymbols = false; + KernelDebug.offset = 0; + SqlResult.offset = 0; +#ifdef JAK3_HASH_TABLE + g_symbol_hash_table.clear(); +#endif +} + +namespace { +u32 u32_in_fixed_sym(u32 offset) { + return Ptr>(s7.offset + offset)->value(); +} + +void fixed_sym_set(u32 offset, u32 value) { + Ptr>(s7.offset + offset)->value() = value; +} +} // namespace + +u64 new_illegal(u32 allocation, u32 type) { + (void)allocation; + MsgErr("dkernel: illegal attempt to call new method of static object type %s\n", + sym_to_string(Ptr(type)->symbol)->data()); + return s7.offset; +} + +u64 alloc_from_heap(u32 heap_symbol, u32 type, s32 size, u32 pp) { + auto heap_ptr = Ptr>>(heap_symbol)->value(); + s32 aligned_size = ((size + 0xf) / 0x10) * 0x10; + if ((heap_symbol == s7.offset + FIX_SYM_GLOBAL_HEAP) || + (heap_symbol == s7.offset + FIX_SYM_DEBUG) || + (heap_symbol == s7.offset + FIX_SYM_LOADING_LEVEL) || + (heap_symbol == s7.offset + FIX_SYM_PROCESS_LEVEL_HEAP)) { + if (!type) { // no type given, just call it a global-object + return kmalloc(heap_ptr, size, KMALLOC_MEMSET, "global-object").offset; + } + + Ptr typ(type); + if (!typ->symbol.offset) { // type doesn't have a symbol, just call it a global-object + return kmalloc(heap_ptr, size, KMALLOC_MEMSET, "global-object").offset; + } + + Ptr gstr = sym_to_string(typ->symbol); + if (!gstr->len) { // string has nothing in it. + return kmalloc(heap_ptr, size, KMALLOC_MEMSET, "global-object").offset; + } + + return kmalloc(heap_ptr, size, KMALLOC_MEMSET, gstr->data()).offset; + } else if (heap_symbol == s7.offset + FIX_SYM_PROCESS_TYPE) { + u32 start = *Ptr(pp + 0x64); + u32 heapEnd = *Ptr(pp + 0x60); + u32 allocEnd = start + aligned_size; + + if (allocEnd < heapEnd) { + *Ptr(pp + 0x64) = allocEnd; + memset(Ptr(start).c(), 0, aligned_size); + return start; + } else { + MsgErr("kmalloc: !alloc mem in heap for # (%d bytes)\n", pp, aligned_size); + return 0; + } + } else if (heap_symbol == s7.offset + FIX_SYM_SCRATCH) { + ASSERT(false); // nyi, I think unused. + return 0; + } else { + memset(Ptr(heap_symbol).c(), 0, aligned_size); // treat it as a stack address + return heap_symbol; + } +} + +/*! + * Allocate untyped memory. + */ +u64 alloc_heap_memory(u32 heap, u32 size) { + // should never happen on process heap + return alloc_from_heap(heap, 0, size, UNKNOWN_PP); +} + +/*! + * Allocate memory and add type tag for an object. + * For allocating basics. + * Called from GOAL. + */ +u64 alloc_heap_object(u32 heap, u32 type, u32 size, u32 pp) { + auto mem = alloc_from_heap(heap, type, size, pp); + if (!mem) { + return 0; + } + + *Ptr(mem) = type; + return mem + BASIC_OFFSET; +} + +/*! + * Allocate a structure and get the structure size from the type. + */ +u64 new_structure(u32 heap, u32 type) { + // should never happen on process heap + return alloc_from_heap(heap, type, Ptr(type)->allocated_size, UNKNOWN_PP); +} + +/*! + * Allocate a structure with a dynamic size + */ +u64 new_dynamic_structure(u32 heap_symbol, u32 type, u32 size) { + // should never happen on process heap + return alloc_from_heap(heap_symbol, type, size, UNKNOWN_PP); +} + +/*! + * Delete a structure. Not supported, as it uses kfree, which doesn't do anything. + */ +void delete_structure(u32 s) { + kfree(Ptr(s)); +} + +/*! + * Allocate a basic of fixed size. + */ +u64 new_basic(u32 heap, u32 type, u32 /*size*/, u32 pp) { + return alloc_heap_object(heap, type, Ptr(type)->allocated_size, pp); +} + +/*! + * Delete a basic. Not supported, as it uses kfree. + */ +void delete_basic(u32 s) { + // note that the game has a bug here and has s as a uint* and does -4 which is actually a + // 16-byte offset. Luckily kfree does nothing so there's no harm done. But it's a good indication + // that the "freeing memory" feature never made it very far in development. This bug exists in + // Jak 3 as well. + kfree(Ptr(s - BASIC_OFFSET * 4)); // replicate the bug +} + +/*! + * Allocate a new pair and set its car and cdr. + */ +u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr) { + auto mem = alloc_from_heap(heap, type, Ptr(type)->allocated_size, UNKNOWN_PP); + if (!mem) { + return 0; + } + + u32* m = Ptr(mem).c(); + m[0] = car; + m[1] = cdr; + return mem + PAIR_OFFSET; +} + +/*! + * Delete a pair. BUG + */ +void delete_pair(u32 s) { + // the -8 should be a -2, but s is likely a u32* in the code. + kfree(Ptr(s - 8)); +} + +u64 make_string(u32 size) { + using namespace jak3_symbols; + auto mem_size = size + 1; // null + if (mem_size < 8) { + mem_size = 8; // min size of string + } + + // total size is mem_size (chars + null term), plus basic_offset (type tag) + 4 (string size) + auto mem = + alloc_heap_object((s7 + FIX_SYM_GLOBAL_HEAP).offset, u32_in_fixed_sym(FIX_SYM_STRING_TYPE), + mem_size + BASIC_OFFSET + sizeof(uint32_t), UNKNOWN_PP); + + // set the string size field. + if (mem) { + *Ptr(mem) = size; + } + return mem; +} + +/*! + * Convert a C string to a GOAL string. + * Allocates from the global heap and copies the string data. + */ +u64 make_string_from_c(const char* c_str) { + auto str_size = strlen(c_str); + auto mem_size = str_size + 1; + if (mem_size < 8) { + mem_size = 8; + } + + auto mem = + alloc_heap_object((s7 + FIX_SYM_GLOBAL_HEAP).offset, u32_in_fixed_sym(FIX_SYM_STRING_TYPE), + mem_size + BASIC_OFFSET + 4, UNKNOWN_PP); + // there's no check for failed allocation here! + + // string size field + *Ptr(mem) = str_size; + + // rest is chars + kstrcpy(Ptr(mem + 4).c(), c_str); + return mem; +} + +u64 make_debug_string_from_c(const char* c_str) { + auto str_size = strlen(c_str); + auto mem_size = str_size + 1; + if (mem_size < 8) { + mem_size = 8; + } + + auto mem = alloc_heap_object((s7 + FIX_SYM_DEBUG).offset, u32_in_fixed_sym(FIX_SYM_STRING_TYPE), + mem_size + BASIC_OFFSET + 4, UNKNOWN_PP); + // there's no check for failed allocation here! + + // string size field + *Ptr(mem) = str_size; + + // rest is chars + kstrcpy(Ptr(mem + 4).c(), c_str); + return mem; +} + +extern "C" { +#ifndef __aarch64__ +#ifdef __APPLE__ +void _arg_call_systemv() asm("_arg_call_systemv"); +void _stack_call_systemv() asm("_stack_call_systemv"); +void _stack_call_win32() asm("_stack_call_win32"); +#else +void _arg_call_systemv(); +void _stack_call_systemv(); +void _stack_call_win32(); +#endif +#else +#if defined(__APPLE__) +void _arg_call_arm64() asm("_arg_call_arm64"); +void _stack_call_arm64() asm("_stack_call_arm64"); +#else +void _arg_call_arm64(); +void _stack_call_arm64(); +#endif +#endif +} + +/*! + * This creates an OpenGOAL function from a C++ function. Only 6 arguments can be accepted. + * But calling this function is fast. It used to be really fast but wrong. + */ +Ptr make_function_from_c_systemv(void* func, bool arg3_is_pp) { + auto mem = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE), 0x40, UNKNOWN_PP)); + auto f = (uint64_t)func; + auto target_function = (u8*)&f; +#ifndef __aarch64__ + auto trampoline_function_addr = _arg_call_systemv; +#else + auto trampoline_function_addr = _arg_call_arm64; +#endif + auto trampoline = (u8*)&trampoline_function_addr; + // TODO - x86 code still being emitted below + + // movabs rax, target_function + int offset = 0; + mem.c()[offset++] = 0x48; + mem.c()[offset++] = 0xb8; + for (int i = 0; i < 8; i++) { + mem.c()[offset++] = target_function[i]; + } + + // push rax + mem.c()[offset++] = 0x50; + + // movabs rax, trampoline + mem.c()[offset++] = 0x48; + mem.c()[offset++] = 0xb8; + for (int i = 0; i < 8; i++) { + mem.c()[offset++] = trampoline[i]; + } + + if (arg3_is_pp) { + // mov rcx, r13. Puts pp in the third argument. + mem.c()[offset++] = 0x4c; + mem.c()[offset++] = 0x89; + mem.c()[offset++] = 0xe9; + } + + // jmp rax + mem.c()[offset++] = 0xff; + mem.c()[offset++] = 0xe0; + // the asm function's ret will return to the caller of this (GOAL code) directlyz. + + // CacheFlush(mem, 0x34); + + return mem.cast(); +} + +/*! + * Create a GOAL function from a C function. This doesn't export it as a global function, it just + * creates a function object on the global heap. + * + * This creates a simple trampoline function which jumps to the C function and reorders the + * arguments to be correct for Windows. + */ +Ptr make_function_from_c_win32(void* func, bool arg3_is_pp) { + // allocate a function object on the global heap + auto mem = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE), 0x80, UNKNOWN_PP)); + auto f = (uint64_t)func; + auto fp = (u8*)&f; + + int i = 0; + // we will put the function address in RAX with a movabs rax, imm8 + mem.c()[i++] = 0x48; + mem.c()[i++] = 0xb8; + for (int j = 0; j < 8; j++) { + mem.c()[i++] = fp[j]; + } + + /* + push rdi + push rsi + push rdx + push rcx + pop r9 + pop r8 + pop rdx + pop rcx + push r10 + push r11 + sub rsp, 40 + */ + for (auto x : {0x57, 0x56, 0x52, 0x51, 0x41, 0x59, 0x41, 0x58, 0x5A, 0x59, 0x41, 0x52, 0x41, 0x53, + 0x48, 0x83, 0xEC, 0x28}) { + mem.c()[i++] = x; + } + + if (arg3_is_pp) { + // mov r9, r13. Puts pp in the third argument. + mem.c()[i++] = 0x4d; + mem.c()[i++] = 0x89; + mem.c()[i++] = 0xe9; + } + + /* + call rax + add rsp, 40 + pop r11 + pop r10 + ret + */ + for (auto x : {0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0x41, 0x5B, 0x41, 0x5A, 0xC3}) { + mem.c()[i++] = x; + } + + // CacheFlush(mem, 0x34); + + return mem.cast(); +} + +Ptr make_stack_arg_function_from_c_systemv(void* func) { + // allocate a function object on the global heap + auto mem = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE), 0x40, UNKNOWN_PP)); + auto f = (uint64_t)func; + auto target_function = (u8*)&f; +#ifndef __aarch64__ + auto trampoline_function_addr = _stack_call_systemv; +#else + auto trampoline_function_addr = _stack_call_arm64; +#endif + auto trampoline = (u8*)&trampoline_function_addr; + + // movabs rax, target_function + int offset = 0; + mem.c()[offset++] = 0x48; + mem.c()[offset++] = 0xb8; + for (int i = 0; i < 8; i++) { + mem.c()[offset++] = target_function[i]; + } + + // push rax + mem.c()[offset++] = 0x50; + + // movabs rax, trampoline + mem.c()[offset++] = 0x48; + mem.c()[offset++] = 0xb8; + for (int i = 0; i < 8; i++) { + mem.c()[offset++] = trampoline[i]; + } + + // jmp rax + mem.c()[offset++] = 0xff; + mem.c()[offset++] = 0xe0; + + // CacheFlush(mem, 0x34); + + return mem.cast(); +} + +#ifdef _WIN32 +/*! + * Create a GOAL function from a C function. This calls a windows function, but doesn't scramble + * the argument order. It's supposed to be used with _format_win32 which assumes GOAL order. + */ +Ptr make_stack_arg_function_from_c_win32(void* func) { + // allocate a function object on the global heap + auto mem = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE), 0x80, UNKNOWN_PP)); + auto f = (uint64_t)func; + auto fp = (u8*)&f; + auto trampoline_function_addr = _stack_call_win32; + auto trampoline = (u8*)&trampoline_function_addr; + + int i = 0; + // we will put the function address in RAX with a movabs rax, imm8 + mem.c()[i++] = 0x48; + mem.c()[i++] = 0xb8; + for (int j = 0; j < 8; j++) { + mem.c()[i++] = fp[j]; + } + + // push rax + mem.c()[i++] = 0x50; + + // we will put the function address in RAX with a movabs rax, imm8 + mem.c()[i++] = 0x48; + mem.c()[i++] = 0xb8; + for (int j = 0; j < 8; j++) { + mem.c()[i++] = trampoline[j]; + } + + /* + * jmp rax + */ + for (auto x : {0xFF, 0xE0}) { + mem.c()[i++] = x; + } + + return mem.cast(); +} +#endif + +/*! + * Create a GOAL function from a C function. This doesn't export it as a global function, it just + * creates a function object on the global heap. + * + * The implementation is to create a simple trampoline function which jumps to the C function. + */ +Ptr make_function_from_c(void* func, bool arg3_is_pp = false) { +#ifdef __linux__ + return make_function_from_c_systemv(func, arg3_is_pp); +#elif __APPLE__ + return make_function_from_c_systemv(func, arg3_is_pp); +#elif _WIN32 + return make_function_from_c_win32(func, arg3_is_pp); +#endif +} + +Ptr make_stack_arg_function_from_c(void* func) { +#ifdef __linux__ + return make_stack_arg_function_from_c_systemv(func); +#elif __APPLE__ + return make_stack_arg_function_from_c_systemv(func); +#elif _WIN32 + return make_stack_arg_function_from_c_win32(func); +#endif +} + +/*! + * Create a GOAL function which does nothing and immediately returns. + */ +Ptr make_nothing_func() { + auto mem = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE), 0x14, UNKNOWN_PP)); + + // a single x86-64 ret. + mem.c()[0] = 0xc3; + // CacheFlush(mem, 8); + return mem.cast(); +} + +/*! + * Create a GOAL function which returns 0. + */ +Ptr make_zero_func() { + auto mem = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE), 0x14, UNKNOWN_PP)); + // xor eax, eax + mem.c()[0] = 0x31; + mem.c()[1] = 0xc0; + // ret + mem.c()[2] = 0xc3; + // CacheFlush(mem, 8); + return mem.cast(); +} + +u64 symbol_to_string_from_c(u32 sym) { + auto name = sym_to_string(Ptr>(sym)); + if (name.offset == UnknownName.offset || + (kglobalheap->top_base.offset < name.offset && + (DebugSegment == 0 || u32_in_fixed_sym(FIX_SYM_KERNEL_SYMBOL_WARNINGS) != s7.offset))) { + MsgWarn( + "dkernel: doing a symbol->string on %s (addr #x%x), but the symbol has not been marked as " + "symbol-export-string\n", + name->data(), sym); + } + return name.offset; +} + +/*! + * Given a C function and a name, create a GOAL function and store it in the symbol with the given + * name. This effectively creates a global GOAL function with the given name which calls the given C + * function. + * + * This work on both Linux and Windows, but only supports up to 6 arguments. + */ +Ptr make_function_symbol_from_c(const char* name, void* f) { + auto sym = intern_from_c(-1, 0, name); + auto func = make_function_from_c(f); + sym->value() = func.offset; + return func; +} + +/*! + * Like make_function_symbol_from_c, but all 8 GOAL arguments are put into an array on the stack. + * The address of this array is passed as the first and only argument to f. + */ +Ptr make_stack_arg_function_symbol_from_c(const char* name, void* f) { + auto sym = intern_from_c(-1, 0, name); + auto func = make_stack_arg_function_from_c(f); + sym->value() = func.offset; + return func; +} + +u32 make_raw_function_symbol_from_c(const char* name, u32 value) { + intern_from_c(-1, 0, name)->value() = value; + return value; +} + +Ptr> set_fixed_symbol(int offset, const char* name, u32 value) { + Ptr> sym(s7.offset + offset); + ASSERT((sym.offset & 3) == 1); // + sym->value() = value; + + if (sym_to_string_ptr(sym).c()->offset) { + printf("setting %s\n", name); + ASSERT_NOT_REACHED(); // duplicate def + } + +#ifdef JAK3_HASH_TABLE + ASSERT((offset % 4) == 0); + g_symbol_hash_table.insert(std::make_pair(name, offset / 4)); +#endif + + kheaplogging = true; + *sym_to_string_ptr(sym).c() = Ptr(make_string_from_c(name)); + NumSymbols = NumSymbols + 1; + kheaplogging = false; + return sym; +} + +Ptr> find_symbol_in_area(const char* name, u32 start, u32 end) { + for (u32 i = start; i < end; i += 4) { + auto sym = Ptr>(i); + auto str = sym_to_string(sym); + if (str.offset && !strcmp(str->data(), name)) { + return sym; + } + } + + // failed + return Ptr>(0); +} + +#ifdef JAK3_HASH_TABLE +Ptr> find_symbol_from_c_ht(const char* name) { + const auto& it = g_symbol_hash_table.find(name); + if (it == g_symbol_hash_table.end()) { + return Ptr>(0); + } else { + return Ptr>(s7.offset + it->second * 4); + } +} + +Ptr> find_slot_in_area(u32 start, u32 end) { + for (u32 i = start; i < end; i += 4) { + auto sym = Ptr>(i); + auto str = sym_to_string(sym); + if (!str.offset) { + return sym; + } + } + + // failed + return Ptr>(0); +} + +Ptr> intern_from_c_ht(const char* name) { + auto existing = find_symbol_from_c_ht(name); + if (existing.offset) { + return existing; + } + + auto slot = find_slot_in_area(s7.offset, LastSymbol.offset); + if (!slot.offset) { + slot = find_slot_in_area(SymbolTable2.offset, s7.offset - 0x10); + } + ASSERT(slot.offset); // out of symbols!! + + NumSymbols++; + *sym_to_string_ptr(slot) = Ptr(make_string_from_c(name)); + g_symbol_hash_table[name] = (slot.offset - s7.offset) / 4; + return slot; +} + +#endif + +/*! + * Get a pointer to a symbol. Can provide the symbol id, the name, or both. + */ +Ptr> find_symbol_from_c(uint16_t sym_id, const char* name) { +#ifdef JAK3_HASH_TABLE + return find_symbol_from_c_ht(name); +#endif + // sign extend + int extended_sym_id = (int16_t)sym_id; + if (sym_id == 0xffff) { + // the ID wasn't provided, so we have to use the name + if (!name) { + // always warn - no name or ID! + MsgErr("dkernel: attempted to find symbol with NULL name and id #x%x\n", sym_id); + return Ptr>(0); + } else { + // find the symbol + Ptr> lookup_result = find_symbol_in_area(name, s7.offset, LastSymbol.offset); + if (lookup_result.offset == 0) { + lookup_result = find_symbol_in_area(name, SymbolTable2.offset, s7.offset - 0x10); + } + + // do some sanity checking, but only in retail or if we've explicitly asked for it. + if (!DebugSegment || u32_in_fixed_sym(FIX_SYM_KERNEL_SYMBOL_WARNINGS) != s7.offset) { + if (lookup_result.offset == 0) { + // lookup by the name failed. + MsgWarn("dkernel: doing a string->symbol on %s, but could not find the name\n", name); + } else { + auto sym_string = sym_to_string(lookup_result); + // not sure how you could get unknown name here... + // but the second check sees if you were only saved by having the symbol string in the + // debug heap. This would tell you that the lookup worked, but would fail in retail mode. + if ((sym_string == UnknownName) || (kglobalheap->top_base.offset < sym_string.offset)) { + MsgWarn( + "dkernel: doing a string->symbol on %s, but the symbol has not been marked " + "as symbol-export-string\n", + name); + } + } + } + + return lookup_result; + } + } else { + // just use the ID. warn if there's a name conflict. + Ptr> sym(s7.offset + extended_sym_id - 1); + if (sym.offset != s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR) { + auto existing_name = sym_to_string(sym); + if (existing_name.offset && !strcmp(existing_name->data(), name)) { + MsgWarn( + "dkernel: WARNING: attempting to find symbol %s at id #x%x but symbol %s was " + "already there.\n", + name, sym_id, existing_name->data()); + } + } + return sym; + } +} + +/*! + * Find or create a symbol. + * New for Jak 3 is that there is no longer a symbol hash table. So there are some significant + * changes to how this works. Also, many symbols do not store their name, to save memory. + * + * @param sym_id The symbol ID. This _must_ be provided if the symbol does not exist yet, or if the + * symbol's name isn't known. Use -1 if the symbol ID is unknown. + * + * @param name The name. This can be used instead of the ID if the symbol's name is stored. + * + * @param flags Optional flag (0x40) can force the symbol's name to be stored. This uses memory. + * + */ +Ptr> intern_from_c(int sym_id, int flags, const char* name) { +#ifdef JAK3_HASH_TABLE + return intern_from_c_ht(name); +#endif + // first, look up the symbol. + Ptr> symbol = find_symbol_from_c(sym_id, name); + kheaplogging = true; + + if (symbol.offset == 0) { + // the function above can only fail if we didn't give an ID. + MsgErr("dkernel: attempted to intern symbol %s using the name, but could not find it\n", name); + kheaplogging = false; + return Ptr>(0); + } + + if (symbol.offset == s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR) { + // in case it's the empty pair, just return and don't worry about names. + kheaplogging = false; + return symbol; + } + + // if the symbol is new, then the name pointer will be 0, and we need to set it up. + auto sptr = sym_to_string_ptr(symbol); + auto current_string = *sptr; + if (current_string.offset) { // existing symbol + if ((flags & 0x40U) == 0) { // symbol-export-string not set + // nothing to do! + kheaplogging = false; + return symbol; + } + + // if the symbol-export-string flag is set, we need to make sure that there's a known name + // and the name is stored in the global heap: + if ((current_string != UnknownName) && + (current_string.offset <= kglobalheap->top_base.offset)) { + // it is, nothing to do. + kheaplogging = false; + return symbol; + } + + // "upgrade" from the debug heap to global. (this could also trigger if the name was previously + // unknown) + MsgWarn("dkernel: upgrading symbol %s (flags #x%x) from debug heap to global\n", name, flags); + *sptr = Ptr(make_string_from_c(name)); + kheaplogging = false; + return symbol; + } + + // setting up a new symbol case: + Ptr new_string; + if (DebugSymbols == 0) { + // normal mode + if ((flags & 0x40U) != 0) { + // if symbol-export-string is set, allocate it on the global heap. + new_string = Ptr(make_string_from_c(name)); + } else if (DebugSegment != 0) { + // if debugsegment, always load all symbols to debug heap for easy debugging. + new_string = Ptr(make_debug_string_from_c(name)); + } else { + // otherwise, no symbols!! save memory! + new_string = UnknownName; + } + } else { + // debug symbol mode is on - force it to the global heap no matter what. + new_string = Ptr(make_string_from_c(name)); + } + *sptr = new_string; + + NumSymbols++; + + kheaplogging = 0; + return symbol; +} + +u64 intern(u32 name) { + return intern_from_c(-1, 0x40, Ptr(name)->data()).offset; +} + +/*! + * Configure a type. + */ +Ptr set_type_values(Ptr type, Ptr parent, u64 flags) { + type->parent = parent; + type->allocated_size = (flags & 0xffff); + type->heap_base = (flags >> 16) & 0xffff; + type->padded_size = ((type->allocated_size + 0xf) & 0xfff0); + + u16 new_methods = (flags >> 32) & 0xffff; // i think this accidentally uses jak1 style flags. + if (type->num_methods < new_methods) { + type->num_methods = new_methods; + } + + return type; +} + +static bool in_valid_memory_for_new_type(u32 addr) { + if (SymbolTable2.offset <= addr && addr < 0x8000000) { + return true; + } + + if (addr < 0x100000 && addr >= 0x84000) { + return true; + } + return false; +} +u32 size_of_type(u32 method_count) { + return (4 * method_count + 0x23) & 0xfffffff0; +} + +static bool is_valid_type(u32 addr) { + if ((addr & 7) != 4) { + return false; + } + + if (*Ptr(addr - 4) != u32_in_fixed_sym(FIX_SYM_TYPE_TYPE)) { + return false; + } + + return true; +} + +/*! + * Given a symbol for the type name, allocate memory for a type and add it to the symbol table. + * New: in Jak 2, there's a level type list + */ +Ptr alloc_and_init_type(Ptr>> sym, + u32 method_count, + bool force_global_type) { + // number of bytes for this type + u32 type_size = size_of_type(method_count); + u32 type_mem = 0; + ASSERT(sym.offset & 1); + + if (!force_global_type && + u32_in_fixed_sym(FIX_SYM_LOADING_LEVEL) != u32_in_fixed_sym(FIX_SYM_GLOBAL_HEAP)) { + u32 type_list_ptr = LevelTypeList->value(); + if (type_list_ptr == 0) { + // we don't have a type-list... just alloc on global + MsgErr("dkernel: trying to init loading level type \'%s\' while type-list is undefined\n", + sym_to_string(sym)->data()); + type_mem = alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_TYPE_TYPE), type_size, UNKNOWN_PP); + } else { + // we do have a type list! allocate on the level heap + type_mem = alloc_heap_object(s7.offset + FIX_SYM_LOADING_LEVEL, + u32_in_fixed_sym(FIX_SYM_TYPE_TYPE), type_size, UNKNOWN_PP); + // link us! + u32 old_head = *Ptr(type_list_ptr); + *Ptr(type_list_ptr) = type_mem; + // I guess we hide this in the memusage method. + Ptr(type_mem)->memusage_method.offset = old_head; + } + } else { + // normal global type + type_mem = alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, + u32_in_fixed_sym(FIX_SYM_TYPE_TYPE), type_size, UNKNOWN_PP); + } + + Ptr the_type(type_mem); + sym->value() = the_type; + the_type->allocated_size = type_size; + the_type->padded_size = ((type_size + 0xf) & 0xfff0); + return the_type; +} + +/*! + * Like intern, but returns a type instead of a symbol. If the type doesn't exist, a new one is + * allocated. + */ +Ptr intern_type_from_c(int a, int b, const char* name, u64 methods) { + // there's a weird flag system used here. + // if methods is a number that's not 0 or 1, its used as the desired number of methods. + // If method is 0, and a new type needs to be created, it uses 12 methods + // If method is 1, and a new type needs to be created, it uses 44 methods + // If method is 0 or 1 and no new type needs to be created, there is no error. + // Requesting a type to have fewer methods than the existing type has is ok. + // Requesting a type to have more methods than the existing type is not ok and prints an error. + + auto symbol = intern_from_c(a, b, name); + u32 sym_value = symbol->value(); + + if (!sym_value) { + // new type + int n_methods = methods; + + if (methods == 0) { + // some stupid types like user-defined children of integers have "0" as the method count + n_methods = 0xc; + } else if (methods == 1) { + // whatever builds the v2/v4 object files (level data) doesn't actually know method counts. + // so it just puts a 1. In this case, we should put lots of methods, just in case. + // I guess 44 was the number they picked. + n_methods = 0x2c; + } + + // create the type. + auto casted_sym = symbol.cast>>(); + auto type = alloc_and_init_type(casted_sym, n_methods, 0); // allow level types + type->symbol = casted_sym; + type->num_methods = n_methods; + return type; + } else { + // the type exists. + auto type = Ptr(sym_value); + // note - flags of 0 or 1 will pass through here without triggering the error. + if (size_of_type(type->num_methods) < size_of_type(methods)) { + MsgErr( + "dkernel: trying to redefine a type '%s' with %d methods when it had %d, try " + "restarting\n", + name, (u32)methods, type->num_methods); + ASSERT(false); + } + return type; + } +} + +/*! + * Wrapper of intern_type_from_c to use with GOAL. It accepts a gstring as a name. + */ +u64 intern_type(u32 name, u64 methods) { + return intern_type_from_c(-1, 0, Ptr(name)->data(), methods).offset; +} + +/*! + * Setup a type which is located in a fixed spot of the symbol table. + */ +Ptr set_fixed_type(u32 offset, + const char* name, + Ptr>> parent_symbol, + u64 flags, + u32 print, + u32 inspect) { + Ptr>> type_symbol(s7.offset + offset); + Ptr symbol_value = type_symbol->value(); + + // set the symbol's name and hash + *sym_to_string_ptr(type_symbol) = Ptr(make_string_from_c(name)); + ASSERT(g_symbol_hash_table.count(name) == 0); + g_symbol_hash_table[name] = (type_symbol.offset - s7.offset) / 4; + NumSymbols++; + + if (symbol_value.offset == 0) { + // no type memory exists, let's allocate it. force it global + // the flag logic here multiplies the method count 2, hopefully + // this will set up the symbol + symbol_value = alloc_and_init_type(type_symbol, (flags >> 0x1f) & 0xffff, 1); + } + + // remember our symbol + symbol_value->symbol = type_symbol; + // make our type a type (we're a basic) + u32 type_of_type = u32_in_fixed_sym(FIX_SYM_TYPE_TYPE); + *Ptr(symbol_value.offset - 4) = type_of_type; + + Ptr parent_type = parent_symbol->value(); + set_type_values(symbol_value, parent_type, flags); + + symbol_value->new_method = parent_type->new_method; + symbol_value->delete_method = parent_type->delete_method; + + if (!print) { + symbol_value->print_method = parent_type->print_method; + } else { + symbol_value->print_method.offset = print; + } + + if (!inspect) { + symbol_value->inspect_method = parent_type->inspect_method; + } else { + symbol_value->inspect_method.offset = inspect; + } + + symbol_value->length_method.offset = u32_in_fixed_sym(FIX_SYM_ZERO_FUNC); + symbol_value->asize_of_method = parent_type->asize_of_method; + symbol_value->copy_method = parent_type->copy_method; + return symbol_value; +} + +u64 new_type(u32 symbol, u32 parent, u64 flags) { + u32 n_methods = (flags >> 32) & 0xffff; + if (n_methods == 0) { + // 12 methods used as default, if the user has not provided us with a number + n_methods = 12; + } + + auto sym_string = sym_to_string(Ptr>(symbol)); + const char* sym_string_c = nullptr; + if (sym_string.offset) { + sym_string_c = sym_string->data(); + } + + u32 parent_num_methods = Ptr(parent)->num_methods; + + auto new_type_obj = intern_type_from_c(((symbol - s7.offset) + 1), 0x80, sym_string_c, n_methods); + u32 original_type_list_value = new_type_obj->memusage_method.offset; + Ptr* child_slots = &(new_type_obj->new_method); + Ptr* parent_slots = &(Ptr(parent)->new_method); + for (u32 i = 0; i < n_methods; i++) { + if (i < parent_num_methods) { // bug fix from jak 1 + child_slots[i] = parent_slots[i]; + } else { + child_slots[i].offset = 0; + } + } + + // deal with loading-level types + if (u32_in_fixed_sym(FIX_SYM_LOADING_LEVEL) == u32_in_fixed_sym(FIX_SYM_GLOBAL_HEAP)) { + // not loading a level + + // we'll consider a type list if it's #f or a valid type + if (original_type_list_value && (original_type_list_value == s7.offset || + (in_valid_memory_for_new_type(original_type_list_value) && + is_valid_type(original_type_list_value)))) { + printf("case 1 for new_type level types\n"); + new_type_obj->memusage_method.offset = original_type_list_value; + } + } else { + if (original_type_list_value == 0) { + // loading a level, but the type is global + MsgWarn("dkernel: loading-level init of type %s, but was interned global (this is okay)\n", + sym_to_string(new_type_obj->symbol)->data()); + } else { + new_type_obj->memusage_method.offset = original_type_list_value; + } + } + auto ret = set_type_values(new_type_obj, Ptr(parent), flags).offset; + ; + return ret; +} +/*! + * Is t1 a t2? + */ +u64 type_typep(Ptr t1, Ptr t2) { + if (t1 == t2) { + return (s7 + FIX_SYM_TRUE).offset; + } + + do { + t1 = t1->parent; + if (t1 == t2) { + return (s7 + FIX_SYM_TRUE).offset; + } + } while (t1.offset && t1.offset != u32_in_fixed_sym(FIX_SYM_OBJECT_TYPE)); + return s7.offset; +} + +u64 method_set(u32 type_, u32 method_id, u32 method) { + Ptr type(type_); + if (method_id > 255) + printf("[METHOD SET ERROR] tried to set method %d\n", method_id); + + auto existing_method = type->get_method(method_id).offset; + + if (method == 1) { + method = 0; + } else if (method == 0) { + return 0; + } else if (method == 2) { + method = type->parent->get_method(method_id).offset; + printf("[Method Set] got 2, inheriting\n"); + } + + // do the set + type->get_method(method_id).offset = method; + + // now, propagate to children + // we don't track children directly, so we end up having to iterate the whole symbol to find all + // types. This is slow, so we only do it in some cases + + // the condition is either setting *enable-method-set* in GOAL, or if we're debugging without the + // disk boot. The point of doing this in debug is just to print warning messages. + if (*EnableMethodSet || (!FastLink && MasterDebug && !DiskBoot)) { + auto sym = Ptr>>(s7.offset); + for (; sym.offset < LastSymbol.offset; sym.offset += 4) { + auto sym_value = sym->value(); + if (in_valid_memory_for_new_type(sym_value.offset) && (sym_value.offset & 7) == 4 && + *Ptr(sym_value.offset - 4) == u32_in_fixed_sym(FIX_SYM_TYPE_TYPE) && + method_id < sym_value->num_methods && + sym_value->get_method(method_id).offset == existing_method && + type_typep(sym_value, type) != s7.offset) { + if (FastLink != 0) { + printf("************ WARNING **************\n"); + printf("method %d of %s redefined - you must define class heirarchies in order now\n", + method_id, sym_to_string(sym)->data()); + printf("***********************************\n"); + } + sym_value->get_method(method_id).offset = method; + } + } + + sym = Ptr>>(SymbolTable2.offset); + for (; sym.offset < s7.offset; sym.offset += 4) { + auto sym_value = sym->value(); + if (in_valid_memory_for_new_type(sym_value.offset) && (sym_value.offset & 7) == 4 && + *Ptr(sym_value.offset - 4) == u32_in_fixed_sym(FIX_SYM_TYPE_TYPE) && + method_id < sym_value->num_methods && + sym_value->get_method(method_id).offset == existing_method && + type_typep(sym_value, type) != s7.offset) { + if (FastLink != 0) { + printf("************ WARNING **************\n"); + printf("method %d of %s redefined - you must define class heirarchies in order now\n", + method_id, sym_to_string(sym)->data()); + printf("***********************************\n"); + } + sym_value->get_method(method_id).offset = method; + } + } + } + + return method; +} + +/*! + * Call a GOAL method of a given type. + */ +u64 call_method_of_type(u32 arg, Ptr type, u32 method_id) { + if (((type.offset < SymbolTable2.offset || 0x7ffffff < type.offset) && // not in normal memory + (type.offset < 0x84000 || 0x100000 <= type.offset)) // not in kernel memory + || ((type.offset & OFFSET_MASK) != BASIC_OFFSET)) { // invalid type + cprintf("#<#%x has invalid type ptr #x%x>\n", arg, type.offset); + } else { + auto type_tag = Ptr>(type.offset - 4); + if ((*type_tag).offset == u32_in_fixed_sym(FIX_SYM_TYPE_TYPE)) { + auto f = type->get_method(method_id); + return call_goal(f, arg, 0, 0, s7.offset, g_ee_main_mem); + } else { + cprintf("#<#x%x has invalid type ptr #x%x, bad type #x%x>\n", arg, type.offset, + (*type_tag).offset); + } + } + printf("[ERROR] call_method_of_type failed!\n"); + return arg; +} + +/*! + * Call a GOAL function with 2 arguments. + */ +u64 call_goal_function_arg2(Ptr func, u64 a, u64 b) { + return call_goal(func, a, b, 0, s7.offset, g_ee_main_mem); +} + +/*! + * Call a global GOAL function by name. + */ +u64 call_goal_function_by_name(const char* name) { + return call_goal_function(Ptr(intern_from_c(-1, 0, name)->value())); +} + +u64 print_object(u32 obj); +u64 print_pair(u32 obj); +u64 print_symbol(u32 obj); + +/*! + * Print an object with a newline after it to the GOAL PrintBuffer (not stdout) + */ +u64 sprint(u32 obj) { + auto rv = print_object(obj); + cprintf("\n"); + return rv; +} + +/*! + * Like call_method_of_type, but has two arguments. Used to "relocate" v2/s4 loads. + */ +u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 a2) { + if (((type.offset < SymbolTable2.offset || 0x7ffffff < type.offset) && // not in normal memory + (type.offset < 0x84000 || 0x100000 <= type.offset)) // not in kernel memory + || ((type.offset & OFFSET_MASK) != BASIC_OFFSET)) { // invalid type + cprintf("#<#%x has invalid type ptr #x%x>\n", arg, type.offset); + } else { + auto type_tag = Ptr>(type.offset - 4); + if ((*type_tag).offset == u32_in_fixed_sym(FIX_SYM_TYPE_TYPE)) { + // return type->get_method(method_id).cast().c()(arg,a1,a2); + return call_goal(type->get_method(method_id), arg, a1, a2, s7.offset, g_ee_main_mem); + } else { + cprintf("#<#x%x has invalid type ptr #x%x, bad type #x%x>\n", arg, type.offset, + (*type_tag).offset); + } + } + ASSERT_MSG(false, "[ERROR] call_method_of_type_arg2 failed!"); + return arg; +} + +/*! + * Most generic printing method. + * Does not correctly handle 64 bit boxed integers or object64's correctly. + * It is important that no objects of type object actually exist or this will loop! + */ +u64 print_object(u32 obj) { + if ((obj & OFFSET_MASK) == BINTEGER_OFFSET) { + return print_binteger(s64(s32(obj))); + } else { + if ((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) { // not in kernel memory + cprintf("#", obj); + } else if ((obj & OFFSET_MASK) == PAIR_OFFSET) { + return print_pair(obj); + } else if ((obj & 1) == SYMBOL_OFFSET && obj >= SymbolTable2.offset && + obj < LastSymbol.offset) { + return print_symbol(obj); + } else if ((obj & OFFSET_MASK) == BASIC_OFFSET) { + return call_method_of_type(obj, Ptr(*Ptr(obj - 4)), GOAL_PRINT_METHOD); + } else { + cprintf("#", obj & OFFSET_MASK, obj); + } + } + return obj; +} + +/*! + * Default print method a basic. + * Confirms basic is valid and prints the type name. + */ +u64 print_basic(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & OFFSET_MASK) != BASIC_OFFSET)) { + cprintf("#", obj); + } else { + cprintf("#<%s @ #x%x>", sym_to_string(Ptr(*Ptr(obj - 4))->symbol)->data(), obj); + } + return obj; +} + +/*! + * Print a pair as a LISP list. Don't try to print circular lists or it will get stuck + * Can print improper lists + */ +u64 print_pair(u32 obj) { + if (obj == s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR) { + cprintf("()"); + } else { + // clang-format off + // we want to treat ('quote ) as just ' unless + if (CollapseQuote->value() == s7.offset // CollapseQuote is enabled + || ((obj & 7) != 2) // this object isn't a pair + || *Ptr(obj - 2) != s7.offset + FIX_SYM_QUOTE // the car isn't 'quote + || (*Ptr(obj + 2) & 7) != 2 // the cdr isn't a pair + || *Ptr(*Ptr(obj + 2) + 2) != s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR // the cddr isn't '() + ) { + // clang-format on + cprintf("("); + auto toPrint = obj; + for (;;) { + if ((toPrint & OFFSET_MASK) == PAIR_OFFSET) { + // print CAR + print_object(*Ptr(toPrint - 2)); + + // load up CDR + auto cdr = *Ptr(toPrint + 2); + toPrint = cdr; + if (cdr == s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR) { // end of proper list + cprintf(")"); + return obj; + } else { // continue list + cprintf(" "); + } + } else { // improper list + cprintf(". "); + print_object(toPrint); + cprintf(")"); + return obj; + } + } + } else { + cprintf("'"); + print_object(*Ptr(*Ptr(obj + 2) - 2)); + } + } + return obj; +} + +/*! + * Print method for symbol. Just prints the name without quotes or anything fancy. + */ +u64 print_symbol(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & 1) != 1) || obj < SymbolTable2.offset || obj >= LastSymbol.offset) { + cprintf("#", obj); + } else { + char* str = sym_to_string(Ptr>(obj))->data(); + cprintf("%s", str); + } + return obj; +} + +/*! + * Print method for type. Just prints the name without quotes + */ +u64 print_type(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & OFFSET_MASK) != BASIC_OFFSET) || + *Ptr(obj - 4) != u32_in_fixed_sym(FIX_SYM_TYPE_TYPE)) { + cprintf("#", obj); + } else { + cprintf("%s", sym_to_string(Ptr(obj)->symbol)->data()); + } + return obj; +} + +/*! + * Print method for string. Prints the string in quotes. + */ +u64 print_string(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & OFFSET_MASK) != BASIC_OFFSET) || + *Ptr(obj - 4) != u32_in_fixed_sym(FIX_SYM_STRING_TYPE)) { + if (obj == s7.offset) { + cprintf("#f"); // new in jak 2. + + } else { + cprintf("#", obj); + } + } else { + cprintf("\"%s\"", Ptr(obj)->data()); + } + return obj; +} + +/*! + * Print method for function. Just prints the address because functions can't identify themselves. + */ +u64 print_function(u32 obj) { + cprintf("#", sym_to_string(Ptr(*Ptr(obj - 4))->symbol)->data(), + obj); + return obj; +} + +/*! + * Get the allocated size field of a basic. By default we grab this from the type struct. + * Dynamically sized basics should override this method. + */ +u64 asize_of_basic(u32 it) { + return Ptr(*Ptr(it - BASIC_OFFSET))->allocated_size; +} + +/*! + * Create a copy of a basic. If the destination isn't identified as a symbol, treat it as an + * address. This seems a little bit unsafe to me, as it reads the 4-bytes before the given address + * and checks it against the symbol type pointer to see if its a symbol. It seems possible to have a + * false positive for this check. + */ +u64 copy_basic(u32 obj, u32 heap, u32 /*unused*/, u32 pp) { + // determine size of basic. We call a method instead of using asize_of_basic in case the type has + // overridden the default asize_of method. + u32 size = call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), GOAL_ASIZE_METHOD); + u32 result; + + if ((heap & 1) == 1) { + // we think we're creating a new copy on a heap. First allocate memory... + result = alloc_heap_object(heap, *Ptr(obj - BASIC_OFFSET), size, pp); + // then copy! (minus the type tag, alloc_heap_object already did it for us) + memcpy(Ptr(result).c(), Ptr(obj).c(), size - BASIC_OFFSET); + } else { + printf("DANGER COPY BASIC!\n"); + // copy directly (including type tag) + memcpy(Ptr(heap - BASIC_OFFSET).c(), Ptr(obj - BASIC_OFFSET).c(), size); + result = heap; + } + return result; +} + +u64 inspect_pair(u32 obj); +u64 inspect_symbol(u32 obj); +/*! + * Highest level inspect method. Won't inspect 64-bit bintegers correctly. + */ +u64 inspect_object(u32 obj) { + if ((obj & OFFSET_MASK) == BINTEGER_OFFSET) { + return inspect_binteger(obj); + } else { + if ((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) { // not in kernel memory + cprintf("#", obj); + } else if ((obj & OFFSET_MASK) == PAIR_OFFSET) { + return inspect_pair(obj); + } else if ((obj & 1) == SYMBOL_OFFSET && obj >= SymbolTable2.offset && + obj < LastSymbol.offset) { + return inspect_symbol(obj); + } else if ((obj & OFFSET_MASK) == BASIC_OFFSET) { + return call_method_of_type(obj, Ptr(*Ptr(obj - BASIC_OFFSET)), + GOAL_INSPECT_METHOD); + } else { + cprintf("#", obj & OFFSET_MASK, obj); + } + } + return obj; +} + +/*! + * Inspect a pair. + */ +u64 inspect_pair(u32 obj) { + cprintf("[%8x] pair ", obj); + print_pair(obj); + cprintf("\n"); + return obj; +} + +/*! + * Inspect a string. There's a typo in allocated_length (has underscore instead of dash). + * This typo is fixed in later games. + */ +u64 inspect_string(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & OFFSET_MASK) != BASIC_OFFSET) || + *Ptr(obj - 4) != u32_in_fixed_sym(FIX_SYM_STRING_TYPE)) { + cprintf("#\n", obj); + } else { + auto str = Ptr(obj); + cprintf("[%8x] string\n\tallocated-length: %d\n\tdata: \"%s\"\n", obj, str->len, str->data()); + } + return obj; +} + +/*! + * Inspect a symbol. + */ +u64 inspect_symbol(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & 1) != 1) || obj < SymbolTable2.offset || obj >= LastSymbol.offset) { + cprintf("#", obj); + } else { + auto sym = Ptr>(obj); + cprintf("[%8x] symbol\n\tname: %s\n\tvalue: ", obj, sym_to_string(sym)->data()); + print_object(sym->value()); + cprintf("\n"); + } + return obj; +} + +/*! + * Inspect a type. + */ +u64 inspect_type(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & OFFSET_MASK) != BASIC_OFFSET) || + *Ptr(obj - 4) != u32_in_fixed_sym(FIX_SYM_TYPE_TYPE)) { + cprintf("#\n", obj); + } else { + auto typ = Ptr(obj); + auto sym = typ->symbol; + + cprintf("[%8x] type\n\tname: %s\n\tparent: ", obj, sym_to_string(sym)->data()); + print_object(typ->parent.offset); + cprintf("\n\tsize: %d/%d\n\theap-base: %d\n\tallocated-length: %d\n\tprint: ", + typ->allocated_size, typ->padded_size, typ->heap_base, typ->num_methods); + print_object(typ->print_method.offset); + cprintf("\n\tinspect: "); + print_object(typ->inspect_method.offset); + cprintf("\n"); + } + return obj; +} + +/*! + * Inspect a basic. This is just a fallback for basics which don't know how to inspect themselves. + * We just use print_object. + */ +u64 inspect_basic(u32 obj) { + if (((obj < SymbolTable2.offset || 0x7ffffff < obj) && // not in normal memory + (obj < 0x84000 || 0x100000 <= obj)) // not in kernel memory + || ((obj & OFFSET_MASK) != BASIC_OFFSET)) { + if (obj == s7.offset) { + // added in jak2 (and inlined in jak 3, but only the final version?) + return inspect_symbol(obj); + } else { + cprintf("#\n", obj); + } + } else { + cprintf("[%8x] ", obj); + print_object(*Ptr(obj - 4)); + cprintf("\n"); + } + return obj; +} + +/*! + * Inspect a link block. This link block doesn't seem to be used at all. + */ +u64 inspect_link_block(u32 ob) { + struct LinkBlock { + u32 length; + u32 version; + }; + + auto lb = Ptr(ob); + cprintf("[%8x] link-block\n\tallocated_length: %d\n\tversion: %d\n\tfunction: ", ob, lb->length, + lb->version); + print_object(ob + lb->length); + cprintf("\n"); + return ob; +} + +namespace { +Ptr>> get_fixed_type_symbol(u32 offset) { + return (s7 + offset).cast>>(); +} + +u64 pack_type_flag(u64 methods, u64 heap_base, u64 size) { + return (methods << 32) + (heap_base << 16) + (size); +} +} // namespace + +int InitHeapAndSymbol() { + Ptr symbol_table = + kmalloc(kglobalheap, 4 * GOAL_MAX_SYMBOLS, KMALLOC_MEMSET, "symbol-table").cast(); + SymbolString = + kmalloc(kglobalheap, 4 * GOAL_MAX_SYMBOLS, KMALLOC_MEMSET, "string-table").cast(); + SymbolString.offset += 2 * GOAL_MAX_SYMBOLS; // point to the middle + LastSymbol = symbol_table + 0xff00; + SymbolTable2 = symbol_table + 5; + s7 = symbol_table + 0x8001; + NumSymbols = 0; + reset_output(); + // empty pair (this is extra confusing). + *Ptr(s7.offset + FIX_SYM_EMPTY_CAR - 1) = s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR; + *Ptr(s7.offset + FIX_SYM_EMPTY_CDR - 1) = s7.offset + S7_OFF_FIX_SYM_EMPTY_PAIR; + fixed_sym_set(FIX_SYM_GLOBAL_HEAP, kglobalheap.offset); + + UnknownName = Ptr(make_string_from_c("*unknown-symbol-name*")); + alloc_and_init_type((s7 + FIX_SYM_TYPE_TYPE).cast>>(), 9, true); + alloc_and_init_type((s7 + FIX_SYM_SYMBOL_TYPE).cast>>(), 9, true); + alloc_and_init_type((s7 + FIX_SYM_STRING_TYPE).cast>>(), 9, true); + alloc_and_init_type((s7 + FIX_SYM_FUNCTION_TYPE).cast>>(), 9, true); + + set_fixed_symbol(FIX_SYM_FALSE, "#f", s7.offset + FIX_SYM_FALSE); + set_fixed_symbol(FIX_SYM_TRUE, "#t", s7.offset + FIX_SYM_TRUE); + set_fixed_symbol(FIX_SYM_NOTHING_FUNC, "nothing", make_nothing_func().offset); + set_fixed_symbol(FIX_SYM_ZERO_FUNC, "zero-func", make_zero_func().offset); + set_fixed_symbol(FIX_SYM_ASIZE_OF_BASIC_FUNC, "asize-of-basic-func", + make_function_from_c((void*)asize_of_basic).offset); + set_fixed_symbol(FIX_SYM_COPY_BASIC_FUNC, "asize-of-basic-func", // typo in name here again. + make_function_from_c((void*)copy_basic, true).offset); + set_fixed_symbol(FIX_SYM_DELETE_BASIC, "delete-basic", + make_function_from_c((void*)delete_basic).offset); + set_fixed_symbol(FIX_SYM_GLOBAL_HEAP, "global", kglobalheap.offset); + set_fixed_symbol(FIX_SYM_DEBUG, "debug", kdebugheap.offset); + set_fixed_symbol(FIX_SYM_STATIC, "static", s7.offset + FIX_SYM_STATIC); + set_fixed_symbol(FIX_SYM_LOADING_LEVEL, "loading-level", kglobalheap.offset); + set_fixed_symbol(FIX_SYM_LOADING_PACKAGE, "loading-package", kglobalheap.offset); + set_fixed_symbol(FIX_SYM_PROCESS_LEVEL_HEAP, "process-level-heap", kglobalheap.offset); + set_fixed_symbol(FIX_SYM_STACK, "stack", s7.offset + FIX_SYM_STACK); + set_fixed_symbol(FIX_SYM_SCRATCH, "scratch", s7.offset + FIX_SYM_SCRATCH); + set_fixed_symbol(FIX_SYM_SCRATCH_TOP, "*scratch-top*", 0x70000000); + set_fixed_symbol(FIX_SYM_LEVEL, "level", 0); + set_fixed_symbol(FIX_SYM_ART_GROUP, "art-group", 0); + set_fixed_symbol(FIX_SYM_TEXTURE_PAGE_DIR, "texture-page-dir", 0); + set_fixed_symbol(FIX_SYM_TEXTURE_PAGE, "texture-page", 0); + + set_fixed_symbol(FIX_SYM_SOUND, "sound", 0); + set_fixed_symbol(FIX_SYM_DGO, "dgo", 0); + set_fixed_symbol(FIX_SYM_TOP_LEVEL, "top-level", u32_in_fixed_sym(FIX_SYM_NOTHING_FUNC)); + set_fixed_symbol(FIX_SYM_QUOTE, "quote", s7.offset + FIX_SYM_QUOTE); + set_fixed_symbol(FIX_SYM_LISTENER_LINK_BLOCK, "*listener-link-block*", 0); + set_fixed_symbol(FIX_SYM_LISTENER_FUNCTION, "*listener-function*", 0x0); + set_fixed_symbol(FIX_SYM_STACK_TOP, "*stack-top*", 0x0); + set_fixed_symbol(FIX_SYM_STACK_BASE, "*stack-base*", 0x0); + set_fixed_symbol(FIX_SYM_STACK_SIZE, "*stack-size*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_FUNCTION, "*kernel-function*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_PACKAGES, "*kernel-packages*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_BOOT_MESSAGE, "*kernel-boot-message*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_BOOT_MODE, "*kernel-boot-mode*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_BOOT_LEVEL, "*kernel-boot-level*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_BOOT_ART_GROUP, "*kernel-boot-art-group*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_DEBUG, "*kernel-debug*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_VERSION, "*kernel-version*", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_DISPATCHER, "kernel-dispatcher", 0x0); + set_fixed_symbol(FIX_SYM_SYNC_DISPATCHER, "sync-dispatcher", 0x0); + set_fixed_symbol(FIX_SYM_PRINT_COLLUMN, "*print-collumn*", 0x0); + set_fixed_symbol(FIX_SYM_DEBUG_SEGMENT, "*debug-segment*", 0x0); + set_fixed_symbol(FIX_SYM_ENABLE_METHOD_SET, "*enable-method-set*", 0x0); + set_fixed_symbol(FIX_SYM_SQL_RESULT, "*sql-result*", 0x0); + set_fixed_symbol(FIX_SYM_COLLAPSE_QUOTE, "*collapse-quote*", 0x0); + set_fixed_symbol(FIX_SYM_LEVEL_TYPE_LIST, "*level-type-list*", 0x0); + set_fixed_symbol(FIX_SYM_DECI_COUNT, "*deci-count*", 0x0); + set_fixed_symbol(FIX_SYM_USER, "*user*", 0x0); + set_fixed_symbol(FIX_SYM_VIDEO_MODE, "*video-mode*", 0x0); + set_fixed_symbol(FIX_SYM_BOOT_VIDEO_MODE, "*boot-video-mode*", 0x0); + set_fixed_symbol(FIX_SYM_BOOT, "boot", 0x0); + set_fixed_symbol(FIX_SYM_DEMO, "demo", 0x0); + set_fixed_symbol(FIX_SYM_DEMO_SHARED, "demo-shared", 0x0); + set_fixed_symbol(FIX_SYM_PREVIEW, "preview", 0x0); + set_fixed_symbol(FIX_SYM_KIOSK, "kiosk", 0x0); + set_fixed_symbol(FIX_SYM_PLAY_BOOT, "play-boot", 0x0); + set_fixed_symbol(FIX_SYM_SIN, "sin", 0x0); + set_fixed_symbol(FIX_SYM_COS, "cos", 0x0); + set_fixed_symbol(FIX_SYM_PUT_DISPLAY_ENV, "put-display-env", 0x0); + set_fixed_symbol(FIX_SYM_SYNCV, "syncv", 0x0); + set_fixed_symbol(FIX_SYM_SYNC_PATH, "sync-path", 0x0); + + set_fixed_symbol(FIX_SYM_RESET_PATH, "reset-path", 0x0); + set_fixed_symbol(FIX_SYM_RESET_GRAPH, "reset-graph", 0x0); + set_fixed_symbol(FIX_SYM_DMA_SYNC, "dma-sync", 0x0); + set_fixed_symbol(FIX_SYM_GS_PUT_IMR, "gs-put-imr", 0x0); + set_fixed_symbol(FIX_SYM_GS_GET_IMR, "gs-get-imr", 0x0); + set_fixed_symbol(FIX_SYM_GS_STORE_IMAGE, "gs-store-image", 0x0); + set_fixed_symbol(FIX_SYM_FLUSH_CACHE, "flush-cache", 0x0); + set_fixed_symbol(FIX_SYM_CPAD_OPEN, "cpad-open", 0x0); + set_fixed_symbol(FIX_SYM_CPAD_GET_DATA, "cpad-get-data", 0x0); + set_fixed_symbol(FIX_SYM_MOUSE_GET_DATA, "mouse-get-data", 0x0); + set_fixed_symbol(FIX_SYM_KEYBD_GET_DATA, "keybd-get-data", 0x0); + set_fixed_symbol(FIX_SYM_INSTALL_HANDLER, "install-handler", 0x0); + set_fixed_symbol(FIX_SYM_INSTALL_DEBUG_HANDLER, "install-debug-handler", 0x0); + set_fixed_symbol(FIX_SYM_FILE_STREAM_OPEN, "file-stream-open", 0x0); + set_fixed_symbol(FIX_SYM_FILE_STREAM_CLOSE, "file-stream-close", 0x0); + set_fixed_symbol(FIX_SYM_FILE_STREAM_LENGTH, "file-stream-length", 0x0); + set_fixed_symbol(FIX_SYM_FILE_STREAM_SEEK, "file-stream-seek", 0x0); + set_fixed_symbol(FIX_SYM_FILE_STREAM_READ, "file-stream-read", 0x0); + set_fixed_symbol(FIX_SYM_FILE_STREAM_WRITE, "file-stream-write", 0x0); + // set_fixed_symbol(FIX_SYM_FILE_STREAM_WRITE, "file-stream-write", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_LANGUAGE, "scf-get-language", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_TIME, "scf-get-time", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_ASPECT, "scf-get-aspect", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_VOLUME, "scf-get-volume", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_TERRITORY, "scf-get-territory", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_TIMEOUT, "scf-get-timeout", 0x0); + set_fixed_symbol(FIX_SYM_SCF_GET_INACTIVE_TIMEOUT, "scf-get-inactive-timeout", 0x0); + set_fixed_symbol(FIX_SYM_DMA_TO_IOP, "dma-to-iop", 0x0); + set_fixed_symbol(FIX_SYM_KERNEL_SHUTDOWN, "kernel-shutdown", 0x0); + set_fixed_symbol(FIX_SYM_AYBABTU, "aybabtu", 0x0); + set_fixed_symbol(FIX_SYM_STRING_TO_SYMBOL, "string->symbol", 0x0); + set_fixed_symbol(FIX_SYM_SYMBOL_TO_STRING, "symbol->string", 0x0); + set_fixed_symbol(FIX_SYM_PRINT, "print", 0x0); + set_fixed_symbol(FIX_SYM_INSPECT, "inspect", 0x0); + set_fixed_symbol(FIX_SYM_LOAD, "load", 0x0); + set_fixed_symbol(FIX_SYM_LOADB, "loadb", 0x0); + set_fixed_symbol(FIX_SYM_LOADO, "loado", 0x0); + set_fixed_symbol(FIX_SYM_UNLOAD, "unload", 0x0); + set_fixed_symbol(FIX_SYM_FORMAT, "_format", 0x0); + set_fixed_symbol(FIX_SYM_MALLOC, "malloc", 0x0); + set_fixed_symbol(FIX_SYM_KMALLOC, "kmalloc", 0x0); + set_fixed_symbol(FIX_SYM_KMEMOPEN, "kmemopen", 0x0); + set_fixed_symbol(FIX_SYM_KMEMCLOSE, "kmemclose", 0x0); + set_fixed_symbol(FIX_SYM_NEW_DYNAMIC_STRUCTURE, "new-dynamic-structure", 0x0); + set_fixed_symbol(FIX_SYM_METHOD_SET, "method-set!", 0x0); + set_fixed_symbol(FIX_SYM_LINK, "link", 0x0); + set_fixed_symbol(FIX_SYM_LINK_BUSY, "link-busy?", 0x0); + set_fixed_symbol(FIX_SYM_LINK_RESET, "link-reset", 0x0); + set_fixed_symbol(FIX_SYM_LINK_BEGIN, "link-begin", 0x0); + set_fixed_symbol(FIX_SYM_LINK_RESUME, "link-resume", 0x0); + set_fixed_symbol(FIX_SYM_DGO_LOAD, "dgo-load", 0x0); + set_fixed_symbol(FIX_SYM_SQL_QUERY, "sql-query", 0x0); + set_fixed_symbol(FIX_SYM_MC_RUN, "mc-run", 0x0); + set_fixed_symbol(FIX_SYM_MC_FORMAT, "mc-format", 0x0); + set_fixed_symbol(FIX_SYM_MC_UNFORMAT, "mc-unformat", 0x0); + set_fixed_symbol(FIX_SYM_MC_CREATE_FILE, "mc-create-file", 0x0); + set_fixed_symbol(FIX_SYM_MC_SAVE, "mc-save", 0x0); + set_fixed_symbol(FIX_SYM_MC_LOAD, "mc-load", 0x0); + set_fixed_symbol(FIX_SYM_MC_CHECK_RESULT, "mc-check-result", 0x0); + set_fixed_symbol(FIX_SYM_MC_GET_SLOT_INFO, "mc-get-slot-info", 0x0); + set_fixed_symbol(FIX_SYM_MC_MAKEFILE, "mc-makefile", 0x0); + set_fixed_symbol(FIX_SYM_KSET_LANGUAGE, "kset-language", 0x0); + set_fixed_symbol(FIX_SYM_RPC_CALL, "rpc-call", 0x0); + set_fixed_symbol(FIX_SYM_RPC_BUSY, "rpc-busy?", 0x0); + set_fixed_symbol(FIX_SYM_TEST_LOAD_DGO_C, "test-load-dgo-c", 0x0); + set_fixed_symbol(FIX_SYM_SYMLINK2, "symlink2", 0x0); + set_fixed_symbol(FIX_SYM_SYMLINK3, "symlink3", 0x0); + set_fixed_symbol(FIX_SYM_ULTIMATE_MEMCPY, "ultimate-memcpy", 0x0); + set_fixed_symbol(FIX_SYM_PLAY, "play", 0x0); + + set_fixed_symbol(FIX_SYM_SYMBOL_STRING, "*symbol-string*", SymbolString.offset); + set_fixed_symbol(FIX_SYM_KERNEL_SYMBOL_WARNINGS, "*kernel-symbol-warnings*", + s7.offset + FIX_SYM_TRUE); + set_fixed_symbol(FIX_SYM_NETWORK_BOOTSTRAP, "network-bootstrap", 0); + + auto new_illegal_func = make_function_from_c((void*)new_illegal); + auto delete_illegal_func = make_function_from_c((void*)delete_illegal); + auto print_object_func = make_function_from_c((void*)print_object); + auto inspect_object_func = make_function_from_c((void*)inspect_object); + + set_fixed_type(FIX_SYM_OBJECT_TYPE, "object", get_fixed_type_symbol(FIX_SYM_OBJECT_TYPE), + pack_type_flag(9, 0, 4), print_object_func.offset, inspect_object_func.offset); + auto object_type = Ptr(u32_in_fixed_sym(FIX_SYM_OBJECT_TYPE)); + object_type->new_method = Ptr(u32_in_fixed_sym(FIX_SYM_NOTHING_FUNC)); // new for jak 2 + object_type->delete_method = delete_illegal_func; + object_type->asize_of_method = + Ptr(u32_in_fixed_sym(FIX_SYM_ZERO_FUNC)); // changed to zero! + object_type->length_method = + Ptr(u32_in_fixed_sym(FIX_SYM_ZERO_FUNC)); // changed to zero! + object_type->copy_method = make_function_from_c((void*)copy_fixed); + + auto structure_type = + set_fixed_type(FIX_SYM_STRUCTURE, "structure", get_fixed_type_symbol(FIX_SYM_OBJECT_TYPE), + pack_type_flag(9, 0, 4), make_function_from_c((void*)print_structure).offset, + make_function_from_c((void*)inspect_structure).offset); + structure_type->new_method = make_function_from_c((void*)new_structure); + structure_type->delete_method = make_function_from_c((void*)delete_structure); + + auto basic_type = + set_fixed_type(FIX_SYM_BASIC, "basic", get_fixed_type_symbol(FIX_SYM_STRUCTURE), + pack_type_flag(9, 0, 4), make_function_from_c((void*)print_basic).offset, + make_function_from_c((void*)inspect_basic).offset); + basic_type->new_method = make_function_from_c((void*)new_basic, true); + basic_type->delete_method = Ptr(u32_in_fixed_sym(FIX_SYM_DELETE_BASIC)); + basic_type->asize_of_method = Ptr(u32_in_fixed_sym(FIX_SYM_ASIZE_OF_BASIC_FUNC)); + basic_type->copy_method = Ptr(u32_in_fixed_sym(FIX_SYM_COPY_BASIC_FUNC)); + + set_fixed_type(FIX_SYM_SYMBOL_TYPE, "symbol", get_fixed_type_symbol(FIX_SYM_OBJECT_TYPE), + pack_type_flag(9, 0, 4), make_function_from_c((void*)print_symbol).offset, + make_function_from_c((void*)inspect_symbol).offset); + auto sym_type = Ptr(u32_in_fixed_sym(FIX_SYM_SYMBOL_TYPE)); + sym_type->new_method = new_illegal_func; + sym_type->delete_method = delete_illegal_func; + + set_fixed_type(FIX_SYM_TYPE_TYPE, "type", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 0x38), make_function_from_c((void*)print_type).offset, + make_function_from_c((void*)inspect_type).offset); + auto type_type = Ptr(u32_in_fixed_sym(FIX_SYM_TYPE_TYPE)); + type_type->new_method = make_function_from_c((void*)new_type); + type_type->delete_method = delete_illegal_func; + + set_fixed_type(FIX_SYM_STRING_TYPE, "string", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 8), make_function_from_c((void*)print_string).offset, + make_function_from_c((void*)inspect_string).offset); + + set_fixed_type(FIX_SYM_FUNCTION_TYPE, "function", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 4), make_function_from_c((void*)print_function).offset, 0); + auto function_type = Ptr(u32_in_fixed_sym(FIX_SYM_FUNCTION_TYPE)); + function_type->new_method = new_illegal_func; + function_type->delete_method = delete_illegal_func; + + set_fixed_type(FIX_SYM_VU_FUNCTION, "vu-function", get_fixed_type_symbol(FIX_SYM_STRUCTURE), + pack_type_flag(9, 0, 0x10), make_function_from_c((void*)print_vu_function).offset, + make_function_from_c((void*)inspect_vu_function).offset); + Ptr(u32_in_fixed_sym(FIX_SYM_VU_FUNCTION))->delete_method = delete_illegal_func; + + set_fixed_type(FIX_SYM_LINK_BLOCK, "link-block", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 0xc), 0, + make_function_from_c((void*)inspect_link_block).offset); + auto link_block_type = Ptr(u32_in_fixed_sym(FIX_SYM_LINK_BLOCK)); + link_block_type->new_method = new_illegal_func; + link_block_type->delete_method = delete_illegal_func; + + set_fixed_type(FIX_SYM_HEAP, "kheap", get_fixed_type_symbol(FIX_SYM_STRUCTURE), + pack_type_flag(9, 0, 0x10), 0, make_function_from_c((void*)inspect_kheap).offset); + + set_fixed_type(FIX_SYM_ARRAY, "array", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 0x10), 0, 0); + + set_fixed_type(FIX_SYM_PAIR_TYPE, "pair", get_fixed_type_symbol(FIX_SYM_OBJECT_TYPE), + pack_type_flag(9, 0, 8), make_function_from_c((void*)print_pair).offset, + make_function_from_c((void*)inspect_pair).offset); + Ptr(u32_in_fixed_sym(FIX_SYM_PAIR_TYPE))->new_method = + make_function_from_c((void*)new_pair); + Ptr(u32_in_fixed_sym(FIX_SYM_PAIR_TYPE))->delete_method = + make_function_from_c((void*)delete_pair); + + set_fixed_type(FIX_SYM_PROCESS_TREE, "process-tree", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(0xe, 0, 0x24), 0, 0); + set_fixed_type(FIX_SYM_PROCESS_TYPE, "process", get_fixed_type_symbol(FIX_SYM_PROCESS_TREE), + pack_type_flag(0xe, 0, 0x80), 0, 0); + set_fixed_type(FIX_SYM_THREAD, "thread", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(0xc, 0, 0x28), 0, 0); + set_fixed_type(FIX_SYM_CONNECTABLE, "connectable", get_fixed_type_symbol(FIX_SYM_STRUCTURE), + pack_type_flag(9, 0, 0x10), 0, 0); + set_fixed_type(FIX_SYM_STACK_FRAME, "stack-frame", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 0xc), 0, 0); + set_fixed_type(FIX_SYM_FILE_STREAM, "file-stream", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 0x14), 0, 0); + set_fixed_type(FIX_SYM_POINTER, "pointer", get_fixed_type_symbol(FIX_SYM_OBJECT_TYPE), + pack_type_flag(9, 0, 4), 0, 0); + Ptr(u32_in_fixed_sym(FIX_SYM_POINTER))->new_method = new_illegal_func; + + set_fixed_type(FIX_SYM_NUMBER, "number", get_fixed_type_symbol(FIX_SYM_OBJECT_TYPE), + pack_type_flag(9, 0, 8), make_function_from_c((void*)print_integer).offset, + make_function_from_c((void*)inspect_integer).offset); + Ptr(u32_in_fixed_sym(FIX_SYM_NUMBER))->new_method = new_illegal_func; + + set_fixed_type(FIX_SYM_FLOAT, "float", get_fixed_type_symbol(FIX_SYM_NUMBER), + pack_type_flag(9, 0, 4), make_function_from_c((void*)print_float).offset, + make_function_from_c((void*)inspect_float).offset); + + set_fixed_type(FIX_SYM_INTEGER, "integer", get_fixed_type_symbol(FIX_SYM_NUMBER), + pack_type_flag(9, 0, 8), 0, 0); + + set_fixed_type(FIX_SYM_BINTEGER, "binteger", get_fixed_type_symbol(FIX_SYM_INTEGER), + pack_type_flag(9, 0, 8), make_function_from_c((void*)print_binteger).offset, + make_function_from_c((void*)inspect_binteger).offset); + + set_fixed_type(FIX_SYM_SINTEGER, "sinteger", get_fixed_type_symbol(FIX_SYM_INTEGER), + pack_type_flag(9, 0, 8), 0, 0); + set_fixed_type(FIX_SYM_INT8, "int8", get_fixed_type_symbol(FIX_SYM_SINTEGER), + pack_type_flag(9, 0, 1), 0, 0); + set_fixed_type(FIX_SYM_INT16, "int16", get_fixed_type_symbol(FIX_SYM_SINTEGER), + pack_type_flag(9, 0, 2), 0, 0); + set_fixed_type(FIX_SYM_INT32, "int32", get_fixed_type_symbol(FIX_SYM_SINTEGER), + pack_type_flag(9, 0, 4), 0, 0); + set_fixed_type(FIX_SYM_INT64, "int64", get_fixed_type_symbol(FIX_SYM_SINTEGER), + pack_type_flag(9, 0, 8), 0, 0); + set_fixed_type(FIX_SYM_INT128, "int128", get_fixed_type_symbol(FIX_SYM_SINTEGER), + pack_type_flag(9, 0, 16), 0, 0); + + set_fixed_type(FIX_SYM_UINTEGER, "uinteger", get_fixed_type_symbol(FIX_SYM_INTEGER), + pack_type_flag(9, 0, 8), 0, 0); + set_fixed_type(FIX_SYM_UINT8, "uint8", get_fixed_type_symbol(FIX_SYM_UINTEGER), + pack_type_flag(9, 0, 1), 0, 0); + set_fixed_type(FIX_SYM_UINT16, "uint16", get_fixed_type_symbol(FIX_SYM_UINTEGER), + pack_type_flag(9, 0, 2), 0, 0); + set_fixed_type(FIX_SYM_UINT32, "uint32", get_fixed_type_symbol(FIX_SYM_UINTEGER), + pack_type_flag(9, 0, 4), 0, 0); + set_fixed_type(FIX_SYM_UINT64, "uint64", get_fixed_type_symbol(FIX_SYM_UINTEGER), + pack_type_flag(9, 0, 8), 0, 0); + set_fixed_type(FIX_SYM_UINT128, "uint128", get_fixed_type_symbol(FIX_SYM_UINTEGER), + pack_type_flag(9, 0, 16), 0, 0); + + Ptr(u32_in_fixed_sym(FIX_SYM_OBJECT_TYPE))->new_method = + make_function_from_c((void*)alloc_heap_object, true); + + make_function_symbol_from_c("string->symbol", (void*)intern); + make_function_symbol_from_c("symbol->string", (void*)symbol_to_string_from_c); + make_function_symbol_from_c("print", (void*)sprint); + make_function_symbol_from_c("inspect", (void*)inspect_object); + make_function_symbol_from_c("load", (void*)load); + make_function_symbol_from_c("loadb", (void*)loadb); + make_function_symbol_from_c("loado", (void*)loado); + make_function_symbol_from_c("unload", (void*)unload); + make_stack_arg_function_symbol_from_c("_format", (void*)format_impl_jak3); + make_function_symbol_from_c("malloc", (void*)alloc_heap_memory); + make_function_symbol_from_c("kmalloc", (void*)goal_malloc); + make_function_symbol_from_c("kmemopen", (void*)kmemopen); + make_function_symbol_from_c("kmemclose", (void*)kmemclose); + make_function_symbol_from_c("new-dynamic-structure", (void*)new_dynamic_structure); + make_function_symbol_from_c("method-set!", (void*)method_set); + make_stack_arg_function_symbol_from_c("link", (void*)link_and_exec_wrapper); + make_function_symbol_from_c("link-busy?", (void*)link_busy); + make_function_symbol_from_c("link-reset", (void*)link_reset); + make_function_symbol_from_c("dgo-load", (void*)load_and_link_dgo); + make_raw_function_symbol_from_c("ultimate-memcpy", 0); + make_raw_function_symbol_from_c("symlink2", 0); + make_raw_function_symbol_from_c("symlink3", 0); + make_stack_arg_function_symbol_from_c("link-begin", (void*)link_begin); + make_function_symbol_from_c("link-resume", (void*)link_resume); + make_function_symbol_from_c("sql-query", (void*)sql_query_sync); + make_function_symbol_from_c("mc-run", (void*)MC_run); + make_function_symbol_from_c("mc-format", (void*)MC_format); + make_function_symbol_from_c("mc-unformat", (void*)MC_unformat); + make_function_symbol_from_c("mc-create-file", (void*)MC_createfile); + make_function_symbol_from_c("mc-save", (void*)MC_save); + make_function_symbol_from_c("mc-load", (void*)MC_load); + make_function_symbol_from_c("mc-check-result", (void*)MC_check_result); + make_function_symbol_from_c("mc-get-slot-info", (void*)MC_get_status); + make_function_symbol_from_c("mc-makefile", (void*)MC_makefile); + make_function_symbol_from_c("kset-language", (void*)MC_set_language); + + auto ds_symbol = intern_from_c(-1, 0, "*debug-segment*"); + if (DebugSegment) { + ds_symbol->value() = (s7 + FIX_SYM_TRUE).offset; + } else { + ds_symbol->value() = (s7 + FIX_SYM_FALSE).offset; + } + + auto method_set_symbol = intern_from_c(-1, 0, "*enable-method-set*"); + EnableMethodSet = method_set_symbol.cast() - 1; + method_set_symbol->value() = 0; + + KernelDebug = intern_from_c(-1, 0, "*kernel-debug*").cast() - 1; + *KernelDebug = 0; + + intern_from_c(-1, 0, "*boot-video-mode*")->value() = 0; + intern_from_c(-1, 0, "*video-mode*")->value() = 0; + + SqlResult = intern_from_c(-1, 0, "*sql-result*"); + SqlResult->value() = s7.offset; + + CollapseQuote = intern_from_c(-1, 0, "*collapse-quote*"); + CollapseQuote->value() = s7.offset + FIX_SYM_TRUE; + + LevelTypeList = intern_from_c(-1, 0, "*level-type-list*"); + + if (MasterUseKernel) { + *EnableMethodSet = *EnableMethodSet + 1; + load_and_link_dgo_from_c("kernel", kglobalheap, + LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, + 0x400000, true); + *EnableMethodSet = *EnableMethodSet + -1; + + auto kernel_version = intern_from_c(-1, 0, "*kernel-version*")->value(); + if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) { + lg::error( + "Kernel version mismatch! Compiled C kernel version is {}.{} but" + " the goal kernel is {}.{}", + KERNEL_VERSION_MAJOR, KERNEL_VERSION_MINOR, kernel_version >> 0x13, + (kernel_version >> 3) & 0xffff); + return -1; + } else { + lg::info("Got correct kernel version {}.{}", kernel_version >> 0x13, + (kernel_version >> 3) & 0xffff); + } + } + + protoBlock.deci2count = intern_from_c(-1, 0, "*deci-count*").cast() - 1; + InitListener(); + InitMachineScheme(); + kmemclose(); + return 0; +} + +u64 load(u32 /*file_name_in*/, u32 /*heap_in*/) { + ASSERT(false); + return 0; +} + +u64 loadb(u32 /*file_name_in*/, u32 /*heap_in*/, u32 /*param3*/) { + ASSERT(false); + return 0; +} + +u64 loadc(const char* /*file_name*/, kheapinfo* /*heap*/, u32 /*flags*/) { + ASSERT(false); + return 0; +} + +u64 loado(u32 file_name_in, u32 heap_in) { + char loadName[272]; + Ptr file_name(file_name_in); + Ptr heap(heap_in); + printf("****** CALL TO loado(%s) ******\n", file_name->data()); + kstrcpy(loadName, MakeFileName(DATA_FILE_TYPE, file_name->data(), 0)); + s32 returnValue = load_and_link(file_name->data(), loadName, heap.c(), LINK_FLAG_PRINT_LOGIN); + + if (returnValue < 0) { + return s7.offset; + } else { + return returnValue; + } +} + +/*! + * "Unload". Doesn't free memory, just informs listener we unloaded. + */ +u64 unload(u32 name) { + output_unload(Ptr(name)->data()); + return 0; +} + +s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags) { + (void)filename; + s32 sz; + auto rv = FileLoad(decode_name, make_ptr(heap), Ptr(0), KMALLOC_ALIGN_64, &sz); + if (((s32)rv.offset) > -1) { + return (s32)link_and_exec(rv, decode_name, sz, make_ptr(heap), flags, false).offset; + } + return (s32)rv.offset; +} + +} // namespace jak3 diff --git a/game/kernel/jak3/kscheme.h b/game/kernel/jak3/kscheme.h new file mode 100644 index 0000000000..9dc847290a --- /dev/null +++ b/game/kernel/jak3/kscheme.h @@ -0,0 +1,74 @@ +#pragma once + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/Symbol4.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kscheme.h" + +namespace jak3 { +void kscheme_init_globals(); +constexpr s32 SYMBOL_OFFSET = 1; +extern Ptr SymbolString; +extern bool DebugSymbols; + +/*! + * GOAL Type + */ +struct Type { + Ptr>> symbol; //! The type's symbol 0x0 + Ptr parent; //! The type's parent 0x4 + u16 allocated_size; //! The type's size in memory 0x8 + u16 padded_size; //! The type's size, when padded? 0xa + + u16 heap_base; //! relative location of heap 0xc + u16 num_methods; //! allocated-length field 0xe - 0xf + + Ptr new_method; // 16 0 + Ptr delete_method; // 20 1 + Ptr print_method; // 24 2 + Ptr inspect_method; // 28 3 + Ptr length_method; // 32 4 + Ptr asize_of_method; // 36 5 + Ptr copy_method; // 40 6 + Ptr relocate_method; // 44 7 + Ptr memusage_method; // 48 8 + + Ptr& get_method(u32 i) { + Ptr* f = &new_method; + return f[i]; + } +}; + +s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags); +Ptr> intern_from_c(int sym_id, int flags, const char* name); +u64 load(u32 /*file_name_in*/, u32 /*heap_in*/); +u64 loadb(u32 /*file_name_in*/, u32 /*heap_in*/, u32 /*param3*/); +u64 loadc(const char* /*file_name*/, kheapinfo* /*heap*/, u32 /*flags*/); +u64 loado(u32 file_name_in, u32 heap_in); +u64 unload(u32 name); +Ptr make_function_symbol_from_c(const char* name, void* f); +Ptr make_stack_arg_function_symbol_from_c(const char* name, void* f); +u64 print_object(u32 obj); +u64 inspect_object(u32 obj); +Ptr> find_symbol_from_c(uint16_t sym_id, const char* name); +u64 make_string_from_c(const char* c_str); +u64 call_method_of_type(u32 arg, Ptr type, u32 method_id); +u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr); +u64 call_goal_function_by_name(const char* name); +Ptr intern_type_from_c(int a, int b, const char* name, u64 methods); +int InitHeapAndSymbol(); +template +Ptr> sym_to_string_ptr(Ptr> in) { + return Ptr>(SymbolString.offset + in.offset - s7.offset); +} +template +Ptr sym_to_string(Ptr> in) { + return *sym_to_string_ptr(in); +} + +template +const char* sym_to_cstring(Ptr> in) { + return sym_to_string(in)->data(); +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/ksocket.cpp b/game/kernel/jak3/ksocket.cpp new file mode 100644 index 0000000000..73d2f164b7 --- /dev/null +++ b/game/kernel/jak3/ksocket.cpp @@ -0,0 +1,3 @@ + + +#include "ksocket.h" diff --git a/game/kernel/jak3/ksocket.h b/game/kernel/jak3/ksocket.h new file mode 100644 index 0000000000..ae16523cd1 --- /dev/null +++ b/game/kernel/jak3/ksocket.h @@ -0,0 +1,3 @@ +#pragma once + +namespace jak3 {} \ No newline at end of file diff --git a/game/kernel/jak3/ksound.cpp b/game/kernel/jak3/ksound.cpp new file mode 100644 index 0000000000..ada5493fe7 --- /dev/null +++ b/game/kernel/jak3/ksound.cpp @@ -0,0 +1,15 @@ +#include "ksound.h" + +#include "game/kernel/common/kdgo.h" +#include "game/kernel/jak3/kscheme.h" + +namespace jak3 { +/*! + * Set up some functions which are somewhat related to sound. + */ +void InitSoundScheme() { + make_function_symbol_from_c("rpc-busy?", (void*)RpcBusy); + make_function_symbol_from_c("test-load-dgo-c", (void*)LoadDGOTest); + make_stack_arg_function_symbol_from_c("rpc-call", (void*)RpcCall_wrapper); +} +} // namespace jak3 \ No newline at end of file diff --git a/game/kernel/jak3/ksound.h b/game/kernel/jak3/ksound.h new file mode 100644 index 0000000000..0030fc17e7 --- /dev/null +++ b/game/kernel/jak3/ksound.h @@ -0,0 +1,5 @@ +#pragma once + +namespace jak3 { +void InitSoundScheme(); +} \ No newline at end of file diff --git a/game/kernel/todo.txt b/game/kernel/todo.txt deleted file mode 100644 index d070d6ba5a..0000000000 --- a/game/kernel/todo.txt +++ /dev/null @@ -1,29 +0,0 @@ -kboot ---------- -usleep in KernelCheckAndDispatch - -kmachine ---------- -rewrite InitParms to not use std::string -InitVideo -InitMachine -CPadGetData -InstallHandler -InstallDebugHandler -dma_to_iop -DecodeTime -PutDisplayEnv - -kscheme ----------- -remove the test function -add memory card stuff -read_clock_code - -klink -------- -v2 support - -kmemcard ---------- -all of it, basically. \ No newline at end of file diff --git a/game/main.cpp b/game/main.cpp index 4dad7c5390..195bf16e5b 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -95,7 +95,6 @@ int main(int argc, char** argv) { bool verbose_logging = false; bool disable_avx2 = false; bool disable_display = false; - bool enable_debug_vm = false; bool enable_profiling = false; std::string gpu_test = ""; std::string gpu_test_out_path = ""; @@ -109,9 +108,8 @@ int main(int argc, char** argv) { app.add_flag( "--port", port_number, "Specify port number for listener connection (default is 8112 for Jak 1 and 8113 for Jak 2)"); - app.add_flag("--no-avx2", verbose_logging, "Disable AVX2 for testing"); + app.add_flag("--no-avx2", disable_avx2, "Disable AVX2 for testing"); app.add_flag("--no-display", disable_display, "Disable video display"); - app.add_flag("--vm", enable_debug_vm, "Enable debug PS2 VM (defaulted to off)"); app.add_flag("--profile", enable_profiling, "Enables profiling immediately from startup"); app.add_option("--gpu-test", gpu_test, "Tests for minimum graphics requirements. Valid Options are: [opengl]"); @@ -146,7 +144,6 @@ int main(int argc, char** argv) { // Create struct with all non-kmachine handled args to pass to the runtime GameLaunchOptions game_options; - game_options.disable_debug_vm = !enable_debug_vm; game_options.disable_display = disable_display; game_options.game_version = game_name_to_version(game_name); game_options.server_port = diff --git a/game/overlord/jak2/vag.cpp b/game/overlord/jak2/vag.cpp index 11396540d0..6bc1532f6f 100644 --- a/game/overlord/jak2/vag.cpp +++ b/game/overlord/jak2/vag.cpp @@ -189,7 +189,7 @@ VagCmd* SmartAllocVagCmd(VagCmd* cmd) { return selected; } -void TerminateVAG(VagCmd* cmd, int param_2) { +void TerminateVAG(VagCmd* cmd, int /*param_2*/) { VagStrListNode vag_node; LfoListNode lfo_node; // undefined4 auStack32 [2]; @@ -386,7 +386,7 @@ void RestartVag(VagCmd* param_1, int param_2, int /*param_3*/) { //} } -void SetVAGVol(VagCmd* cmd, int param_2) { +void SetVAGVol(VagCmd* cmd, int /*param_2*/) { VagCmd* stereo_cmd; u32 lvol, rvol; diff --git a/game/runtime.cpp b/game/runtime.cpp index 88190e7707..eab215e7da 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -43,11 +43,17 @@ #include "game/kernel/common/kprint.h" #include "game/kernel/common/kscheme.h" #include "game/kernel/jak1/kboot.h" +#include "game/kernel/jak1/kdgo.h" #include "game/kernel/jak1/klisten.h" #include "game/kernel/jak1/kscheme.h" #include "game/kernel/jak2/kboot.h" +#include "game/kernel/jak2/kdgo.h" #include "game/kernel/jak2/klisten.h" #include "game/kernel/jak2/kscheme.h" +#include "game/kernel/jak3/kboot.h" +#include "game/kernel/jak3/kdgo.h" +#include "game/kernel/jak3/klisten.h" +#include "game/kernel/jak3/kscheme.h" #include "game/overlord/common/fake_iso.h" #include "game/overlord/common/iso.h" #include "game/overlord/common/sbank.h" @@ -74,8 +80,6 @@ #include "game/overlord/jak2/vag.h" #include "game/system/Deci2Server.h" #include "game/system/iop_thread.h" -#include "game/system/vm/dmac.h" -#include "game/system/vm/vm.h" #include "sce/deci2.h" #include "sce/iop.h" #include "sce/libcdvd_ee.h" @@ -183,21 +187,28 @@ void ee_runner(SystemThreadInterface& iface) { fileio_init_globals(); jak1::kboot_init_globals(); jak2::kboot_init_globals(); + jak3::kboot_init_globals(); kboot_init_globals_common(); kdgo_init_globals(); + jak1::kdgo_init_globals(); + jak2::kdgo_init_globals(); + jak3::kdgo_init_globals(); + kdsnetm_init_globals_common(); klink_init_globals(); kmachine_init_globals_common(); jak1::kscheme_init_globals(); jak2::kscheme_init_globals(); + jak3::kscheme_init_globals(); kscheme_init_globals_common(); kmalloc_init_globals_common(); klisten_init_globals(); jak1::klisten_init_globals(); jak2::klisten_init_globals(); + jak3::klisten_init_globals(); jak2::vag_init_globals(); @@ -216,6 +227,8 @@ void ee_runner(SystemThreadInterface& iface) { case GameVersion::Jak2: jak2::goal_main(g_argc, g_argv); break; + case GameVersion::Jak3: + jak3::goal_main(g_argc, g_argv); default: ASSERT_MSG(false, "Unsupported game version"); } @@ -309,6 +322,7 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) { jak1::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete); break; case GameVersion::Jak2: + case GameVersion::Jak3: // TODO: jak3 using jak2's overlord. jak2::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete); break; default: @@ -349,32 +363,6 @@ void null_runner(SystemThreadInterface& iface) { iface.initialization_complete(); } -/*! - * SystemThread function for running the PS2 DMA controller. - * This does not actually emulate the DMAC, it only fakes its existence enough that we can debug - * the DMA packets the original game sends. The port will replace all DMAC code. - */ -void dmac_runner(SystemThreadInterface& iface) { - VM::subscribe_component(); - - VM::dmac_init_globals(); - - iface.initialization_complete(); - - while (!iface.get_want_exit() && !VM::vm_want_exit()) { - // for (int i = 0; i < 10; ++i) { - // if (VM::dmac_ch[i]->chcr.str) { - // // lg::info("DMA detected on channel {}, clearing", i); - // VM::dmac_ch[i]->chcr.str = 0; - // } - // } - // avoid running the DMAC on full blast (this does not sync to its clockrate) - std::this_thread::sleep_for(std::chrono::microseconds(50000)); - } - - VM::unsubscribe_component(); -} - /*! * Main function to launch the runtime. * GOAL kernel arguments are currently ignored. @@ -386,7 +374,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c g_main_thread_id = std::this_thread::get_id(); bool enable_display = !game_options.disable_display; - VM::use = !game_options.disable_debug_vm; g_game_version = game_options.game_version; g_server_port = game_options.server_port; @@ -417,7 +404,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c // step 2: system prep prof().begin_event("startup::exec_runtime::system_prep"); - VM::vm_prepare(); // our fake ps2 VM needs to be prepared SystemThreadManager tm; auto& deci_thread = tm.create_thread("DMP"); auto& iop_thread = tm.create_thread("IOP"); @@ -425,7 +411,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c // a general worker thread to perform background operations from the EE thread (to not block the // game) auto& ee_worker_thread = tm.create_thread("EE-Worker"); - auto& vm_dmac_thread = tm.create_thread("VM-DMAC"); prof().end_event(); // step 3: start the EE! @@ -445,10 +430,6 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c auto p = scoped_prof("startup::exec_runtime::ee-start"); ee_thread.start(ee_runner); } - if (VM::use) { - auto p = scoped_prof("startup::exec_runtime::dmac-start"); - vm_dmac_thread.start(dmac_runner); - } // step 4: wait for EE to signal a shutdown. meanwhile, run video loop on main thread. // TODO relegate this to its own function diff --git a/game/sound/989snd/loader.cpp b/game/sound/989snd/loader.cpp index f30e605cdf..28cdf33b76 100644 --- a/game/sound/989snd/loader.cpp +++ b/game/sound/989snd/loader.cpp @@ -27,7 +27,7 @@ void FileAttributes::Read(BinaryReader& data) { num_chunks = data.read(); where.resize(num_chunks); - for (int i = 0; i < num_chunks; i++) { + for (size_t i = 0; i < num_chunks; i++) { where[i].offset = data.read(); where[i].size = data.read(); } @@ -215,16 +215,16 @@ SFXBlock* SFXBlock::ReadBlock(nonstd::span bank_data, nonstd::span sampl s16 NumSounds = data.read(); block->Sounds.resize(NumSounds); - s16 NumGrains = data.read(); - s16 NumVAGs = data.read(); + [[maybe_unused]] s16 NumGrains = data.read(); + [[maybe_unused]] s16 NumVAGs = data.read(); u32 FirstSound = data.read(); u32 FirstGrain = data.read(); - u32 VagsInSR = data.read(); - u32 VagDataSize = data.read(); - u32 SRAMAllocSize = data.read(); - u32 NextBlock = data.read(); + [[maybe_unused]] u32 VagsInSR = data.read(); + [[maybe_unused]] u32 VagDataSize = data.read(); + [[maybe_unused]] u32 SRAMAllocSize = data.read(); + [[maybe_unused]] u32 NextBlock = data.read(); u32 GrainData = 0; if (block->Version >= 2) { GrainData = data.read(); @@ -344,12 +344,12 @@ MusicBank* MusicBank::ReadBank(nonstd::span bank_data, bank->Sounds.resize(NumSounds); s16 NumProgs = data.read(); bank->Progs.resize(NumProgs); - s16 NumTones = data.read(); - s16 NumVAGs = data.read(); + [[maybe_unused]] s16 NumTones = data.read(); + [[maybe_unused]] s16 NumVAGs = data.read(); u32 FirstSound = data.read(); u32 FirstProg = data.read(); - u32 FirstTone = data.read(); + [[maybe_unused]] u32 FirstTone = data.read(); // vagsinsr data.read(); diff --git a/game/system/hid/input_bindings.cpp b/game/system/hid/input_bindings.cpp index b587e2cedb..773f0b92ed 100644 --- a/game/system/hid/input_bindings.cpp +++ b/game/system/hid/input_bindings.cpp @@ -377,8 +377,8 @@ InputBindingInfo::InputBindingInfo(const InputBinding bind, const bool _analog_button) : sdl_idx(sdl_code), pad_idx(bind.pad_data_index), - modifiers(bind.modifiers), - analog_button(_analog_button) { + analog_button(_analog_button), + modifiers(bind.modifiers) { switch (device_type) { case CONTROLLER: if (analog_button) { diff --git a/game/system/vm/dmac.cpp b/game/system/vm/dmac.cpp deleted file mode 100644 index 54d68d96ce..0000000000 --- a/game/system/vm/dmac.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * @file dmac.cpp - * DMAC implementation for the "PS2 virtual machine". - * Not meant to work as a full DMAC emulator, just enough to inspect DMA packets. - */ - -#include "dmac.h" - -#include "vm.h" - -#include "common/log/log.h" - -#include "game/kernel/common/kmalloc.h" -#include "game/runtime.h" - -namespace VM { - -Ptr dmac; -Ptr dmac_ch[10]; - -void dmac_init_globals() { - dmac = kmalloc(kdebugheap, sizeof(DmaCommonRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmac") - .cast(); - - for (int i = 0; i < 10; ++i) { - dmac_ch[i] = - kmalloc(kdebugheap, sizeof(DmaChannelRegisters), KMALLOC_ALIGN_16 | KMALLOC_MEMSET, "dmach") - .cast(); - } -} - -} // namespace VM diff --git a/game/system/vm/dmac.h b/game/system/vm/dmac.h deleted file mode 100644 index a8b7503d02..0000000000 --- a/game/system/vm/dmac.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -/*! - * @file dmac.h - * DMAC implementation for the "PS2 virtual machine". - * Not meant to work as a full DMAC emulator, just enough to inspect DMA packets. - */ - -#include "common/common_types.h" - -#include "game/kernel/common/Ptr.h" - -namespace VM { - -/*! - * DMA channel CHCR register. The only one we care about right now. - */ -struct DmaChcr { - u32 dir : 1; - u32 _pad1 : 1; - u32 mod : 2; - u32 asp : 2; - u32 tte : 1; - u32 tie : 1; - u32 str : 1; - u32 _pad2 : 7; - u16 tag : 16; -}; - -/*! - * Layout of the DMA channel registers in EE memory. For simplicity's sake, all are included, - * however, each channel may not actually have some of these registers. - * They are 16-byte aligned. - */ -struct alignas(16) DmaChannelRegisters { - alignas(16) DmaChcr chcr; - alignas(16) u32 madr; - alignas(16) u32 qwc; - alignas(16) u32 tadr; - alignas(16) u32 asr0; - alignas(16) u32 asr1; - alignas(16) u128 _pad1; - alignas(16) u128 _pad2; - alignas(16) u32 sadr; -}; - -/*! - * Layout of the DMAC registers in EE memory. - * They are 16-byte aligned. - */ -struct alignas(16) DmaCommonRegisters { - alignas(16) u32 ctrl; - alignas(16) u32 stat; - alignas(16) u32 pcr; - alignas(16) u32 sqwc; - alignas(16) u32 rbsr; - alignas(16) u32 rbor; - alignas(16) u32 stadr; -}; - -// pointer to DMAC registers -extern Ptr dmac; -// array of pointers to DMAC channels (they are not stored contiguously) -extern Ptr dmac_ch[10]; - -// enum DmaChannel { VIF0, VIF1, GIF, fromIPU, toIPU, SIF0, SIF1, SIF2, fromSPR, toSPR }; - -static_assert(sizeof(DmaChannelRegisters) == 0x90, "DmaChannelRegisters wrong size"); -static_assert(alignof(DmaChannelRegisters) == 0x10, "DmaChannelRegisters unaligned"); - -void dmac_init_globals(); - -} // namespace VM diff --git a/game/system/vm/vm.cpp b/game/system/vm/vm.cpp deleted file mode 100644 index 7adafc8468..0000000000 --- a/game/system/vm/vm.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/*! - * @file vm.cpp - * Base "PS2 virtual machine" code. - * Simulates the existence of select PS2 components, for inspection & debugging. - * Not an emulator! - */ - -#include "vm.h" - -#include -#include - -#include "dmac.h" - -#include "common/log/log.h" - -namespace VM { - -bool use = true; // enable VM by default, since we're debugging right now - -namespace { -Status status; -std::condition_variable vm_init_cv; -std::condition_variable vm_dead_cv; -std::mutex status_mutex; - -int components = 0; -} // namespace - -static void vm_change_status(Status new_status) { - std::unique_lock lk(status_mutex); - status = new_status; -} - -void wait_vm_init() { - std::unique_lock lk(status_mutex); - - vm_init_cv.wait(lk, [&] { return status == Status::Inited; }); -} - -void wait_vm_dead() { - if (status != Status::Kill && status != Status::Dead) { - lg::warn("[VM] Dying without being killed! There are {} component(s) running", components); - } - - std::unique_lock lk(status_mutex); - - vm_dead_cv.wait(lk, [&] { return components == 0; }); -} - -bool vm_want_exit() { - return status == Status::Kill || status == Status::Dead; -} - -void vm_prepare() { - lg::debug("[VM] Preparing..."); - vm_change_status(Status::Uninited); - lg::debug("[VM] Prepared"); -} - -void vm_init() { - if (status != Status::Uninited) { - lg::warn("[VM] unexpected status {}", fmt::underlying(status)); - } - - lg::debug("[VM] Inited"); - vm_change_status(Status::Inited); - vm_init_cv.notify_all(); -} - -void vm_kill() { - lg::debug("[VM] Killing"); - - vm_change_status(Status::Kill); - - // stall caller until VM is done dying - wait_vm_dead(); - - vm_change_status(Status::Dead); -} - -void subscribe_component() { - if (status == Status::Dead) { - throw std::runtime_error("[VM] Cannot add new components when VM is dead!"); - } - - status_mutex.lock(); - ++components; - status_mutex.unlock(); - - // stall component until VM is ready - if (status == Status::Uninited) { - wait_vm_init(); - } -} - -void unsubscribe_component() { - status_mutex.lock(); - --components; - status_mutex.unlock(); - vm_dead_cv.notify_all(); -} - -/*! - * Return the GOAL pointer to a specified PS2 VM component based on the EE address. - */ -u64 get_vm_ptr(u32 ptr) { - // currently, only DMAC and DMA channel banks are implemented. add more as necessary. - if (ptr == 0x10008000) { - return VM::dmac_ch[0].offset; - } else if (ptr == 0x10009000) { - return VM::dmac_ch[1].offset; - } else if (ptr == 0x1000a000) { - return VM::dmac_ch[2].offset; - } else if (ptr == 0x1000b000) { - return VM::dmac_ch[3].offset; - } else if (ptr == 0x1000b400) { - return VM::dmac_ch[4].offset; - } else if (ptr == 0x1000c000) { - return VM::dmac_ch[5].offset; - } else if (ptr == 0x1000c400) { - return VM::dmac_ch[6].offset; - } else if (ptr == 0x1000c800) { - return VM::dmac_ch[7].offset; - } else if (ptr == 0x1000d000) { - return VM::dmac_ch[8].offset; - } else if (ptr == 0x1000d400) { - return VM::dmac_ch[9].offset; - } else if (ptr == 0x1000e000) { - return VM::dmac.offset; - } else { - // return zero, using this result will segfault GOAL! - // we could die immediately, but it might be worth it to keep going just on the off chance more - // errors are reported, and not just only this one. - lg::error("unknown EE register for VM at #x{:08x}", ptr); - return 0; - } -} - -} // namespace VM diff --git a/game/system/vm/vm.h b/game/system/vm/vm.h deleted file mode 100644 index 260e59d184..0000000000 --- a/game/system/vm/vm.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*! - * @file vm.h - * Base "PS2 virtual machine" code. - * Simulates the existence of select PS2 components, for inspection & debugging. - * Not an emulator! - */ - -#include "common/common_types.h" - -namespace VM { - -extern bool use; - -enum class Status { Disabled, Uninited, Inited, Kill, Dead }; - -void wait_vm_init(); -void wait_vm_dead(); - -bool vm_want_exit(); - -void vm_prepare(); -void vm_init(); -void vm_kill(); - -void subscribe_component(); -void unsubscribe_component(); - -u64 get_vm_ptr(u32 ptr); - -} // namespace VM diff --git a/goal_src/jak3/compiler-setup.gc b/goal_src/jak3/compiler-setup.gc index 20ea2b1056..1a65ff5d2c 100644 --- a/goal_src/jak3/compiler-setup.gc +++ b/goal_src/jak3/compiler-setup.gc @@ -34,4 +34,4 @@ ((basic? ,obj) (-> (the basic ,obj) type)) (else symbol) ) - ) \ No newline at end of file + ) diff --git a/goal_src/jak3/kernel-defs.gc b/goal_src/jak3/kernel-defs.gc index 5f7e33ff99..6183a4fe43 100644 --- a/goal_src/jak3/kernel-defs.gc +++ b/goal_src/jak3/kernel-defs.gc @@ -127,6 +127,8 @@ (define-extern kernel-shutdown (function none)) (define-extern *boot-video-mode* "Defined in the kernel" int) (define-extern *kernel-boot-message* symbol) +(define-extern *kernel-symbol-warnings* symbol) +(define-extern symbol->string (function symbol string)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; PC Port functions diff --git a/goal_src/jak3/kernel/gcommon.gc b/goal_src/jak3/kernel/gcommon.gc index f6cb248b32..446de05ef0 100644 --- a/goal_src/jak3/kernel/gcommon.gc +++ b/goal_src/jak3/kernel/gcommon.gc @@ -7,3 +7,1328 @@ ;; DECOMP BEGINS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Game constants +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; disable PS2 only code and enable PC-specific code +(defglobalconstant PC_PORT #t) + +;; whether we're allowed to use more memory than the original game or not +(defglobalconstant BIG_MEMORY #t) +(defglobalconstant PC_BIG_MEMORY (and PC_PORT BIG_MEMORY)) + +;; enables the with-profiler statements, which send profiling data from +;; GOAL code to the frame profiler in C++. +(defglobalconstant PC_PROFILER_ENABLE #t) + +;; pointers larger than this are invalid by valid? +(defconstant END_OF_MEMORY #x8000000) + +(defun identity ((arg0 object)) + "Return the input. Works for any 64-bit value." + arg0 + ) + +(defun 1/ ((arg0 float)) + "Floating point reciprocal" + (declare (inline)) + (/ 1.0 arg0) + ) + +;; These functions exist a function objects that wrap the compiler's built-in operators. + +(defun + ((arg0 int) (arg1 int)) + "Add two integers (64-bit)." + (+ arg0 arg1) + ) + +(defun - ((arg0 int) (arg1 int)) + "Subtract two integers (64-bit)." + (- arg0 arg1) + ) + +(defun * ((arg0 int) (arg1 int)) + "Multiply two integers (32-bit)" + (* arg0 arg1) + ) + +(defun / ((arg0 int) (arg1 int)) + "Divide two integers (32-bit, signed)" + (/ arg0 arg1) + ) + +(defun mod ((arg0 int) (arg1 int)) + "Integer mod (signed, 32-bit)" + (mod arg0 arg1) + ) + +(defun rem ((arg0 int) (arg1 int)) + "Integer mod (signed, 32-bit). Even though it's called rem, it behaves the same as mod." + (mod arg0 arg1) + ) + + +(defun ash ((value int) (shift-amount int)) + "Arithmetic shift value by shift-amount. + A positive shift-amount will shift to the left and a negative will shift to the right." + ;; OpenGOAL does not support ash in the compiler, so we implement it here as an inline function. + (declare (inline)) + (if (> shift-amount 0) + (shl value shift-amount) + (sar value (- shift-amount)) + ) + ) + +(defun abs ((a int)) + "Take the absolute value of a 64-bit signed integer" + (declare (inline)) + ;; OpenGOAL doesn't support abs, so we implement it here. + (if (> a 0) + a + (- a) + ) + ) + +(defun min ((a int) (b int)) + "Compute minimum of two 64-bit signed integers." + (declare (inline)) + ;; OpenGOAL doesn't support min, so we implement it here. + (if (> a b) b a) + ) + +(defun max ((a int) (b int)) + "Compute maximum of two 64-bit signed integer." + (declare (inline)) + ;; OpenGOAL doesn't support max so we implement it here. + (if (> a b) a b) + ) + +(defun logior ((arg0 int) (arg1 int)) + "Logical or (64-bit)" + (logior arg0 arg1) + ) + +(defun logand ((arg0 int) (arg1 int)) + "Logical and (64-bit)" + (logand arg0 arg1) + ) + +(defun lognor ((a int) (b int)) + "Compute not or (64-bit)." + ;; Note - MIPS has a 'nor' instruction, but x86 doesn't. + ;; the OpenGOAL x86 compiler therefore doesn't have a nor operation, + ;; so lognor is implemented by this inline function instead. + (declare (inline)) + (lognot (logior a b)) + ) + +(defun logxor ((arg0 int) (arg1 int)) + "Logical exclusive or (64-bit)" + (logxor arg0 arg1) + ) + +(defun lognot ((arg0 int)) + "Logical not (64-bit)" + (lognot arg0) + ) + +(defun false-func () + "Return #f." + #f + ) + +(defun true-func () + "Return #t." + #t + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;; +;; format +;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The "format" function is implemented in C but is called _format. +;; This defines the format function to point to the same thing as _format. +(define format _format) + +;;;;;;;;;;;;;;;;;;;;;;;;;; +;; numeric types +;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; vec4s: 4 floats packed into a 128-bit integer register. This is rarely used. +(deftype vec4s (uint128) + ((x float :offset 0 :size 32) + (y float :offset 32 :size 32) + (z float :offset 64 :size 32) + (w float :offset 96 :size 32) + ) + ) + +(defmethod print ((this vec4s)) + "Custom print for vec4s that prints the 4 values." + (format #t "#" + (-> this x) + (-> this y) + (-> this z) + (-> this w) + this) + this + ) + +(deftype vector (structure) + ((data float 4) + (x float :overlay-at (-> data 0)) + (y float :overlay-at (-> data 1)) + (z float :overlay-at (-> data 2)) + (w float :overlay-at (-> data 3)) + (quad uint128 :overlay-at (-> data 0)) + ) + ) + +(defmacro print128 (value &key (stream #t)) + "Print a 128-bit value" + `(let ((temp (new 'stack-no-clear 'array 'uint64 2))) + (set! (-> (the (pointer uint128) temp)) ,value) + (format ,stream "#x~16X~16X" (-> temp 1) (-> temp 0)) + ) + ) + +(defmacro make-u128 (upper lower) + "Make a i128 from two 64-bit values." + `(rlet ((result :class i128) + (upper-xmm :class i128) + (lower-xmm :class i128)) + (.mov upper-xmm ,upper) + (.mov lower-xmm ,lower) + (.pcpyld result upper-xmm lower-xmm) + (the-as uint result) + ) + ) + +;; bfloat: boxed float type. A floating point number with type information. +;; It's a heap allocated basic. +(deftype bfloat (basic) + ((data float) + ) + ) + + +(defmethod print ((this bfloat)) + (format #t "~f" (-> this data)) + this + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Type System +;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmethod asize-of ((this type)) + "Get the size in memory of a type. The value calculated here is wrong." + (the-as int (logand (the-as uint #xfffffff0) (+ (* (-> this allocated-length) 4) 43))) + ) + +(defun basic-type? ((arg0 basic) (arg1 type)) + "Is the given basic an object of the given type?" + (let ((v1-0 (-> arg0 type)) + (a0-1 object) + ) + (until (= v1-0 a0-1) + (if (= v1-0 arg1) + (return #t) + ) + (set! v1-0 (-> v1-0 parent)) + ) + ) + #f + ) + +(defun type-type? ((arg0 type) (arg1 type)) + "Is the given type equal to, or a child of, the second type?" + (let ((v1-0 object)) + (if (= arg1 v1-0) + (return #t) + ) + (until (or (= arg0 v1-0) (zero? arg0)) + (if (= arg0 arg1) + (return #t) + ) + (set! arg0 (-> arg0 parent)) + ) + ) + #f + ) + +(defun type? ((arg0 object) (arg1 type)) + "Is the given object an object of the given type? Works for any boxed object (basic, symbol, binteger, pair)." + (let ((v1-0 object) + (a0-1 (rtype-of arg0)) + ) + (if (= arg1 v1-0) + (return #t) + ) + (until (or (= a0-1 v1-0) (zero? a0-1)) + (if (= a0-1 arg1) + (return #t) + ) + (set! a0-1 (-> a0-1 parent)) + ) + ) + #f + ) + +(defun find-parent-method ((arg0 type) (arg1 int)) + "Go up the type tree and find the first parent type that has a different implementation for the given method." + (local-vars (v0-0 function)) + (let ((v1-2 (-> arg0 method-table arg1))) + (until (!= v0-0 v1-2) + (if (= arg0 object) + (return nothing) + ) + (set! arg0 (-> arg0 parent)) + (set! v0-0 (-> arg0 method-table arg1)) + (if (zero? v0-0) + (return nothing) + ) + ) + ) + v0-0 + ) + +(defmacro call-parent-method (&rest args) + "Find the first different implementation of the current method in a parent type and call it with these arguments." + `((the (current-method-function-type) (find-parent-method (current-method-type) (current-method-id))) + ,@args) + ) + +(defun ref ((arg0 object) (arg1 int)) + "Get the n-th item in a linked list. No range checking." + (dotimes (v1-0 arg1) + (nop!) + (nop!) + (set! arg0 (cdr arg0)) + ) + (car arg0) + ) + +(defun ref& ((arg0 object) (arg1 int)) + "Get the pair containing the n-th item in a linked list. No range checking." + (dotimes (v1-0 arg1) + (nop!) + (nop!) + (set! arg0 (cdr arg0)) + ) + (if (null? arg0) + #f + arg0 + ) + ) + +(defmethod length ((this pair)) + (local-vars (v0-0 int)) + (cond + ((null? this) + (set! v0-0 0) + ) + (else + (let ((v1-1 (cdr this))) + (set! v0-0 1) + (while (and (not (null? v1-1)) (pair? v1-1)) + (+! v0-0 1) + (set! v1-1 (cdr v1-1)) + ) + ) + ) + ) + v0-0 + ) + +(defmethod asize-of ((this pair)) + (the-as int (-> pair size)) + ) + +(defun last ((arg0 object)) + (let ((v0-0 arg0)) + (while (not (null? (cdr v0-0))) + (nop!) + (nop!) + (set! v0-0 (cdr v0-0)) + ) + v0-0 + ) + ) + +(defun member ((arg0 object) (arg1 object)) + (let ((v1-0 arg1)) + (while (not (or (null? v1-0) (= (car v1-0) arg0))) + (set! v1-0 (cdr v1-0)) + ) + (if (not (null? v1-0)) + v1-0 + ) + ) + ) + +;; need to forward declare this, we haven't loaded the string library yet. +(define-extern name= (function object object symbol)) + +(defun nmember ((arg0 basic) (arg1 object)) + (while (not (or (null? arg1) (name= (car arg1) arg0))) + (set! arg1 (cdr arg1)) + ) + (if (not (null? arg1)) + arg1 + ) + ) + +(defun assoc ((arg0 object) (arg1 object)) + (let ((v1-0 arg1)) + (while (not (or (null? v1-0) (= (car (car v1-0)) arg0))) + (set! v1-0 (cdr v1-0)) + ) + (if (not (null? v1-0)) + (car v1-0) + ) + ) + ) + +(defun assoce ((arg0 object) (arg1 object)) + (let ((v1-0 arg1)) + (while (not (or (null? v1-0) (= (car (car v1-0)) arg0) (= (car (car v1-0)) 'else))) + (set! v1-0 (cdr v1-0)) + ) + (if (not (null? v1-0)) + (car v1-0) + ) + ) + ) + +(defun nassoc ((arg0 string) (arg1 object)) + (while (not (or (null? arg1) (let ((a1-1 (car (car arg1)))) + (if (pair? a1-1) + (nmember arg0 a1-1) + (name= a1-1 arg0) + ) + ) + ) + ) + (set! arg1 (cdr arg1)) + ) + (if (not (null? arg1)) + (car arg1) + ) + ) + +(defun nassoce ((arg0 string) (arg1 object)) + (while (not (or (null? arg1) (let ((s4-0 (car (car arg1)))) + (if (pair? s4-0) + (nmember arg0 s4-0) + (or (name= s4-0 arg0) (= s4-0 'else)) + ) + ) + ) + ) + (set! arg1 (cdr arg1)) + ) + (if (not (null? arg1)) + (car arg1) + ) + ) + +(defun append! ((arg0 object) (arg1 object)) + (cond + ((null? arg0) + arg1 + ) + (else + (let ((v1-1 arg0)) + (while (not (null? (cdr v1-1))) + (nop!) + (nop!) + (set! v1-1 (cdr v1-1)) + ) + (if (not (null? v1-1)) + (set! (cdr v1-1) arg1) + ) + ) + arg0 + ) + ) + ) + +(defun delete! ((arg0 object) (arg1 object)) + (the-as pair (cond + ((= arg0 (car arg1)) + (cdr arg1) + ) + (else + (let ((v1-1 arg1) + (a2-0 (cdr arg1)) + ) + (while (not (or (null? a2-0) (= (car a2-0) arg0))) + (set! v1-1 a2-0) + (set! a2-0 (cdr a2-0)) + ) + (if (not (null? a2-0)) + (set! (cdr v1-1) (cdr a2-0)) + ) + ) + arg1 + ) + ) + ) + ) + +(defun delete-car! ((arg0 object) (arg1 object)) + (cond + ((= arg0 (car (car arg1))) + (cdr arg1) + ) + (else + (let ((v1-2 arg1) + (a2-0 (cdr arg1)) + ) + (while (not (or (null? a2-0) (= (car (car a2-0)) arg0))) + (set! v1-2 a2-0) + (set! a2-0 (cdr a2-0)) + ) + (if (not (null? a2-0)) + (set! (cdr v1-2) (cdr a2-0)) + ) + ) + arg1 + ) + ) + ) + +(defun insert-cons! ((arg0 object) (arg1 object)) + (let ((a3-0 (delete-car! (car arg0) arg1))) + (cons arg0 a3-0) + ) + ) + +(defun sort ((arg0 pair) (arg1 (function object object object))) + (let ((s4-0 -1)) + (while (nonzero? s4-0) + (set! s4-0 0) + (let ((s3-0 arg0)) + (while (not (or (null? (cdr s3-0)) (not (pair? (cdr s3-0))))) + (let* ((s2-0 (car s3-0)) + (s1-0 (car (cdr s3-0))) + (v1-1 (arg1 s2-0 s1-0)) + ) + (when (and (or (not v1-1) (> (the-as int v1-1) 0)) (!= v1-1 #t)) + (+! s4-0 1) + (set! (car s3-0) s1-0) + (set! (car (cdr s3-0)) s2-0) + ) + ) + (set! s3-0 (cdr s3-0)) + ) + ) + ) + ) + arg0 + ) + +(defun string->symbol-debug ((arg0 string)) + (let ((gp-0 *kernel-symbol-warnings*)) + (set! *kernel-symbol-warnings* #f) + (let ((v0-0 (string->symbol arg0))) + (set! *kernel-symbol-warnings* gp-0) + v0-0 + ) + ) + ) + +(defun symbol->string-debug ((arg0 symbol)) + (let ((gp-0 *kernel-symbol-warnings*)) + (set! *kernel-symbol-warnings* #f) + (let ((v0-0 (symbol->string arg0))) + (set! *kernel-symbol-warnings* gp-0) + v0-0 + ) + ) + ) + +(defun symbol->hash ((arg0 symbol)) + (the-as pointer arg0) + ) + +(defmethod new array ((allocation symbol) (type-to-make type) (arg0 type) (arg1 int)) + (let ((v0-1 (object-new + allocation + type-to-make + (the-as int (+ (-> type-to-make size) (* arg1 (if (type-type? arg0 number) + (the-as int (-> arg0 size)) + 4 + ) + ) + ) + ) + ) + ) + ) + (set! (-> v0-1 allocated-length) arg1) + (set! (-> v0-1 length) arg1) + (set! (-> v0-1 content-type) arg0) + v0-1 + ) + ) + +(defmethod print ((this array)) + (format #t "#(") + (cond + ((type-type? (-> this content-type) integer) + (case (-> this content-type symbol) + (('int32) + (dotimes (s5-0 (-> this length)) + (format + #t + (if (zero? s5-0) + "~D" + " ~D" + ) + (-> (the-as (array int32) this) s5-0) + ) + ) + ) + (('uint32) + (dotimes (s5-1 (-> this length)) + (format + #t + (if (zero? s5-1) + "~D" + " ~D" + ) + (-> (the-as (array uint32) this) s5-1) + ) + ) + ) + (('int64) + (dotimes (s5-2 (-> this length)) + (format + #t + (if (zero? s5-2) + "~D" + " ~D" + ) + (-> (the-as (array int64) this) s5-2) + ) + ) + ) + (('uint64) + (dotimes (s5-3 (-> this length)) + (format + #t + (if (zero? s5-3) + "#x~X" + " #x~X" + ) + (-> (the-as (array uint64) this) s5-3) + ) + ) + ) + (('int8) + (dotimes (s5-4 (-> this length)) + (format + #t + (if (zero? s5-4) + "~D" + " ~D" + ) + (-> (the-as (array int8) this) s5-4) + ) + ) + ) + (('uint8) + (dotimes (s5-5 (-> this length)) + (format + #t + (if (zero? s5-5) + "~D" + " ~D" + ) + (-> (the-as (array uint8) this) s5-5) + ) + ) + ) + (('int16) + (dotimes (s5-6 (-> this length)) + (format + #t + (if (zero? s5-6) + "~D" + " ~D" + ) + (-> (the-as (array int16) this) s5-6) + ) + ) + ) + (('uint16) + (dotimes (s5-7 (-> this length)) + (format + #t + (if (zero? s5-7) + "~D" + " ~D" + ) + (-> (the-as (array uint16) this) s5-7) + ) + ) + ) + (('uint128 'int128) + (dotimes (s5-8 (-> this length)) + (format + #t + (if (zero? s5-8) + "#x~X" + " #x~X" + ) + (-> (the-as (array uint128) this) s5-8) + ) + ) + ) + (else + (dotimes (s5-9 (-> this length)) + (format + #t + (if (zero? s5-9) + "~D" + " ~D" + ) + (-> (the-as (array int32) this) s5-9) + ) + ) + ) + ) + ) + ((= (-> this content-type) float) + (dotimes (s5-10 (-> this length)) + (if (zero? s5-10) + (format #t "~f" (-> (the-as (array float) this) s5-10)) + (format #t " ~f" (-> (the-as (array float) this) s5-10)) + ) + ) + ) + (else + (dotimes (s5-11 (-> this length)) + (if (zero? s5-11) + (format #t "~A" (-> (the-as (array basic) this) s5-11)) + (format #t " ~A" (-> (the-as (array basic) this) s5-11)) + ) + ) + ) + ) + (format #t ")") + this + ) + +(defmethod inspect ((this array)) + (format #t "[~8x] ~A~%" this (-> this type)) + (format #t "~Tallocated-length: ~D~%" (-> this allocated-length)) + (format #t "~Tlength: ~D~%" (-> this length)) + (format #t "~Tcontent-type: ~A~%" (-> this content-type)) + (format #t "~Tdata[~D]: @ #x~X~%" (-> this allocated-length) (-> this data)) + (cond + ((and (= (logand (the-as int (-> this content-type)) 7) 4) (type-type? (-> this content-type) integer)) + (case (-> this content-type symbol) + (('int32) + (dotimes (s5-0 (-> this length)) + (format #t "~T [~D] ~D~%" s5-0 (-> (the-as (array int32) this) s5-0)) + ) + ) + (('uint32) + (dotimes (s5-1 (-> this length)) + (format #t "~T [~D] ~D~%" s5-1 (-> (the-as (array uint32) this) s5-1)) + ) + ) + (('int64) + (dotimes (s5-2 (-> this length)) + (format #t "~T [~D] ~D~%" s5-2 (-> (the-as (array int64) this) s5-2)) + ) + ) + (('uint64) + (dotimes (s5-3 (-> this length)) + (format #t "~T [~D] #x~X~%" s5-3 (-> (the-as (array uint64) this) s5-3)) + ) + ) + (('int8) + (dotimes (s5-4 (-> this length)) + (format #t "~T [~D] ~D~%" s5-4 (-> (the-as (array int8) this) s5-4)) + ) + ) + (('uint8) + (dotimes (s5-5 (-> this length)) + (format #t "~T [~D] ~D~%" s5-5 (-> (the-as (array int8) this) s5-5)) + ) + ) + (('int16) + (dotimes (s5-6 (-> this length)) + (format #t "~T [~D] ~D~%" s5-6 (-> (the-as (array int16) this) s5-6)) + ) + ) + (('uint16) + (dotimes (s5-7 (-> this length)) + (format #t "~T [~D] ~D~%" s5-7 (-> (the-as (array uint16) this) s5-7)) + ) + ) + (('int128 'uint128) + (dotimes (s5-8 (-> this length)) + (format #t "~T [~D] #x~X~%" s5-8 (-> (the-as (array uint128) this) s5-8)) + ) + ) + (else + (dotimes (s5-9 (-> this length)) + (format #t "~T [~D] ~D~%" s5-9 (-> (the-as (array int32) this) s5-9)) + ) + ) + ) + ) + ((= (-> this content-type) float) + (dotimes (s5-10 (-> this length)) + (format #t "~T [~D] ~f~%" s5-10 (-> (the-as (array float) this) s5-10)) + ) + ) + (else + (dotimes (s5-11 (-> this length)) + (format #t "~T [~D] ~A~%" s5-11 (-> (the-as (array basic) this) s5-11)) + ) + ) + ) + this + ) + +(defmethod length ((this array)) + (-> this length) + ) + +(defmethod asize-of ((this array)) + (the-as + int + (+ (-> this type size) (* (-> this allocated-length) (if (type-type? (-> this content-type) number) + (the-as int (-> this content-type size)) + 4 + ) + ) + ) + ) + ) + +(defun mem-copy! ((arg0 pointer) (arg1 pointer) (arg2 int)) + (let ((v0-0 arg0)) + (dotimes (v1-0 arg2) + (set! (-> (the-as (pointer uint8) arg0)) (-> (the-as (pointer uint8) arg1))) + (&+! arg0 1) + (&+! arg1 1) + ) + v0-0 + ) + ) + +(defun qmem-copy<-! ((arg0 pointer) (arg1 pointer) (arg2 int)) + (let ((v0-0 arg0)) + (countdown (v1-1 (/ (+ arg2 15) 16)) + (set! (-> (the-as (pointer uint128) arg0)) (-> (the-as (pointer uint128) arg1))) + (&+! arg0 16) + (&+! arg1 16) + ) + v0-0 + ) + ) + +(defun qmem-copy->! ((arg0 pointer) (arg1 pointer) (arg2 int)) + (let ((v0-0 arg0)) + (let* ((v1-1 (/ (+ arg2 15) 16)) + (a0-1 (&+ arg0 (* v1-1 16))) + (a1-1 (&+ arg1 (* v1-1 16))) + ) + (while (nonzero? v1-1) + (+! v1-1 -1) + (&+! a0-1 -16) + (&+! a1-1 -16) + (set! (-> (the-as (pointer uint128) a0-1)) (-> (the-as (pointer uint128) a1-1))) + ) + ) + v0-0 + ) + ) + +(defun qmem-clear! ((arg0 pointer) (arg1 int)) + (let ((v0-0 arg0)) + (dotimes (v1-0 arg1) + (set! (-> (the-as (pointer int128) arg0)) (the int128 0)) + (&+! arg0 16) + ) + v0-0 + ) + ) + +(defun mem-set32! ((arg0 pointer) (arg1 int) (arg2 int)) + (let ((v0-0 arg0)) + (dotimes (v1-0 arg1) + (set! (-> (the-as (pointer int32) arg0)) arg2) + (&+! arg0 4) + (nop!) + ) + v0-0 + ) + ) + +(defun mem-or! ((arg0 pointer) (arg1 pointer) (arg2 int)) + (let ((v0-0 arg0)) + (dotimes (v1-0 arg2) + (logior! (-> (the-as (pointer uint8) arg0)) (-> (the-as (pointer uint8) arg1))) + (&+! arg0 1) + (&+! arg1 1) + ) + v0-0 + ) + ) + +(defun quad-copy! ((dst pointer) (src pointer) (qwc int)) + "Optimized memory copy. The original is pretty clever, but this isn't." + (qmem-copy<-! dst src (* qwc 16)) + (none) + ) + +(deftype inline-array-class (basic) + ((length int32) + (allocated-length int32) + (_data uint8 :dynamic :offset 16) + ) + (:methods + (new (symbol type int) _type_) + (push-back (_type_ object) int) + (inline-array-class-method-10 () none) + (clear-1 (_type_) symbol) + (clear-2 (_type_) none) + (pop-front (_type_ int) pointer) + ) + ) + +;; these specicializations exist so the push-back and pop-front methods can be hard-coded to have +;; a fixed sized store/load, rather than mem-cpy the size of the element. +;; This is kinda like a manual version of C++ templates (and perhaps was a attempt to use GOOS macros to do +;; something similar? or they just copy-pasted it, idk) +(deftype inline-array-class-uint64 (inline-array-class) + ((data uint64 :dynamic :offset 16) + ) + ) + +(deftype inline-array-class-uint32 (inline-array-class) + ((data uint32 :dynamic :offset 16) + ) + ) + +(defmethod new inline-array-class ((allocation symbol) (type-to-make type) (arg0 int)) + (let ((v0-0 (object-new + allocation + type-to-make + (the-as int (+ (-> type-to-make size) (* (the-as uint arg0) (-> type-to-make heap-base)))) + ) + ) + ) + (when (nonzero? v0-0) + (set! (-> v0-0 length) arg0) + (set! (-> v0-0 allocated-length) arg0) + ) + v0-0 + ) + ) + +(defmethod length ((this inline-array-class)) + (-> this length) + ) + +(defmethod asize-of ((this inline-array-class)) + (the-as int (+ (-> this type size) (* (-> this allocated-length) (the-as int (-> this type heap-base))))) + ) + +(defmethod push-back ((this inline-array-class) (arg0 object)) + (let ((s5-0 (-> this length))) + (let ((a2-0 (-> this type heap-base))) + (mem-copy! + (the-as + pointer + (+ (+ (* s5-0 (the-as int (-> this type heap-base))) -4 (-> this type size)) (the-as int this)) + ) + (the-as pointer arg0) + (the-as int a2-0) + ) + ) + (+! (-> this length) 1) + s5-0 + ) + ) + +(defmethod push-back ((this inline-array-class-uint32) (arg0 object)) + (let ((v0-0 (-> this length))) + (-> this type heap-base) + (set! (-> (the-as + (pointer int32) + (+ (+ (* v0-0 (the-as int (-> this type heap-base))) -4 (-> this type size)) (the-as int this)) + ) + ) + (the-as int32 arg0) + ) + (+! (-> this length) 1) + v0-0 + ) + ) + +(defmethod push-back ((this inline-array-class-uint64) (arg0 object)) + (let ((v0-0 (-> this length))) + (-> this type heap-base) + (set! (-> (the-as + (pointer int64) + (+ (+ (* v0-0 (the-as int (-> this type heap-base))) -4 (-> this type size)) (the-as int this)) + ) + ) + (the-as int64 arg0) + ) + (+! (-> this length) 1) + v0-0 + ) + ) + +(defmethod pop-front ((this inline-array-class) (arg0 int)) + (+! (-> this length) -1) + (+ (-> this length) -1) + (let ((a2-0 (-> this type heap-base)) + (t9-0 mem-copy!) + (v1-10 (+ (+ (* (the-as uint arg0) (-> this type heap-base)) -4 (-> this type size)) (the-as uint this))) + (a1-4 (-> this type heap-base)) + ) + (t9-0 + (the-as pointer v1-10) + (the-as pointer (+ (+ (* (-> this length) (the-as int a1-4)) -4 (-> this type size)) (the-as int this))) + (the-as int a2-0) + ) + ) + ) + +(defmethod pop-front ((this inline-array-class-uint64) (arg0 int)) + (+! (-> this length) -1) + (+ (-> this length) -1) + (-> this type heap-base) + (let* ((v1-7 (-> this type heap-base)) + (v0-0 (-> (the-as + (pointer uint64) + (+ (+ (* (-> this length) (the-as int v1-7)) -4 (-> this type size)) (the-as int this)) + ) + ) + ) + ) + (set! (-> (the-as + (pointer uint64) + (+ (+ (* (the-as uint arg0) (-> this type heap-base)) -4 (-> this type size)) (the-as uint this)) + ) + ) + v0-0 + ) + (the-as pointer v0-0) + ) + ) + +(defmethod pop-front ((this inline-array-class-uint32) (arg0 int)) + (+! (-> this length) -1) + (+ (-> this length) -1) + (-> this type heap-base) + (let* ((v1-7 (-> this type heap-base)) + (v0-0 (-> (the-as + (pointer uint32) + (+ (+ (* (-> this length) (the-as int v1-7)) -4 (-> this type size)) (the-as int this)) + ) + ) + ) + ) + (set! (-> (the-as + (pointer uint32) + (+ (+ (* (the-as uint arg0) (-> this type heap-base)) -4 (-> this type size)) (the-as uint this)) + ) + ) + v0-0 + ) + (the-as pointer v0-0) + ) + ) + +(defmethod clear-2 ((this inline-array-class)) + (set! (-> this length) 0) + 0 + (none) + ) + +(defmethod clear-1 ((this inline-array-class)) + (set! (-> this length) 0) + #t + ) + +(defun-recursive fact int ((arg0 int)) + (if (= arg0 1) + 1 + (* arg0 (fact (+ arg0 -1))) + ) + ) + +(define *print-column* (the-as binteger 0)) + +(defun print ((arg0 object)) + ((method-of-type (rtype-of arg0) print) arg0) + ) + +(defun printl ((arg0 object)) + (let ((a0-1 arg0)) + ((method-of-type (rtype-of a0-1) print) a0-1) + ) + (format #t "~%") + arg0 + ) + +(defun inspect ((arg0 object)) + ((method-of-type (rtype-of arg0) inspect) arg0) + ) + +(defun-debug mem-print ((arg0 (pointer uint32)) (arg1 int)) + (dotimes (s4-0 (/ arg1 4)) + (format + 0 + "~X: ~X ~X ~X ~X~%" + (&-> arg0 (* s4-0 4)) + (-> arg0 (* s4-0 4)) + (-> arg0 (+ (* s4-0 4) 1)) + (-> arg0 (+ (* s4-0 4) 2)) + (-> arg0 (+ (* s4-0 4) 3)) + ) + ) + #f + ) + +(define *trace-list* '()) + +(defun print-tree-bitmask ((arg0 int) (arg1 int)) + (dotimes (s4-0 arg1) + (if (not (logtest? arg0 1)) + (format #t " ") + (format #t "| ") + ) + (set! arg0 (shr arg0 1)) + ) + #f + ) + +(defun breakpoint-range-set! ((arg0 uint) (arg1 uint) (arg2 uint)) + "Sets some debug register (COP0 Debug, dab, dabm) to break on memory access. + This is not supported in OpenGOAL." + (break!) + ) + +#| +(defun valid? ((arg0 object) (arg1 type) (arg2 string) (arg3 symbol) (arg4 object)) + (local-vars (v1-11 int) (v1-26 int) (v1-56 int) (v1-60 int) (s7-0 none)) + (let ((v1-1 + (and (>= (the-as uint arg0) (the-as uint __START-OF-TABLE__)) (< (the-as uint arg0) (the-as uint #x8000000))) + ) + ) + (cond + ((not arg1) + (cond + ((logtest? (the-as int arg0) 3) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object (misaligned)~%" arg0 arg2) + ) + #f + ) + ((not v1-1) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object (bad address)~%" arg0 arg2) + ) + #f + ) + (else + #t + ) + ) + ) + ((and arg3 (not arg0)) + #t + ) + ((= arg1 structure) + (cond + ((logtest? (the-as int arg0) 15) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" arg0 arg2 arg1) + ) + #f + ) + ((or (not v1-1) (begin + (let ((v1-10 #x8000)) + (.daddu v1-11 v1-10 s7-0) + ) + (< (the-as uint arg0) (the-as uint v1-11)) + ) + ) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" arg0 arg2 arg1) + ) + #f + ) + (else + #t + ) + ) + ) + ((= arg1 pair) + (cond + ((not (pair? arg0)) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" arg0 arg2 arg1) + ) + #f + ) + ((not v1-1) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" arg0 arg2 arg1) + ) + #f + ) + (else + #t + ) + ) + ) + ((= arg1 binteger) + (cond + ((not (logtest? (the-as int arg0) 7)) + #t + ) + (else + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" arg0 arg2 arg1) + ) + #f + ) + ) + ) + ((or (= arg1 symbol) (= arg1 boolean)) + (cond + ((not (logtest? (the-as int arg0) 1)) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" arg0 arg2 arg1) + ) + #f + ) + ((or (not v1-1) (< (the-as int arg0) (the-as int __START-OF-TABLE__)) (begin + (let ((v1-25 #x8000)) + (.daddu v1-26 v1-25 s7-0) + ) + (>= (the-as int arg0) v1-26) + ) + ) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" arg0 arg2 arg1) + ) + #f + ) + (else + #t + ) + ) + ) + ((!= (logand (the-as int arg0) 7) 4) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (misaligned)~%" arg0 arg2 arg1) + ) + #f + ) + ((not v1-1) + (if arg2 + (format arg4 "ERROR: object #x~X ~S is not a valid object of type '~A' (bad address)~%" arg0 arg2 arg1) + ) + #f + ) + ((and (= arg1 type) (!= (rtype-of arg0) type)) + (if arg2 + (format + arg4 + "ERROR: object #x~X ~S is not a valid object of type '~A' (invalid type #x~X)~%" + arg0 + arg2 + arg1 + (rtype-of arg0) + ) + ) + #f + ) + ((and (!= arg1 type) (not (valid? (rtype-of arg0) type (the-as string #f) #t 0))) + (if arg2 + (format + arg4 + "ERROR: object #x~X ~S is not a valid object of type '~A' (invalid type #x~X)~%" + arg0 + arg2 + arg1 + (rtype-of arg0) + ) + ) + #f + ) + ((not (type? arg0 arg1)) + (if arg2 + (format + arg4 + "ERROR: object #x~X ~S is not a valid object of type '~A' (is type '~A' instead)~%" + arg0 + arg2 + arg1 + (rtype-of arg0) + ) + ) + #f + ) + ((= arg1 symbol) + (let ((v1-55 #x8000)) + (.daddu v1-56 v1-55 s7-0) + ) + (cond + ((>= (the-as uint arg0) (the-as uint v1-56)) + (if arg2 + (format + arg4 + "ERROR: object #x~X ~S is not a valid object of type '~A' (not in symbol table)~%" + arg0 + arg2 + arg1 + ) + ) + #f + ) + (else + #t + ) + ) + ) + ((begin + (let ((v1-59 #x8000)) + (.daddu v1-60 v1-59 s7-0) + ) + (< (the-as uint arg0) (the-as uint v1-60)) + ) + (if arg2 + (format + arg4 + "ERROR: object #x~X ~S is not a valid object of type '~A' (inside symbol table)~%" + arg0 + arg2 + arg1 + ) + ) + #f + ) + (else + #t + ) + ) + ) + ) +|# diff --git a/goal_src/jak3/kernel/gkernel-h.gc b/goal_src/jak3/kernel/gkernel-h.gc index 768c7378a3..01076dd065 100644 --- a/goal_src/jak3/kernel/gkernel-h.gc +++ b/goal_src/jak3/kernel/gkernel-h.gc @@ -7,3 +7,615 @@ ;; DECOMP BEGINS +(defconstant *kernel-major-version* 2) +(defconstant *kernel-minor-version* 0) + +(defconstant DPROCESS_STACK_SIZE (#if PC_PORT #x8000 #x3800)) +(defconstant PROCESS_STACK_SIZE (#if PC_PORT #x4000 #x1c00)) + +;; the size of the shared heap used by dynamically sized processes +(#if PC_BIG_MEMORY + (defconstant PROCESS_HEAP_MULT 4) ;; 4x actors + (defconstant PROCESS_HEAP_MULT 1) + ) +(defconstant PROCESS_HEAP_SIZE (* PROCESS_HEAP_MULT 1540 1024)) +(defconstant PROCESS_HEAP_MAX (* PROCESS_HEAP_MULT 768)) + +(defconstant *tab-size* (the binteger 8)) +(defconstant *gtype-basic-offset* 4) + +;; if set, will attempt to detect memory corruption and stack overflow bugs +;; to some extent. +(defglobalconstant KERNEL_DEBUG #t) + +(defconstant *scratch-memory-top* (the pointer #x70004000)) + +(defenum process-mask + :type uint32 + :bitfield #t + (execute 0) ;; 1 + (freeze 1) + (pause 2) + (menu 3) + (progress 4) + (actor-pause 5) ;; 32 + (sleep 6) ;; 64 + (sleep-code 7) ;; 128 + (process-tree 8) ;; 256 + (heap-shrunk 9) ;; 512 + (going 10) ;; 1024 + (kernel-run 11) ;; 2048 + (no-kill 12) ;; 4096 + (movie 13) ;; 8192 + (dark-effect 14) ;; 0x4000 + (target 15) ;; 0x8000 + + (sidekick 16) ;; 0x1'0000 + (crate 17) ;; 0x2'0000 + (collectable 18) ;; 0x4'0000 + (enemy 19) ;; 0x8'0000 + (camera 20) ;; 0x10'0000 + (platform 21) ;; 0x20'0000 + (ambient 22) ;; 0x40'0000 + (entity 23) ;; 0x80'0000 + (projectile 24) ;; 0x100'0000 + (bot 25) ;; 0x200'0000 + (death 26) ;; 0x400'0000 + (guard 27) ;; 0x800'0000 + (vehicle 28) ;; 0x1000'0000 + (civilian 29) ;; 0x2000'0000 + (kg-robot 30) ;; 0x4000'0000 + (metalhead 31) ;; 0x8000'0000 + ) + +;; forward declarations +(declare-type process-tree basic) +(declare-type process process-tree) +(declare-type res-lump basic) +(declare-type entity res-lump) +(declare-type entity-actor entity) +(declare-type dead-pool basic) +(declare-type level basic) +(declare-type state basic) +(declare-type event-message-block structure) +(declare-type stack-frame basic) +(declare-type thread basic) +(declare-type cpu-thread thread) + +(deftype kernel-context (basic) + ((prevent-from-run process-mask) + (require-for-run process-mask) + (allow-to-run process-mask) + (next-pid int32) + (fast-stack-top pointer) + (current-process process) + (relocating-process basic) + (relocating-min int32) + (relocating-max int32) + (relocating-offset int32) + (relocating-level level) + (low-memory-message symbol) + (login-object basic) + (login-art-group basic) + (login-level-index int32) + ) + ) + +(deftype time-frame (int64) + () + ) + +;; times are stored in 300ths of a second. +;; this divides evenly into frames at both 50 and 60 fps. +;; typically these are stored as integers as more precision is not useful. +;; an unsigned 32-bit integer can store about 150 days +(defglobalconstant TICKS_PER_SECOND 300) ;; 5 t/frame @ 60fps, 6 t/frame @ 50fps + +;; this was usec in GOAL +(defmacro seconds (x) + "Convert number to seconds unit. + Returns uint." + (cond + ((integer? x) + (* TICKS_PER_SECOND x) + ) + ((float? x) + (* 1 (* 1.0 x TICKS_PER_SECOND)) + ) + (#t + `(the uint (* TICKS_PER_SECOND ,x)) + ) + ) + ) + + +(deftype clock (basic) + ((index int16) + (ref-count uint16) + (mask process-mask) + (clock-ratio float) + (accum float) + (integral-accum float) + (frame-counter uint64) + (old-frame-counter uint64) + (integral-frame-counter uint64) + (old-integral-frame-counter uint64) + (sparticle-data vector :inline) + (seconds-per-frame float) + (frames-per-second float) + (time-adjust-ratio float) + ) + (:methods + (new (symbol type int) _type_) + (update-rates! (_type_ float) float) + (clock-method-10 () none) + (clock-method-11 () none) + (clock-method-12 () none) + (clock-method-13 () none) + (clock-method-14 () none) + (clock-method-15 () none) + (frame-mask-2 (_type_ int) symbol) + (frame-mask-4 (_type_ int) symbol) + (frame-mask-8 (_type_ int) symbol) + (frame-mask-16 (_type_ int) symbol) + (frame-mask-32 (_type_ int) symbol) + (frame-mask-64 (_type_ int) symbol) + (frame-mask-128 (_type_ int) symbol) + (frame-mask-256 (_type_ int) symbol) + ) + ) + +(defmethod frame-mask-2 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 1)) + ) + +(defmethod frame-mask-4 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 3)) + ) + +(defmethod frame-mask-8 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 7)) + ) + +(defmethod frame-mask-16 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 15)) + ) + +(defmethod frame-mask-32 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 31)) + ) + +(defmethod frame-mask-64 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 63)) + ) + +(defmethod frame-mask-128 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 127)) + ) + +(defmethod frame-mask-256 ((this clock) (arg0 int)) + (not (logtest? (logxor arg0 (the-as int (-> this integral-frame-counter))) 255)) + ) + +(defmethod new clock ((allocation symbol) (type-to-make type) (arg0 int)) + (let ((gp-0 (object-new allocation type-to-make (the-as int (-> type-to-make size))))) + (set! (-> gp-0 index) arg0) + (set! (-> gp-0 frame-counter) (the-as uint #x493e0)) + (set! (-> gp-0 integral-frame-counter) (the-as uint #x493e0)) + (set! (-> gp-0 old-frame-counter) (+ (-> gp-0 frame-counter) -1)) + (set! (-> gp-0 old-integral-frame-counter) (+ (-> gp-0 integral-frame-counter) -1)) + (update-rates! gp-0 1.0) + gp-0 + ) + ) + +(deftype process-tree (basic) + ((name string) + (mask process-mask) + (clock clock) + (parent (pointer process-tree)) + (brother (pointer process-tree)) + (child (pointer process-tree)) + (ppointer (pointer process)) + (self process-tree) + ) + (:methods + (new (symbol type string) _type_) + (activate (_type_ process-tree basic pointer) process-tree) + (deactivate (_type_) none) + (init-from-entity! (_type_ entity-actor) none) ;; todo check + (run-logic? (_type_) symbol) + (process-tree-method-13 () none) + ) + :no-runtime-type + ) + +(deftype thread (basic) + ((name symbol) + (process process) + (previous thread) + (suspend-hook (function cpu-thread none)) + (resume-hook (function cpu-thread none)) + (pc pointer) + (sp pointer) + (stack-top pointer) + (stack-size int32) + ) + (:methods + (thread-method-9 () none) + (thread-method-10 () none) + (thread-method-11 () none) + ) + ) + +(deftype cpu-thread (thread) + ((rreg uint64 7) + (freg float 8) + (stack uint8 :dynamic) + ) + (:methods + (new (symbol type process symbol int pointer) _type_) + ) + ) + +(deftype dead-pool (process-tree) + () + (:methods + (dead-pool-method-14 () none) + (dead-pool-method-15 () none) + ) + ) + +(deftype dead-pool-heap-rec (structure) + ((process process) + (prev dead-pool-heap-rec) + (next dead-pool-heap-rec) + ) + :pack-me + ) + +(deftype dead-pool-heap (dead-pool) + ((allocated-length int32) + (compact-time uint32) + (compact-count-targ uint32) + (compact-count uint32) + (fill-percent float) + (first-gap dead-pool-heap-rec) + (first-shrink dead-pool-heap-rec) + (heap kheap :inline) + (alive-list dead-pool-heap-rec :inline) + (last dead-pool-heap-rec :overlay-at (-> alive-list prev)) + (dead-list dead-pool-heap-rec :inline) + (process-list dead-pool-heap-rec :dynamic) + ) + (:methods + (dead-pool-heap-method-16 () none) + (dead-pool-heap-method-17 () none) + (dead-pool-heap-method-18 () none) + (dead-pool-heap-method-19 () none) + (dead-pool-heap-method-20 () none) + (dead-pool-heap-method-21 () none) + (dead-pool-heap-method-22 () none) + (dead-pool-heap-method-23 () none) + (dead-pool-heap-method-24 () none) + (dead-pool-heap-method-25 () none) + (dead-pool-heap-method-26 () none) + (dead-pool-heap-method-27 () none) + ) + ) + +(deftype stack-frame (basic) + ((name symbol) + (next stack-frame) + ) + ) + +(deftype catch-frame (stack-frame) + ((sp int32) + (ra int32) + (freg float 10) + (rreg uint128 7) + ) + (:methods + (new (symbol type symbol function (pointer uint64)) object) + ) + ) + +(deftype protect-frame (stack-frame) + ((exit (function object)) + ) + ) + +(deftype handle (uint64) + ((process (pointer process) :offset 0 :size 32) + (pid int32 :offset 32 :size 32) + (u64 uint64 :offset 0 :size 64) + ) + ) + +(defmacro handle->process (handle) + "Convert a handle to a process. If the process no longer exists, returns #f." + `(let ((the-handle (the-as handle ,handle))) + (if (-> the-handle process) ;; if we don't point to a process, kernel sets this to #f + (let ((proc (-> (-> the-handle process)))) + (if (= (-> the-handle pid) (-> proc pid)) ;; make sure it's the same process + proc + ) + ) + ) + ) + ) + +(defmethod inspect handle ((this handle)) + (when (not this) + (return this) + ) + (format #t "[~8x] ~A~%" this 'handle) + (format #t "~1Tprocess: #x~X~%" (-> this process)) + (format #t "~1Tpid: ~D~%" (-> this pid)) + this + ) + +; (defmethod print ((this handle)) +; (if (nonzero? this) +; (format #t "#" (handle->process this) (-> this pid)) +; (format #t "#") +; ) +; this +; ) + +(defmacro ppointer->process (ppointer) + "convert a (pointer process) to a process." + ;; this uses the self field, which seems to always just get set to the object. + ;; confirmed in Jak 1 that using self here is useless, not sure... + `(let ((the-pp ,ppointer)) + (if the-pp (-> the-pp 0 self)) + ) + ) + +(defmacro process->ppointer (proc) + "safely get a (pointer process) from a process, returning #f if invalid." + `(let ((the-proc ,proc)) + (if the-proc (-> the-proc ppointer)) + ) + ) + +(defmacro ppointer->handle (pproc) + "convert a ppointer to a handle. assumes the ppointer is valid." + `(let ((the-process (the-as (pointer process) ,pproc))) + (new 'static 'handle :process the-process :pid (if the-process (-> the-process 0 pid))) + ) + ) + +(defmacro process->handle (proc) + "convert a process to a handle. if proc is #f, returns a #f handle." + `(ppointer->handle (process->ppointer (the-as process ,proc))) + ) + + +(deftype state (protect-frame) + ((parent basic) + (code function) + (trans (function object)) + (post function) + (enter function) + (event (function process int symbol event-message-block object)) + ) + ) + +(deftype event-message-block (structure) + ((to-handle uint64) + (to (pointer process) :overlay-at to-handle) + (from-handle uint64) + (from (pointer process) :overlay-at from-handle) + (param uint64 6) + (message symbol) + (num-params int32) + ) + ) + +(deftype event-message-block-array (inline-array-class) + ((data event-message-block :dynamic) + ) + (:methods + (event-message-block-array-method-14 () none) + ) + ) + +(set! (-> event-message-block-array heap-base) (the-as uint 80)) + +(deftype sql-result (array) + ((result-data object :dynamic :offset 16) + ) + ) + +; (defmethod new sql-result ((allocation symbol) (type-to-make type) (arg0 type) (arg1 int)) +; (let ((v0-0 (object-new allocation type-to-make (the-as int (+ (-> type-to-make size) (* arg0 4)))))) +; (set! (-> v0-0 allocated-length) (the-as int arg0)) +; (set! (-> v0-0 content-type) (the-as type 'error)) +; v0-0 +; ) +; ) + +(defmethod print ((this sql-result)) + (format #t "#(~A" (-> this content-type)) + (dotimes (s5-0 (-> this length)) + (format #t " ~A" (-> this result-data s5-0)) + ) + (format #t ")") + this + ) + +(define *sql-result* (the-as sql-result #f)) + +;; failed to figure out what this is: +0 + + + +(defmacro defbehavior (name process-type bindings &rest body) + "define a new behavior. This is simply a function where self is bound to the process register, + which is assumed to have type process-type." + (if (and + (> (length body) 1) ;; more than one thing in function + (string? (first body)) ;; first thing is a string + ) + ;; then it's a docstring and we ignore it. + `(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@(cdr body))) + ;; otherwise don't ignore it. + `(define ,name (lambda :name ,name :behavior ,process-type ,bindings ,@body)) + ) + ) + +(defmacro process-stack-used (proc) + "get how much stack the top thread of a process has used." + `(- (the int (-> ,proc top-thread stack-top)) + (the int (-> ,proc top-thread sp)) + ) + ) + +(defmacro process-stack-size (proc) + "get how much stack the top thread of a process has" + `(-> ,proc top-thread stack-size) + ) + +(defmacro process-heap-used (proc) + "get how much heap a process has used." + `(- (-> ,proc allocated-length) + (- (the int (-> ,proc heap-top)) + (the int (-> ,proc heap-cur)) + ) + ) + ) + +(defmacro process-heap-size (proc) + "get how much heap a process has" + `(the int (-> ,proc allocated-length)) + ) + +(defmacro break () + "crash the game by dividing by 0." + `(/ 0 0) + ) + +(defmacro with-pp (&rest body) + "execute the body with pp bound to the current process register." + `(rlet ((pp :reg r13 :reset-here #t :type process)) + ,@body) + ) + +(defconstant PP (with-pp pp)) + +(defmacro process-mask? (mask enum-value) + "Are any of the given bits set in the process mask?" + `(!= 0 (logand ,mask (process-mask ,enum-value))) + ) + +(defmacro process-mask-set! (mask &rest enum-value) + "Set the given bits in the process mask" + `(logior! ,mask (process-mask ,@enum-value)) + ) + +(defmacro process-mask-clear! (mask &rest enum-value) + "Clear the given bits in the process mask." + `(logclear! ,mask (process-mask ,@enum-value)) + ) + +(defmacro suspend () + "suspend the current process, to be resumed on the next frame." + `(rlet ((pp :reg r13 :reset-here #t)) + ;; debug check for stack overflow here, where we can easily print the process name. + (#when (or KERNEL_DEBUG) + (rlet ((sp :reg rsp :reset-here #t :type int) + (off :reg r15 :type uint)) + (let* ((sp-goal (- sp off)) + (stack-top-goal (-> (the process pp) top-thread stack-top)) + (stack-used (&- stack-top-goal sp-goal)) + (stack-size (-> (the process pp) top-thread stack-size)) + ) + (when (> stack-used stack-size) + (format 0 "ERROR: suspend called without enough stack in proc:~%~A~%Stack: ~D/~D~%" pp stack-used stack-size) + ) + ) + ) + ) + ;; set to the current thread + (set! pp (-> (the process pp) top-thread)) + ;; call the suspend hook (put nothing as the argument) + ((-> (the cpu-thread pp) suspend-hook) (the cpu-thread 0)) + ;; the kernel will set pp (possibly to a new value, if we've been relocated) on resume. + ) + ) + +(defmacro process-deactivate () + "deactivate (kill) the current process" + `(rlet ((pp :reg r13 :reset-here #t :type process)) + (deactivate pp) + ) + ) + +;; Some assembly functions in GOAL are ported to C++, then accessed from GOAL using these mips2c macros. +(defmacro def-mips2c (name type) + "Define a mips2c object (typically a function)." + `(begin + (define-extern ,name ,type) + (set! ,name (the-as ,type (__pc-get-mips2c ,(symbol->string name)))) + ) + ) + +(defmacro defmethod-mips2c (name method-id method-type) + "Define a mips2c method." + `(method-set! ,method-type ,method-id (__pc-get-mips2c ,name)) + ) + +(defmacro kheap-alloc (heap size) + "allocate space for a kheap" + `(let ((heap ,heap) (size ,size)) + (set! (-> heap base) (malloc 'global size)) + (set! (-> heap current) (-> heap base)) + (set! (-> heap top-base) (&+ (-> heap base) size)) + (set! (-> heap top) (-> heap top-base)) + ) + ) + +(defmacro kheap-reset (heap) + "reset the kheap, so you can use its memory again" + `(let ((heap ,heap)) + (set! (-> heap current) (-> heap base)) + ) + ) + +(defmacro scratchpad-object (type &key (offset 0)) + "Access an object on the scratchpad." + `(the-as ,type (&+ *fake-scratchpad-data* ,offset)) + ) + +(defmacro scratchpad-ptr (type &key (offset 0)) + "Create a pointer to an object on the scratchpad." + `(the-as (pointer ,type) (&+ *fake-scratchpad-data* ,offset)) + ) + +(defmacro current-time () + `(-> PP clock frame-counter) + ) + +(defmacro seconds-per-frame () + `(-> PP clock seconds-per-frame) + ) + +(defmacro seconds-per-frame-high-fps () + "Macro for assuming a 16.6 ms frame time at higher frame rates." + `(if (= (get-video-mode) 'custom) + 0.016666668 + (-> PP clock seconds-per-frame) + ) + ) + +(defmacro set-time! (time) + `(set! ,time (current-time)) + ) + +(defmacro time-elapsed? (time duration) + `(>= (- (current-time) ,time) ,duration) + ) + diff --git a/goal_src/jak3/kernel/gkernel.gc b/goal_src/jak3/kernel/gkernel.gc index 0f60f426f3..1220cec7a6 100644 --- a/goal_src/jak3/kernel/gkernel.gc +++ b/goal_src/jak3/kernel/gkernel.gc @@ -6,4 +6,25 @@ ;; dgos: KERNEL ;; DECOMP BEGINS +(define *use-old-listener-print* #f) +(define *kernel-version* (the binteger (logior (ash *kernel-major-version* 16) *kernel-minor-version*))) +(defun kernel-dispatcher () + "Temporary kernel dispatcher for now." + + ;; execute the listener function, if we got one. + (when *listener-function* + (+! *enable-method-set* 1) ;; allow out-of-order method definitions (slower) + ;; (let ((result (reset-and-call (-> *listener-process* main-thread) *listener-function*))) ;; run function! + (let ((result (*listener-function*))) + ;; print result. + (if *use-old-listener-print* + (format #t "~D~%" result result result) + (format #t "~D #x~X ~F ~A~%" result result result result) + ) + ) + ;; clear pending function + (set! *listener-function* #f) + (+! *enable-method-set* -1) + ) + ) \ No newline at end of file diff --git a/goalc/emitter/ObjectGenerator.cpp b/goalc/emitter/ObjectGenerator.cpp index 2f154829e0..50080b9d90 100644 --- a/goalc/emitter/ObjectGenerator.cpp +++ b/goalc/emitter/ObjectGenerator.cpp @@ -493,10 +493,7 @@ void ObjectGenerator::emit_link_type_pointer(int seg, const TypeSystem* ts) { out.push_back(ts->get_type_method_count(rec.first)); break; case GameVersion::Jak2: - // the linker/intern_type functions do the +3. - out.push_back(ts->get_type_method_count(rec.first) / 4); - break; - case GameVersion::Jak3: + case GameVersion::Jak3: // jak3 opengoal uses same format as jak2 for code. // the linker/intern_type functions do the +3. out.push_back(ts->get_type_method_count(rec.first) / 4); break; diff --git a/test/goalc/test_type_consistency.cpp b/test/goalc/test_type_consistency.cpp index 21fb47d078..3671302dc3 100644 --- a/test/goalc/test_type_consistency.cpp +++ b/test/goalc/test_type_consistency.cpp @@ -17,7 +17,7 @@ void add_jak2_expected_type_mismatches(Compiler& c) { c.add_ignored_type_definition("editable-plane"); } -void add_jak3_expected_type_mismatches(Compiler& c) {} +void add_jak3_expected_type_mismatches(Compiler& /*c*/) {} TEST(Jak1TypeConsistency, MANUAL_TEST_TypeConsistencyWithBuildFirst) { Compiler compiler(GameVersion::Jak1);