diff --git a/common/goal_constants.h b/common/goal_constants.h index c9d9ad83dc..2f7a8ccdee 100644 --- a/common/goal_constants.h +++ b/common/goal_constants.h @@ -1,6 +1,7 @@ #pragma once -#include "common_types.h" +#include "common/common_types.h" +#include "common/versions.h" constexpr s32 BINTEGER_OFFSET = 0; constexpr s32 PAIR_OFFSET = 2; @@ -15,6 +16,8 @@ constexpr int ARRAY_DATA_OFFSET = 12; // not including type tag */ // constexpr s32 GOAL_MAX_SYMBOLS = 8192; // this MUST be a multiple of 2!! // constexpr s32 SYM_INFO_OFFSET = 8167 * 8 - 4; + +namespace jak1 { constexpr s32 GOAL_MAX_SYMBOLS = 16384; constexpr s32 SYM_INFO_OFFSET = GOAL_MAX_SYMBOLS * 8 - 4; @@ -35,6 +38,25 @@ constexpr int bits_for_sym() { return b + 1; } static_assert(bits_for_sym() != -1, "symbol table invalid length"); +} // namespace jak1 + +namespace jak2 { +// 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; +} // namespace jak2 + +constexpr s32 max_symbols(GameVersion version) { + switch (version) { + case GameVersion::Jak1: + return jak1::GOAL_MAX_SYMBOLS; + case GameVersion::Jak2: + return jak2::GOAL_MAX_SYMBOLS; + } +} enum class RegClass { GPR_64, FLOAT, INT_128, VECTOR_FLOAT, INVALID }; diff --git a/common/symbols.h b/common/symbols.h index 8ab1ef323a..0eae391907 100644 --- a/common/symbols.h +++ b/common/symbols.h @@ -83,10 +83,71 @@ constexpr int FIX_FIXED_SYM_END_OFFSET = 0x1d0; namespace jak2_symbols { constexpr int FIX_SYM_EMPTY_CAR = -0x8; -constexpr int FIX_SYM_EMPTY_PAIR = -0x7; +constexpr int FIX_SYM_EMPTY_PAIR = -0x6; 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_FIXED_SYM_END_OFFSET = 0xec; + } // namespace jak2_symbols constexpr int false_symbol_offset() { @@ -102,11 +163,12 @@ constexpr int true_symbol_offset(GameVersion version) { } } -constexpr int empty_pair_offset(GameVersion version) { +constexpr int empty_pair_offset_from_s7(GameVersion version) { switch (version) { case GameVersion::Jak1: return jak1_symbols::FIX_SYM_EMPTY_PAIR; case GameVersion::Jak2: - return jak2_symbols::FIX_SYM_EMPTY_PAIR; + // minus 1 for the symbol table pointer's offset. + return jak2_symbols::FIX_SYM_EMPTY_PAIR - 1; } } \ No newline at end of file diff --git a/common/versions.h b/common/versions.h index b7e99e9af2..c2878f7afa 100644 --- a/common/versions.h +++ b/common/versions.h @@ -23,7 +23,11 @@ constexpr u32 TX_PAGE_VERSION = 7; namespace jak2 { constexpr u32 ART_FILE_VERSION = 7; -} +constexpr u32 DGO_FILE_VERSION = 1; +constexpr u32 LEVEL_FILE_VERSION = 36; +constexpr u32 TX_PAGE_VERSION = 8; + +} // namespace jak2 } // namespace versions @@ -35,4 +39,11 @@ constexpr int KERNEL_VERSION_MINOR = 0; constexpr int IRX_VERSION_MAJOR = 2; constexpr int IRX_VERSION_MINOR = 0; -enum class GameVersion { Jak1 = 1, Jak2 = 2 }; \ No newline at end of file +enum class GameVersion { Jak1 = 1, Jak2 = 2 }; + +template +struct PerGameVersion { + constexpr PerGameVersion(T jak1, T jak2) : data{jak1, jak2} {} + constexpr T operator[](GameVersion v) const { return data[(int)v - 1]; } + T data[2]; +}; \ No newline at end of file diff --git a/decompiler/analysis/analyze_inspect_method.cpp b/decompiler/analysis/analyze_inspect_method.cpp index ed44d90e37..4084d0338e 100644 --- a/decompiler/analysis/analyze_inspect_method.cpp +++ b/decompiler/analysis/analyze_inspect_method.cpp @@ -260,7 +260,7 @@ FieldPrint get_field_print(const std::string& str) { int get_start_idx(Function& function, LinkedObjectFile& file, TypeInspectorResult* result, - const std::string& parent_type, + const std::string& /*parent_type*/, const std::string& type_name, Env& env) { if (function.basic_blocks.size() != 5) { @@ -957,7 +957,7 @@ std::string TypeInspectorResult::print_as_deftype(StructureType* old_game_type) std::string inspect_top_level_symbol_defines(std::unordered_set& already_seen, Function& top_level, - LinkedObjectFile& file, + LinkedObjectFile& /*file*/, DecompilerTypeSystem& dts, DecompilerTypeSystem& previous_game_ts) { if (!top_level.ir2.atomic_ops) { diff --git a/decompiler/analysis/atomic_op_builder.cpp b/decompiler/analysis/atomic_op_builder.cpp index b78af1c05c..fdf2de9c4e 100644 --- a/decompiler/analysis/atomic_op_builder.cpp +++ b/decompiler/analysis/atomic_op_builder.cpp @@ -569,7 +569,8 @@ std::unique_ptr convert_daddiu_1(const Instruction& i0, int idx, GameV // get symbol pointer return std::make_unique( make_dst_var(i0, idx), SimpleAtom::make_sym_ptr(i0.get_src(1).get_sym()).as_expr(), idx); - } else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(empty_pair_offset(version))) { + } else if (i0.get_src(0).is_reg(rs7()) && + i0.get_src(1).is_imm(empty_pair_offset_from_s7(version))) { // get empty pair return std::make_unique(make_dst_var(i0, idx), SimpleAtom::make_empty_list().as_expr(), idx); diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 5c836c3657..9ae6b249b4 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -23,20 +23,39 @@ set(RUNTIME_SOURCE sce/sif_ee_memcard.cpp sce/iop.cpp sce/stubs.cpp + kernel/common/fileio.cpp + kernel/common/kboot.cpp + kernel/common/kdgo.cpp + kernel/common/kdsnetm.cpp + kernel/common/klink.cpp + kernel/common/klisten.cpp + kernel/common/kmachine.cpp + kernel/common/kmalloc.cpp + kernel/common/kmemcard.cpp + kernel/common/kprint.cpp + kernel/common/kscheme.cpp + kernel/common/ksocket.cpp + kernel/common/ksound.cpp + kernel/jak1/fileio.cpp + kernel/jak1/kboot.cpp + kernel/jak1/kdgo.cpp + kernel/jak1/klink.cpp + kernel/jak1/klisten.cpp + kernel/jak1/kmachine.cpp + kernel/jak1/kprint.cpp + kernel/jak1/kscheme.cpp + kernel/jak1/ksound.cpp + kernel/jak2/fileio.cpp + kernel/jak2/kboot.cpp + kernel/jak2/kdgo.cpp + kernel/jak2/klink.cpp + kernel/jak2/klisten.cpp + kernel/jak2/kmachine.cpp + kernel/jak2/kmalloc.cpp + kernel/jak2/kprint.cpp + kernel/jak2/kscheme.cpp + kernel/jak2/ksound.cpp kernel/asm_funcs.asm - kernel/fileio.cpp - kernel/kboot.cpp - kernel/kdgo.cpp - kernel/kdsnetm.cpp - kernel/klink.cpp - kernel/klisten.cpp - kernel/kmachine.cpp - kernel/kmalloc.cpp - kernel/kmemcard.cpp - kernel/kprint.cpp - kernel/kscheme.cpp - kernel/ksocket.cpp - kernel/ksound.cpp mips2c/mips2c_table.cpp mips2c/functions/bones.cpp mips2c/functions/collide_cache.cpp diff --git a/game/common/dgo_rpc_types.h b/game/common/dgo_rpc_types.h index 18d15a2fc6..0890a3407e 100644 --- a/game/common/dgo_rpc_types.h +++ b/game/common/dgo_rpc_types.h @@ -6,8 +6,9 @@ */ #include "common/common_types.h" +#include "common/versions.h" -constexpr int DGO_RPC_ID = 0xdeb4; +constexpr PerGameVersion DGO_RPC_ID(0xdeb4, 0xfab3); constexpr int DGO_RPC_CHANNEL = 3; constexpr int DGO_RPC_LOAD_FNO = 0; constexpr int DGO_RPC_LOAD_NEXT_FNO = 1; diff --git a/game/common/loader_rpc_types.h b/game/common/loader_rpc_types.h index 1deee886fa..c7dc7d420b 100644 --- a/game/common/loader_rpc_types.h +++ b/game/common/loader_rpc_types.h @@ -5,5 +5,7 @@ * Types used for the Loader Remote Procedure Call between the EE and the IOP */ -constexpr int LOADER_RPC_ID = 0xdeb2; +#include "common/versions.h" + +constexpr PerGameVersion LOADER_RPC_ID(0xdeb2, 0xfab1); constexpr int LOADER_RPC_CHANNEL = 1; diff --git a/game/common/play_rpc_types.h b/game/common/play_rpc_types.h index 72521275a0..b9bd6039c2 100644 --- a/game/common/play_rpc_types.h +++ b/game/common/play_rpc_types.h @@ -5,6 +5,7 @@ * Types used for the play Remote Procedure Call between the EE and the IOP. * Note that PLAY and PLAYER are different. */ +#include "common/versions.h" -constexpr int PLAY_RPC_ID = 0xdeb6; +constexpr PerGameVersion PLAY_RPC_ID(0xdeb6, 0xfab5); constexpr int PLAY_RPC_CHANNEL = 5; diff --git a/game/common/player_rpc_types.h b/game/common/player_rpc_types.h index 562666e4ca..6e7dfa0503 100644 --- a/game/common/player_rpc_types.h +++ b/game/common/player_rpc_types.h @@ -5,6 +5,7 @@ * Types used for the player Remote Procedure Call between the EE and the IOP. * Note that PLAY and PLAYER are different. */ +#include "common/versions.h" -constexpr int PLAYER_RPC_ID = 0xdeb1; +constexpr PerGameVersion PLAYER_RPC_ID(0xdeb1, 0xfab0); constexpr int PLAYER_RPC_CHANNEL = 0; diff --git a/game/common/ramdisk_rpc_types.h b/game/common/ramdisk_rpc_types.h index bc02176a88..2a990b5f03 100644 --- a/game/common/ramdisk_rpc_types.h +++ b/game/common/ramdisk_rpc_types.h @@ -6,8 +6,9 @@ */ #include "common/common_types.h" +#include "common/versions.h" -constexpr int RAMDISK_RPC_ID = 0xdeb3; +constexpr PerGameVersion RAMDISK_RPC_ID(0xdeb3, 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 b2da99e0f8..d461176cd5 100644 --- a/game/common/str_rpc_types.h +++ b/game/common/str_rpc_types.h @@ -1,10 +1,11 @@ #pragma once #include "common/common_types.h" +#include "common/versions.h" #include "game/common/overlord_common.h" -constexpr int STR_RPC_ID = 0xdeb5; +constexpr PerGameVersion STR_RPC_ID(0xdeb5, 0xfab4); constexpr int STR_RPC_CHANNEL = 4; struct RPC_Str_Cmd { diff --git a/game/graphics/gfx.cpp b/game/graphics/gfx.cpp index a495bafce0..b1cecce2c4 100644 --- a/game/graphics/gfx.cpp +++ b/game/graphics/gfx.cpp @@ -16,7 +16,7 @@ #include "common/util/FileUtil.h" #include "game/common/file_paths.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/common/kscheme.h" #include "game/kernel/svnrev.h" #include "game/runtime.h" #include "game/system/newpad.h" diff --git a/game/graphics/gfx.h b/game/graphics/gfx.h index 3b82338674..e4bbeb11c6 100644 --- a/game/graphics/gfx.h +++ b/game/graphics/gfx.h @@ -11,7 +11,7 @@ #include "common/common_types.h" -#include "game/kernel/kboot.h" +#include "game/kernel/common/kboot.h" #include "game/system/newpad.h" // forward declarations diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index 2c6c26a7e0..431c51614d 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -22,7 +22,9 @@ #include "third-party/imgui/imgui.h" // for the vif callback -#include "game/kernel/kmachine.h" +#include "game/kernel/common/kmachine.h" +#include "game/runtime.h" + namespace { std::string g_current_render; diff --git a/game/kernel/Ptr.h b/game/kernel/common/Ptr.h similarity index 100% rename from game/kernel/Ptr.h rename to game/kernel/common/Ptr.h diff --git a/game/kernel/common/Symbol4.h b/game/kernel/common/Symbol4.h new file mode 100644 index 0000000000..c423f5bab1 --- /dev/null +++ b/game/kernel/common/Symbol4.h @@ -0,0 +1,17 @@ +#pragma once + +#include "common/common_types.h" +#include "common/util/Assert.h" + +#include "game/kernel/common/Ptr.h" + +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 { + ASSERT_MSG(false, "nyi"); + return nullptr; + } +}; diff --git a/game/kernel/common/fileio.cpp b/game/kernel/common/fileio.cpp new file mode 100644 index 0000000000..753313b393 --- /dev/null +++ b/game/kernel/common/fileio.cpp @@ -0,0 +1,346 @@ +#include "fileio.h" + +#include + +#include "common/common_types.h" +#include "common/util/Assert.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kprint.h" +#include "game/sce/sif_ee.h" + +using namespace ee; + +// buffer for file paths. This might be static char buffer[512]. Maybe 633 is the line number? +char buffer_633[512]; + +void fileio_init_globals() { + memset(buffer_633, 0, 512); +} + +/*! + * Return pointer to null terminator of string. + * const is for losers. + * DONE, EXACT + */ +char* strend(char* str) { + while (*str) + str++; + return str; +} + +/*! + * An implementation of Huffman decoding. + * In this limited decoder, your data must have lower two bits equal to zero. + * @param loc_ptr pointer to pointer to data to read (will be modified to point to next word) + * @return decoded word + * UNUSED, EXACT + */ +u32 ReadHufWord(u8** loc_ptr) { + u8* loc = *loc_ptr; // pointer to data to read + u32 value = *(u32*)loc; // read word + u8* next_loc = loc + 1; // next data to read + u32 length = value & 3; // length of word is stored in lower two bits. + switch (length) { + case 0: // already all set. + break; + + case 1: + value = (value & 0xfc) | (loc[1] << 8); + next_loc = loc + 2; + break; + + case 2: + value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10); + next_loc = loc + 3; + break; + + case 3: + value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10) | (loc[3] << 0x18); + next_loc = loc + 4; + break; + + default: + ASSERT(false); + } + + // update location pointer + *loc_ptr = next_loc; + return value; +} + +/*! + * Copy a string from src to dst. The null terminator is copied too. + * This is identical to normal strcpy. + * DONE, EXACT + */ +void kstrcpy(char* dst, const char* src) { + char* dst_ptr = dst; + const char* src_ptr = src; + + while (*src_ptr != 0) { + *dst_ptr = *src_ptr; + src_ptr++; + dst_ptr++; + } + *dst_ptr = 0; +} + +/*! + * Copy a string from src to dst, making all letters upper case. + * The null terminator is copied too. + * DONE, EXACT + */ +void kstrcpyup(char* dst, const char* src) { + while (*src) { + char c = *src; + if (c >= 'a' && c <= 'z') { // A-Z,a-z + c -= 0x20; + } + *dst = c; + dst++; + src++; + } + *dst = 0; +} + +/*! + * Concatenate two strings. Src is added to dest. + * The new string is null terminated. No bounds checking is done. + * DONE, EXACT + */ +void kstrcat(char* dest, const char* src) { + // seek to end of first string + while (*dest) { + dest++; + } + // copy second string + while (*src) { + *dest = *src; + src++; + dest++; + } + // null terminate + *dest = 0; +} + +/*! + * Concatenate two strings with a maximum length for the resulting string + * The maximum length should be larger than the length of the original string. + * The resulting string will be truncated when it reaches the given length. + * The null terminator is added, but doesn't count toward the length. + * DONE, EXACT + */ +void kstrncat(char* dest, const char* src, s32 count) { + // seek to null terminator of first string, count length + s32 i = 0; + while (*dest) { + dest++; + i++; + } + + // append second string, not exceeding length + while (*src && (i < count)) { + *dest = *src; + src++; + dest++; + i++; + } + + // null terminate + *dest = 0; +} + +/*! + * Insert the pad char at the beginning of a string, count times. + * DONE, EXACT + */ +char* kstrinsert(char* str, char pad, s32 count) { + // shift string+null terminator to the right. + s32 len = strlen(str); + while (len > -1) { + str[len + count] = str[len]; + len--; + } + + // pad + len = 0; + while (len < count) { + str[len++] = pad; + } + return str; +} + +/*! + * Get filename from path. + * This function is renamed to basename_goal so it doesn't conflict with "basename" that is + * already defined on my computer. + * For example: + * a/b/c.e will return c.e + * a\b\c.e will return c.e + * asdf.asdf will return asdf.asdf + * DONE, EXACT + */ +char* basename_goal(char* s) { + char* input = s; + char* pt = s; + + // seek to end + for (;;) { + char c = *pt; + if (c) { + pt++; + } else { + break; + } + } + + /* Original code, has memory bug. + // back up... + for (;;) { + if (pt < input) { + return input; + } + pt--; + char c = *pt; + // until we hit a slash. + if (c == '\\' || c == '/') { // slashes + return pt + 1; // and return one past + } + } + */ + + // back up... + for (;;) { + if (pt <= input) { + return input; + } + pt--; + char c = *pt; + // until we hit a slash. + if (c == '\\' || c == '/') { // slashes + return pt + 1; // and return one past + } + } +} + +/*! + * Does the file exist? No. It doesn't. + * @return 0 always, even if the file exists. + * DONE, EXACT, UNUSED + */ +u32 FileExists(const char* name) { + (void)name; + return 0; +} + +/*! + * Does nothing. Likely is supposed to delete a file. + * @param name + * DONE, EXACT, UNUSED + */ +void FileDelete(const char* name) { + (void)name; +} + +/*! + * Does nothing. Likely is supposed to copy a file. + * @param a + * @param b + * DONE, EXACT, UNUSED + */ +void FileCopy(const char* a, const char* b) { + (void)a; + (void)b; +} + +/*! + * Determine the file length in bytes. + * DONE, EXACT + */ +s32 FileLength(char* filename) { + s32 fd = sceOpen(filename, SCE_RDONLY); + if (fd < 0) { + MsgErr("dkernel: file length !open \'%s\' (%d)\n", filename, fd); + sceClose(fd); + return 0xfffffffb; + } else { + s32 rv = sceLseek(fd, 0, SCE_SEEK_END); + sceClose(fd); + return rv; + } +} + +/*! + * Load a file into memory + * @param name : file name + * @param heap : heap to allocate into, if memory is null + * @param memory : memory to load into. If null, allocates on the given kheap (with 64 extra bytes) + * @param malloc_flags : flags for the kmalloc + * @param size_out : file size is written here, if it's not null + * @return pointer to file data + * DONE, EXACT + */ +Ptr FileLoad(char* name, Ptr heap, Ptr memory, u32 malloc_flags, s32* size_out) { + s32 fd = sceOpen(name, SCE_RDONLY); + if (fd < 0) { + MsgErr("dkernel: file read !open \'%s\' (%d)\n", name, fd); + sceClose(fd); + return Ptr(0xfffffffb); + } + + // determine size + s32 initial_pos = sceLseek(fd, 0, SCE_SEEK_CUR); + s32 size = sceLseek(fd, 0, SCE_SEEK_END); + sceLseek(fd, initial_pos, SCE_SEEK_SET); + + if (size > 0) { + if (memory.offset == 0) { + memory = kmalloc(heap, size + 0x40, malloc_flags, name); + } + if (memory.offset == 0) { + MsgErr("dkernel: mem full for file read: '%s' (%d bytes)\n", name, size); + return Ptr(0xfffffffd); + } + + s32 read_amount = sceRead(fd, memory.c(), size); + if (read_amount == size) { + sceClose(fd); + if (size_out) + *size_out = size; + return memory; + } else { + MsgErr("dkernel: can't read full file (%d of %d): '%s'\n", read_amount, size, name); + sceClose(fd); + return Ptr(0xfffffffb); + } + } else { + return Ptr(0); + } +} + +/*! + * Write a file. + * DONE, EXACT + */ +s32 FileSave(char* name, u8* data, s32 size) { + s32 fd = sceOpen(name, SCE_WRONLY | SCE_TRUNC | SCE_CREAT); + if (fd < 0) { + MsgErr("dkernel: file write !open '%s'\n", name); + sceClose(fd); + return 0xfffffffa; + } + + if (size != 0) { + s32 written = sceWrite(fd, data, size); + if (written != size) { + MsgErr("dkernel: can't write full file '%s'\n", name); + sceClose(fd); + return 0xfffffffa; + } + } + + sceClose(fd); + return 0; +} diff --git a/game/kernel/fileio.h b/game/kernel/common/fileio.h similarity index 71% rename from game/kernel/fileio.h rename to game/kernel/common/fileio.h index 0d22a3c340..55bc258a20 100644 --- a/game/kernel/fileio.h +++ b/game/kernel/common/fileio.h @@ -1,18 +1,24 @@ #pragma once -/*! - * @file fileio.h - * GOAL Low-Level File I/O and String Utilities - */ - -#ifndef RUNTIME_FILEIO_H -#define RUNTIME_FILEIO_H - -#include "Ptr.h" -#include "kmalloc.h" - #include "common/common_types.h" +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kmalloc.h" + +constexpr char FOLDER_PREFIX[] = ""; + +void fileio_init_globals(); +char* strend(char* str); +void kstrcpy(char* dst, const char* src); +char* basename_goal(char* s); +void kstrcpyup(char* dst, const char* src); +void kstrcat(char* dest, const char* src); +char* kstrinsert(char* str, char pad, s32 count); +void kstrncat(char* dest, const char* src, s32 count); +Ptr FileLoad(char* name, Ptr heap, Ptr memory, u32 malloc_flags, s32* size_out); + +extern char buffer_633[512]; + // GOAL File Types enum GoalFileType { LISTENER_TO_KERNEL_FILE_TYPE = 1, @@ -22,7 +28,7 @@ enum GoalFileType { LISTENER_TO_KERNEL_LOCK_FILE_TYPE = 5, KERNEL_TO_LISTENER_LOCK_FILE_TYPE = 6, IOP_MODULE_FILE_TYPE = 8, - DATA_FILE_TYPE = 0x20, + DATA_FILE_TYPE = 0x20, // called FINAL in jak2 TX_PAGE_FILE_TYPE = 0x21, JA_FILE_TYPE = 0x22, JG_FILE_TYPE = 0x23, @@ -42,29 +48,12 @@ enum GoalFileType { GAME_CGO_FILE_TYPE = 0x39, CNT_FILE_TYPE = 0x3a, RES_FILE_TYPE = 0x3b, + SND_BNK_FILE_TYPE = 0x3c, + MUSIC_BNK_FILE_TYPE = 0x3d, + VAG_FILE_TYPE = 0x3e, + MISC_FILE_TYPE = 0x3f, // jak2 only + MAP_FILE_TYPE = 0x40, // jak2 only REFPLANT_FILE_TYPE = 0x301, // added this, allows access directly to out/iso from fileio. ISO_FILE_TYPE = 0x302 -}; - -constexpr char FOLDER_PREFIX[] = ""; - -char* strend(char* str); -u32 ReadHufWord(u8** loc_ptr); -void kstrcpy(char* dst, const char* src); -void kstrcpyup(char* dst, const char* src); -void kstrcat(char* dest, const char* src); -void kstrncat(char* dest, const char* src, s32 count); -char* kstrinsert(char* str, char pad, s32 count); -char* basename_goal(char* s); -char* DecodeFileName(const char* name); -char* MakeFileName(int type, const char* name, int new_string); -u32 FileExists(const char* name); -void FileDelete(const char* name); -void FileCopy(const char* a, const char* b); -s32 FileLength(char* filename); -Ptr FileLoad(char* name, Ptr heap, Ptr memory, u32 malloc_flags, s32* size_out); -s32 FileSave(char* name, u8* data, s32 size); -void fileio_init_globals(); - -#endif // RUNTIME_FILEIO_H +}; \ No newline at end of file diff --git a/game/kernel/common/kboot.cpp b/game/kernel/common/kboot.cpp new file mode 100644 index 0000000000..433c6fb303 --- /dev/null +++ b/game/kernel/common/kboot.cpp @@ -0,0 +1,37 @@ +#include "kboot.h" + +#include + +// Set to 1 to kill GOAL kernel +RuntimeExitStatus MasterExit; + +// Set to 1 to load game engine after boot automatically +u32 DiskBoot; + +// Set to 1 to enable debug heap +u32 MasterDebug; + +// Set to 1 to load debug code +u32 DebugSegment; + +u32 MasterUseKernel; + +// Level to load on boot +char DebugBootLevel[64]; + +// Pass to GOAL kernel on boot +char DebugBootMessage[64]; + +// game configuration +MasterConfig masterConfig; + +void kboot_init_globals_common() { + MasterExit = RuntimeExitStatus::RUNNING; + DiskBoot = 0; + MasterDebug = 1; + DebugSegment = 1; + MasterUseKernel = 1; + strcpy(DebugBootLevel, "#f"); // no specified level + strcpy(DebugBootMessage, "play"); // play mode, the default retail mode + memset(&masterConfig, 0, sizeof(MasterConfig)); +} \ No newline at end of file diff --git a/game/kernel/kboot.h b/game/kernel/common/kboot.h similarity index 63% rename from game/kernel/kboot.h rename to game/kernel/common/kboot.h index d7535f51bf..458a2680a4 100644 --- a/game/kernel/kboot.h +++ b/game/kernel/common/kboot.h @@ -1,27 +1,10 @@ #pragma once - -/*! - * @file kboot.h - * GOAL Boot. Contains the "main" function to launch GOAL runtime. - */ - #include "common/common_types.h" #define GAME_TERRITORY_SCEA 0 // sony america #define GAME_TERRITORY_SCEE 1 // sony europe #define GAME_TERRITORY_SCEI 2 // sony inc. (japan) -struct MasterConfig { - u16 language; //! GOAL language 0 - u16 aspect; //! SCE_ASPECT 2 - u16 disable_game; // 4 - u16 inactive_timeout; // todo 6 - u16 timeout; // todo 8 - u16 volume; // todo 12 - - u16 disable_sound = 0; // added. disables all sound code. -}; - enum class RuntimeExitStatus { RUNNING = 0, RESTART_RUNTIME = 1, @@ -36,51 +19,42 @@ enum class VideoMode { FPS150 = 3, }; -// Video Mode that's set based on display refresh rate on boot -extern VideoMode BootVideoMode; - -// Level to load on boot -extern char DebugBootLevel[64]; - -// Pass to GOAL kernel on boot -extern char DebugBootMessage[64]; - -// Set to 1 to kill GOAL kernel +// Set to nonzero to kill GOAL kernel extern RuntimeExitStatus MasterExit; +// Set to 1 to load game engine after boot automatically +extern u32 DiskBoot; + // Set to 1 to enable debug heap extern u32 MasterDebug; // Set to 1 to load debug code extern u32 DebugSegment; -// Set to 1 to load game engine after boot automatically -extern u32 DiskBoot; +// Level to load on boot +extern char DebugBootLevel[64]; + +// Pass to GOAL kernel on boot +extern char DebugBootMessage[64]; + +// Added in PC port, option to run listener functions without the kernel for debugging +extern u32 MasterUseKernel; + +struct MasterConfig { + u16 language; //! GOAL language 0 + u16 aspect; //! SCE_ASPECT 2 + u16 disable_game; // 4 + u16 inactive_timeout; // todo 6 + u16 timeout; // todo 8 + u16 jak2_only_unused; // seems unused 12 + u16 volume; // todo 14 in jak 2, 12 in jak 1 + + u16 disable_sound = 0; // added. disables all sound code. +}; extern MasterConfig masterConfig; /*! * Initialize global variables for kboot */ -void kboot_init_globals(); - -/*! - * Launch the GOAL Kernel (EE). - * See InitParms for launch argument details. - * @param argc : argument count - * @param argv : argument list - * @return 0 on success, otherwise failure. - */ -s32 goal_main(int argc, const char* const* argv); - -/*! - * Run the GOAL Kernel. - */ -void KernelCheckAndDispatch(); - -/*! - * Stop running the GOAL Kernel. - */ -void KernelShutdown(); - -extern u32 MasterUseKernel; +void kboot_init_globals_common(); \ No newline at end of file diff --git a/game/kernel/kdgo.cpp b/game/kernel/common/kdgo.cpp similarity index 67% rename from game/kernel/kdgo.cpp rename to game/kernel/common/kdgo.cpp index 45d1e33504..a92ebb9877 100644 --- a/game/kernel/kdgo.cpp +++ b/game/kernel/common/kdgo.cpp @@ -1,18 +1,7 @@ -/*! - * @file kdgo.cpp - * Loading DGO Files. Also has some general SIF RPC stuff used for RPCs other than DGO loading. - * DONE! - */ - #include "kdgo.h" #include -#include "fileio.h" -#include "klink.h" -#include "kmalloc.h" -#include "kprint.h" - #include "common/log/log.h" #include "game/common/dgo_rpc_types.h" @@ -21,20 +10,20 @@ #include "game/common/player_rpc_types.h" #include "game/common/ramdisk_rpc_types.h" #include "game/common/str_rpc_types.h" +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kprint.h" #include "game/sce/sif_ee.h" -using namespace ee; - -sceSifClientData cd[6]; //! client data for each IOP Remove Procedure Call. -u16 x[8]; //! stupid temporary for storing a message -u32 sShowStallMsg; //! setting to show a "stalled on iop" 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 +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(cd, 0, sizeof(cd)); memset(x, 0, sizeof(x)); + memset(cd, 0, sizeof(cd)); sShowStallMsg = 1; sLastMsg = nullptr; memset(sMsg, 0, sizeof(sMsg)); @@ -120,6 +109,7 @@ u32 RpcBind(s32 channel, s32 id) { } Msg(6, "kernel: RPC port #%d started [%4.4X]\n", channel, id); // FlushCache(0); + // In Jak 2 they do a sceSifCheckStatRpc, but we can just skip that. // this was not optimized out in Jak 1, but is _almost_ optimized out in Jak 2 and later. u32 i = 0; @@ -144,9 +134,12 @@ u32 RpcBind(s32 channel, s32 id) { * Setup all RPCs */ u32 InitRPC() { - if (!RpcBind(PLAYER_RPC_CHANNEL, PLAYER_RPC_ID) && !RpcBind(LOADER_RPC_CHANNEL, LOADER_RPC_ID) && - !RpcBind(RAMDISK_RPC_CHANNEL, RAMDISK_RPC_ID) && !RpcBind(DGO_RPC_CHANNEL, DGO_RPC_ID) && - !RpcBind(STR_RPC_CHANNEL, STR_RPC_ID) && !RpcBind(PLAY_RPC_CHANNEL, PLAY_RPC_ID)) { + if (!RpcBind(PLAYER_RPC_CHANNEL, PLAYER_RPC_ID[g_game_version]) && + !RpcBind(LOADER_RPC_CHANNEL, LOADER_RPC_ID[g_game_version]) && + !RpcBind(RAMDISK_RPC_CHANNEL, RAMDISK_RPC_ID[g_game_version]) && + !RpcBind(DGO_RPC_CHANNEL, DGO_RPC_ID[g_game_version]) && + !RpcBind(STR_RPC_CHANNEL, STR_RPC_ID[g_game_version]) && + !RpcBind(PLAY_RPC_CHANNEL, PLAY_RPC_ID[g_game_version])) { return 0; } printf("Entering endless loop ... please wait\n"); @@ -159,8 +152,8 @@ u32 InitRPC() { */ void StopIOP() { x[2] = 0x14; // todo - this type and message - // RpcSync(PLAYER_RPC_CHANNEL); - // RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); + // RpcSync(PLAYER_RPC_CHANNEL); + // RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); printf("IOP shut down\n"); // sceDmaSync(0x10009000, 0, 0); printf("DMA shut down\n"); @@ -302,85 +295,4 @@ void LoadDGOTest() { } sShowStallMsg = lastShowStall; -} - -/*! - * 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) { - 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); - link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now! - - // inform IOP we are done - if (!lastObjectLoaded) { - ContinueLoadingDGO(Ptr((heap->current + 0x3f).offset & 0xffffffc0)); - } - } - sShowStallMsg = oldShowStall; -} +} \ No newline at end of file diff --git a/game/kernel/common/kdgo.h b/game/kernel/common/kdgo.h new file mode 100644 index 0000000000..5a75d0b1d0 --- /dev/null +++ b/game/kernel/common/kdgo.h @@ -0,0 +1,25 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" + +s32 RpcCall(s32 rpcChannel, + u32 fno, + bool async, + void* sendBuff, + s32 sendSize, + void* recvBuff, + s32 recvSize); +void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr currentHeap); +Ptr GetNextDGO(u32* lastObjectFlag); +void ContinueLoadingDGO(Ptr heapPtr); +u64 RpcCall_wrapper(void* _args); +u32 RpcBusy(s32 channel); +void RpcSync(s32 channel); +void LoadDGOTest(); +void kdgo_init_globals(); +u32 InitRPC(); +void StopIOP(); + +extern u32 sShowStallMsg; diff --git a/game/kernel/kdsnetm.cpp b/game/kernel/common/kdsnetm.cpp similarity index 87% rename from game/kernel/kdsnetm.cpp rename to game/kernel/common/kdsnetm.cpp index ab8d5d3e0e..0949563e20 100644 --- a/game/kernel/kdsnetm.cpp +++ b/game/kernel/common/kdsnetm.cpp @@ -1,22 +1,10 @@ -/*! - * @file kdsnetm.cpp - * Low-level DECI2 wrapper for ksocket - * DONE! - */ - #include "kdsnetm.h" #include -#include - -#include "kprint.h" - -#include "common/util/Assert.h" +#include "game/kernel/common/kprint.h" #include "game/sce/deci2.h" -#include "game/system/deci_common.h" // todo, reorganize to avoid this include - -using namespace ee; +#include "game/system/deci_common.h" /*! * Current state of the GOAL Protocol @@ -27,7 +15,7 @@ GoalProtoBlock protoBlock; /*! * Initialize global variables for kdsnetm */ -void kdsnetm_init_globals() { +void kdsnetm_init_globals_common() { protoBlock.reset(); } @@ -36,7 +24,7 @@ void kdsnetm_init_globals() { * DONE, EXACT */ void InitGoalProto() { - protoBlock.socket = sceDeci2Open(DECI2_PROTOCOL, &protoBlock, GoalProtoHandler); + protoBlock.socket = ee::sceDeci2Open(DECI2_PROTOCOL, &protoBlock, GoalProtoHandler); if (protoBlock.socket < 0) { MsgErr("gproto: open proto error\n"); } else { @@ -56,7 +44,7 @@ void InitGoalProto() { */ void ShutdownGoalProto() { if (protoBlock.socket > 0) { - sceDeci2Close(protoBlock.socket); + ee::sceDeci2Close(protoBlock.socket); } } @@ -90,7 +78,7 @@ void GoalProtoHandler(int event, int param, void* opt) { if (pb->receive_progress + param <= (int)DEBUG_MESSAGE_BUFFER_SIZE) { // actually get data from DECI2 s32 received = - sceDeci2ExRecv(pb->socket, ((u8*)pb->receive_buffer) + pb->receive_progress, param); + ee::sceDeci2ExRecv(pb->socket, ((u8*)pb->receive_buffer) + pb->receive_progress, param); if (received < 0) { // receive failure @@ -108,21 +96,21 @@ void GoalProtoHandler(int event, int param, void* opt) { } break; - // read is finished! + // read is finished! case DECI2_READDONE: // set last_receive_size to indicate that there is a pending message in the buffer. pb->last_receive_size = pb->receive_progress; pb->receive_progress = 0; break; - // send some data + // send some data case DECI2_WRITE: { // note that we should not attempt to send more than 0xffff bytes at a time, or this will be // wrong. This is correctly checked for prints, but not for outputs. ASSERT(pb->send_remaining < 0xffff); // why and it with 0xffff? Seems like saturation would be better. Either way some data // will be lost, so I guess it doesn't matter. - s32 sent = sceDeci2ExSend(pb->socket, (void*)pb->send_ptr, pb->send_remaining & 0xffff); + s32 sent = ee::sceDeci2ExSend(pb->socket, (void*)pb->send_ptr, pb->send_remaining & 0xffff); if (sent < 0) { // if we got an error, put it in send status, signaling a send error (negative) pb->send_status = sent; @@ -133,7 +121,7 @@ void GoalProtoHandler(int event, int param, void* opt) { } } break; - // done sending! + // done sending! case DECI2_WRITEDONE: if (pb->send_remaining <= 0) { // if we've send everything we want, set status to zero to indicate success @@ -152,7 +140,7 @@ void GoalProtoHandler(int event, int param, void* opt) { case DECI2_CHSTATUS: break; - // other events are undefined, so we just error. + // other events are undefined, so we just error. default: pb->last_receive_size = -1; break; @@ -169,7 +157,7 @@ s32 SendFromBufferD(s32 msg_kind, u64 msg_id, char* data, s32 size) { // wait for send to finish or error first... while (protoBlock.send_status > 0) { // on actual PS2, the kernel will run this in another thread. - LIBRARY_sceDeci2_run_sends(); + ee::LIBRARY_sceDeci2_run_sends(); } // retry at most 10 times until we complete without an error. @@ -197,7 +185,7 @@ s32 SendFromBufferD(s32 msg_kind, u64 msg_id, char* data, s32 size) { header->msg_id = msg_id; // start send! - auto rv = sceDeci2ReqSend(protoBlock.socket, header->deci2_header.dst); + auto rv = ee::sceDeci2ReqSend(protoBlock.socket, header->deci2_header.dst); if (rv < 0) { printf("1sceDeci2ReqSend fail, reason code = %08x\n", rv); return 0xfffffffa; @@ -205,7 +193,7 @@ s32 SendFromBufferD(s32 msg_kind, u64 msg_id, char* data, s32 size) { // wait for send to complete or error. while (protoBlock.send_status > 0) { - LIBRARY_sceDeci2_run_sends(); + ee::LIBRARY_sceDeci2_run_sends(); } // if send completes, exit. Otherwise if there's an error, just try again. diff --git a/game/kernel/kdsnetm.h b/game/kernel/common/kdsnetm.h similarity index 92% rename from game/kernel/kdsnetm.h rename to game/kernel/common/kdsnetm.h index 35179f9d8b..9f86ccd124 100644 --- a/game/kernel/kdsnetm.h +++ b/game/kernel/common/kdsnetm.h @@ -6,13 +6,10 @@ * DONE! */ -#ifndef JAK_KDSNETM_H -#define JAK_KDSNETM_H - -#include "Ptr.h" - #include "common/listener_common.h" +#include "game/kernel/common/Ptr.h" + struct GoalProtoBlock { s32 socket = 0; ListenerMessageHeader* send_buffer = nullptr; @@ -42,7 +39,7 @@ extern GoalProtoBlock protoBlock; /*! * Initialize global variables for kdsnetm */ -void kdsnetm_init_globals(); +void kdsnetm_init_globals_common(); /*! * Register GOAL DECI2 Protocol Driver with DECI2 service @@ -75,5 +72,3 @@ s32 SendFromBufferD(s32 p1, u64 msg_id, char* data, s32 size); * Print GOAL Protocol status */ void GoalProtoStatus(); - -#endif // JAK_KDSNETM_H diff --git a/game/kernel/common/kernel_types.h b/game/kernel/common/kernel_types.h new file mode 100644 index 0000000000..a75be55d03 --- /dev/null +++ b/game/kernel/common/kernel_types.h @@ -0,0 +1,38 @@ +#pragma once + +//! Mirror of cpad-info +struct CPadInfo { + u8 valid; + u8 status; + u16 button0; + u8 rightx; + u8 righty; + u8 leftx; + u8 lefty; + u8 abutton[12]; + u8 dummy[12]; + s32 number; + s32 cpad_file; + u32 button0_abs[3]; + u32 button0_shadow_abs[1]; + u32 button0_rel[3]; + float stick0_dir; + float stick0_speed; + s32 new_pad; + s32 state; + u8 align[6]; + u8 direct[6]; + u8 buzz_val[2]; + u8 __pad[2]; + u64 buzz_time[2]; + u32 buzz; + s32 buzz_act; + s32 change_time; // actually u64 in goal! +}; + +struct FileStream { + u32 flags; + u32 mode; // basic + u32 name; // basic + s32 file; // int32 +}; diff --git a/game/kernel/common/klink.cpp b/game/kernel/common/klink.cpp new file mode 100644 index 0000000000..4a89d7694c --- /dev/null +++ b/game/kernel/common/klink.cpp @@ -0,0 +1,260 @@ +#include "klink.h" + +#include "common/goal_constants.h" +#include "common/symbols.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kscheme.h" + +#include "third-party/fmt/core.h" + +namespace { +// turn on printf's for debugging linking issues. +constexpr bool link_debug_printfs = false; + +bool is_opengoal_object(const void* data) { + auto* header = (const LinkHeaderV2*)data; + return !(header->type_tag == 0xffffffff && (header->version == 2 || header->version == 4)); +} +} // namespace + +// space to store a single in-progress linking state. +link_control saved_link_control; + +// pointer to GOAL *ultimate-memcpy*, if its loaded. +Ptr gfunc_774; + +void klink_init_globals() { + saved_link_control.reset(); + gfunc_774.offset = 0; +} + +/*! + * 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) { + if (is_opengoal_object(object_file.c())) { + // 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; + // not an open goal object. + if (link_debug_printfs) { + printf("Linking GOAL style object\n"); + } + + // initialize + m_object_data = object_file; + kstrcpy(m_object_name, name); + m_object_size = size; + m_heap = heap; + m_flags = flags; + m_entry.offset = 0; + m_heap_top = m_heap->top; + m_keep_debug = false; + m_link_block_ptr = object_file + BASIC_OFFSET; + m_code_size = 0; + m_code_start = object_file; + m_state = 0; + m_segment_process = 0; + + const auto* header = (LinkHeaderV2*)(m_link_block_ptr.c() - 4); + + m_version = header->version; + if (header->version < 4) { + // seek past header + m_object_data.offset += header->length; + m_code_size = m_object_size - header->length; + 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, header->length, KMALLOC_TOP, "link-block"); + auto old_link_block = m_link_block_ptr - BASIC_OFFSET; + + // copy it (was ultimate memcpy) + memmove(new_link_block.c(), old_link_block.c(), header->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 { + auto header_v4 = (const LinkHeaderV4*)header; + auto old_object_data = m_object_data; + m_link_block_ptr = + old_object_data + header_v4->code_size + sizeof(LinkHeaderV4) + BASIC_OFFSET; + m_object_data = old_object_data + sizeof(LinkHeaderV4); + m_code_size = header_v4->code_size; + } + + if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) { + m_keep_debug = true; + } + } +} + +Ptr c_symlink2(Ptr objData, Ptr linkObj, Ptr relocTable) { + u8* relocPtr = relocTable.c(); + Ptr objPtr = objData; + + do { + u8 table_value = *relocPtr; + u32 result = table_value; + u8* next_reloc = relocPtr + 1; + + if (result & 3) { + result = (relocPtr[1] << 8) | table_value; + next_reloc = relocPtr + 2; + if (result & 2) { + result = (relocPtr[2] << 16) | result; + next_reloc = relocPtr + 3; + if (result & 1) { + result = (relocPtr[3] << 24) | result; + next_reloc = relocPtr + 4; + } + } + } + + relocPtr = next_reloc; + objPtr = objPtr + (result & 0xfffffffc); + u32 objValue = *(objPtr.cast()); + if (objValue == 0xffffffff) { + *(objPtr.cast()) = linkObj.offset; + } else { + // I don't think we should hit this ever. + ASSERT(false); + } + } while (*relocPtr); + + return make_ptr(relocPtr + 1); +} \ No newline at end of file diff --git a/game/kernel/klink.h b/game/kernel/common/klink.h similarity index 78% rename from game/kernel/klink.h rename to game/kernel/common/klink.h index 99300399fa..652b6c2f0f 100644 --- a/game/kernel/klink.h +++ b/game/kernel/common/klink.h @@ -1,19 +1,13 @@ #pragma once - -/*! - * @file klink.cpp - * GOAL Linker for x86-64 - * DONE! - */ - #include -#include "Ptr.h" -#include "kmalloc.h" - #include "common/common_types.h" #include "common/link_types.h" +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kscheme.h" + constexpr int LINK_FLAG_OUTPUT_LOAD = 0x1; constexpr int LINK_FLAG_OUTPUT_TRUE = 0x2; constexpr int LINK_FLAG_EXECUTE = 0x4; @@ -21,6 +15,24 @@ constexpr int LINK_FLAG_PRINT_LOGIN = 0x8; //! Note, doesn't actually do anythi constexpr int LINK_FLAG_FORCE_DEBUG = 0x10; constexpr int LINK_FLAG_FORCE_FAST_LINK = 0x20; +// only used in OpenGOAL +struct SegmentInfo { + uint32_t offset; + uint32_t size; +}; + +// only used in OpenGOAL +struct ObjectFileHeader { + uint16_t goal_version_major; + uint16_t goal_version_minor; + uint32_t object_file_version; + uint32_t segment_count; + SegmentInfo link_infos[N_SEG]; + SegmentInfo code_infos[N_SEG]; + uint32_t link_block_length; +}; + +void klink_init_globals(); /*! * Stores the state of the linker. Used for multi-threaded linking, so it can be suspended. */ @@ -47,15 +59,23 @@ struct link_control { int m_table_toggle; 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); - uint32_t work(); - uint32_t work_v3(); - uint32_t work_v2(); - void finish(bool jump_from_c_to_goal); + + // was originally "work" + uint32_t jak1_work(); + uint32_t jak2_work(); + + uint32_t jak1_work_v3(); + uint32_t jak1_work_v2(); + uint32_t jak2_work_v3(); + uint32_t jak2_work_v2(); + void jak1_finish(bool jump_from_c_to_goal); + void jak2_finish(bool jump_from_c_to_goal); void reset() { m_object_data.offset = 0; @@ -72,40 +92,12 @@ struct link_control { m_state = 0; m_segment_process = 0; m_version = 0; + m_busy = false; } }; -// only used in OpenGOAL -struct SegmentInfo { - uint32_t offset; - uint32_t size; -}; - -// only used in OpenGOAL -struct ObjectFileHeader { - uint16_t goal_version_major; - uint16_t goal_version_minor; - uint32_t object_file_version; - uint32_t segment_count; - SegmentInfo link_infos[N_SEG]; - SegmentInfo code_infos[N_SEG]; - uint32_t link_block_length; -}; - void klink_init_globals(); - -u64 link_and_exec_wrapper(u64* args); - -Ptr link_and_exec(Ptr data, - const char* name, - int32_t size, - Ptr heap, - uint32_t flags, - bool jump_from_c_to_goal); - -uint64_t link_begin(u64* args); - -uint64_t link_resume(); -void ultimate_memcpy(void* dst, void* src, uint32_t size); +Ptr c_symlink2(Ptr objData, Ptr linkObj, Ptr relocTable); extern link_control saved_link_control; +extern Ptr gfunc_774; // actually 807 in jak2. diff --git a/game/kernel/common/klisten.cpp b/game/kernel/common/klisten.cpp new file mode 100644 index 0000000000..df321f2508 --- /dev/null +++ b/game/kernel/common/klisten.cpp @@ -0,0 +1,76 @@ +#include "klisten.h" + +#include +#include + +#include "common/listener_common.h" + +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/ksocket.h" + +Ptr print_column; +u32 ListenerStatus; + +void klisten_init_globals() { + print_column.offset = 0; + ListenerStatus = 0; +} + +/*! + * Flush pending messages. If debugging, will send to compiler, otherwise to stdout. + */ +void ClearPending() { + if (!MasterDebug) { + // if we aren't debugging print the print buffer to stdout. + if (PrintPending.offset != 0) { + auto size = strlen(PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); + if (size > 0) { + printf("%s", PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); + } + clear_print(); + } + } else { + if (ListenerStatus) { + if (OutputPending.offset != 0) { + Ptr msg = OutputBufArea.cast() + sizeof(ListenerMessageHeader); + auto size = strlen(msg.c()); + // note - if size is ever greater than 2^16 this will cause an issue. + SendFromBuffer(msg.c(), size); + clear_output(); + } + + if (PrintPending.offset != 0) { + char* msg = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); + auto size = strlen(msg); + while (size > 0) { + // sends larger than 64 kB are broken by the GoalProtoBuffer thing, so they are split + auto send_size = size; + if (send_size > 64000) { + send_size = 64000; + } + SendFromBufferD(2, 0, msg, send_size); + size -= send_size; + msg += send_size; + } + clear_print(); + } + } + } +} + +/*! + * Send an "ack" message. The original game had the AckBufArea which stores "ack", but did not + * calculate the length correctly, so the message would not actually contain the "ack" text. + * The "ack" text is unimportant, as the compiler can recognize the messages as ACK due to the + * ListenerMessageKind::MSG_ACK field. Both the type and msg_id fields are sent, which is enough + * for it to work. + * Note: jak2 sent 0 length acks. + */ +void SendAck() { + if (MasterDebug) { + SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id, + AckBufArea + sizeof(ListenerMessageHeader), + strlen(AckBufArea + sizeof(ListenerMessageHeader))); + } +} diff --git a/game/kernel/common/klisten.h b/game/kernel/common/klisten.h new file mode 100644 index 0000000000..f1f7d30b31 --- /dev/null +++ b/game/kernel/common/klisten.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" + +extern Ptr print_column; +extern u32 ListenerStatus; + +void klisten_init_globals(); +void ClearPending(); +void SendAck(); \ No newline at end of file diff --git a/game/kernel/common/kmachine.cpp b/game/kernel/common/kmachine.cpp new file mode 100644 index 0000000000..b7768ab56a --- /dev/null +++ b/game/kernel/common/kmachine.cpp @@ -0,0 +1,470 @@ +#include "kmachine.h" + +#include "common/global_profiler/GlobalProfiler.h" +#include "common/log/log.h" +#include "common/symbols.h" +#include "common/util/FileUtil.h" +#include "common/util/Timer.h" + +#include "game/graphics/gfx.h" +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kernel_types.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/sce/libcdvd_ee.h" +#include "game/sce/libpad.h" +#include "game/sce/libscf.h" +#include "game/sce/sif_ee.h" + +/*! + * Where does OVERLORD load its data from? + */ +OverlordDataSource isodrv; + +// Get IOP modules from DVD or from dsefilesv +u32 modsrc; + +// Reboot IOP with IOP kernel from DVD/CD on boot +u32 reboot; + +const char* init_types[] = {"fakeiso", "deviso", "iso_cd"}; +u8 pad_dma_buf[2 * SCE_PAD_DMA_BUFFER_SIZE]; + +// added +u32 vif1_interrupt_handler = 0; + +Timer ee_clock_timer; + +void kmachine_init_globals_common() { + memset(pad_dma_buf, 0, sizeof(pad_dma_buf)); + isodrv = fakeiso; // changed. fakeiso is the only one that works in opengoal. + modsrc = 1; + reboot = 1; + vif1_interrupt_handler = 0; + ee_clock_timer = Timer(); +} + +/*! + * Initialize the CD Drive + * DONE, EXACT + */ +void InitCD() { + lg::info("Initializing CD drive. This may take a while..."); + ee::sceCdInit(SCECdINIT); + ee::sceCdMmode(SCECdDVD); + while (ee::sceCdDiskReady(0) == SCECdNotReady) { + lg::debug("Drive not ready... insert a disk!"); + } + lg::debug("Disk type {}\n", ee::sceCdGetDiskType()); +} + +/*! + * Initialize the GS and display the splash screen. + * Not yet implemented. TODO + */ +void InitVideo() {} + +/*! + * Flush caches. Does all the memory, regardless of what you specify + */ +void CacheFlush(void* mem, int size) { + (void)mem; + (void)size; + // FlushCache(0); + // FlushCache(2); +} + +/*! + * Open a new controller pad. + * Set the new_pad flag to 1 and state to 0. + * Prints an error if it fails to open. + */ +u64 CPadOpen(u64 cpad_info, s32 pad_number) { + auto cpad = Ptr(cpad_info).c(); + if (cpad->cpad_file == 0) { + // not open, so we will open it + cpad->cpad_file = + ee::scePadPortOpen(pad_number, 0, pad_dma_buf + pad_number * SCE_PAD_DMA_BUFFER_SIZE); + if (cpad->cpad_file < 1) { + MsgErr("dkernel: !open cpad #%d (%d)\n", pad_number, cpad->cpad_file); + } + cpad->new_pad = 1; + cpad->state = 0; + } + return cpad_info; +} + +/*! + * Not checked super carefully for jak 2, but looks the same + */ +u64 CPadGetData(u64 cpad_info) { + using namespace ee; + auto cpad = Ptr(cpad_info).c(); + auto pad_state = scePadGetState(cpad->number, 0); + if (pad_state == scePadStateDiscon) { + cpad->state = 0; + } + cpad->valid = pad_state | 0x80; + switch (cpad->state) { + // case 99: // functional + default: // controller is functioning as normal + if (pad_state == scePadStateStable || pad_state == scePadStateFindCTP1) { + scePadRead(cpad->number, 0, (u8*)cpad); + // ps2 controllers would send an enabled bit if the button was NOT pressed, but we don't do + // that here. removed code that flipped the bits. + + if (cpad->change_time != 0) { + scePadSetActDirect(cpad->number, 0, cpad->direct); + } + cpad->valid = pad_state; + } + break; + case 0: // unavailable + if (pad_state == scePadStateStable || pad_state == scePadStateFindCTP1) { + auto pad_mode = scePadInfoMode(cpad->number, 0, InfoModeCurID, 0); + if (pad_mode != 0) { + auto vibration_mode = scePadInfoMode(cpad->number, 0, InfoModeCurExID, 0); + if (vibration_mode > 0) { + // vibration supported + pad_mode = vibration_mode; + } + if (pad_mode == 4) { + // controller mode + cpad->state = 40; + } else if (pad_mode == 7) { + // dualshock mode + cpad->state = 70; + } else { + // who knows mode + cpad->state = 90; + } + } + } + break; + case 40: // controller mode - check for extra modes + cpad->change_time = 0; + if (scePadInfoMode(cpad->number, 0, InfoModeIdTable, -1) == 0) { + // no controller modes + cpad->state = 90; + return cpad_info; + } + cpad->state = 41; + case 41: // controller mode - change to dualshock mode! + // try to enter the 2nd controller mode (dualshock for ds2's) + if (scePadSetMainMode(cpad->number, 0, 1, 3) == 1) { + cpad->state = 42; + } + break; + case 42: // controller mode change check + if (scePadGetReqState(cpad->number, 0) == scePadReqStateFailed) { + // failed to change to DS2 + cpad->state = 41; + } + if (scePadGetReqState(cpad->number, 0) == scePadReqStateComplete) { + // change successful. go back to the beginning. + cpad->state = 0; + } + break; + case 70: // dualshock mode - check vibration + // get number of actuators (2 for DS2) + if (scePadInfoAct(cpad->number, 0, -1, 0) < 1) { + // no actuators means no vibration. skip to end! + cpad->change_time = 0; + cpad->state = 99; + } else { + // we have actuators to use. + cpad->change_time = 1; // remember to update pad times. + cpad->state = 75; + } + break; + case 75: // set actuator vib param info + if (scePadSetActAlign(cpad->number, 0, cpad->align) != 0) { + if (scePadInfoPressMode(cpad->number, 0) == 1) { + // pressure buttons supported + cpad->state = 76; + } else { + // no pressure buttons, done with controller setup + cpad->state = 99; + } + } + break; + case 76: // enter pressure mode + if (scePadEnterPressMode(cpad->number, 0) == 1) { + cpad->state = 78; + } + break; + case 78: // pressure mode request check + if (scePadGetReqState(cpad->number, 0) == scePadReqStateFailed) { + cpad->state = 76; + } + if (scePadGetReqState(cpad->number, 0) == scePadReqStateComplete) { + cpad->state = 99; + } + break; + case 90: + break; // unsupported controller. too bad! + } + return cpad_info; +} + +// should make sure this works the same way in jak 2 +void InstallHandler(u32 handler_idx, u32 handler_func) { + ASSERT(handler_idx == 5); // vif1 + vif1_interrupt_handler = handler_func; +} + +// nothing used this in jak1, hopefully same for 2 +void InstallDebugHandler() { + ASSERT(false); +} + +/*! + * Get length of a file. + */ +s32 klength(u64 fs) { + auto file_stream = Ptr(fs).c(); + if ((file_stream->flags ^ 1) & 1) { + // first flag bit not set. This means no errors + auto end_seek = ee::sceLseek(file_stream->file, 0, SCE_SEEK_END); + auto reset_seek = ee::sceLseek(file_stream->file, 0, SCE_SEEK_SET); + if (reset_seek < 0 || end_seek < 0) { + // seeking failed, flag it + file_stream->flags |= 1; + } + return end_seek; + } else { + return 0; + } +} + +/*! + * Seek a file stream. + */ +s32 kseek(u64 fs, s32 offset, s32 where) { + s32 result = -1; + auto file_stream = Ptr(fs).c(); + if ((file_stream->flags ^ 1) & 1) { + result = ee::sceLseek(file_stream->file, offset, where); + if (result < 0) { + file_stream->flags |= 1; + } + } + return result; +} + +/*! + * Read from a file stream. + */ +s32 kread(u64 fs, u64 buffer, s32 size) { + s32 result = -1; + auto file_stream = Ptr(fs).c(); + if ((file_stream->flags ^ 1) & 1) { + result = ee::sceRead(file_stream->file, Ptr(buffer).c(), size); + if (result < 0) { + file_stream->flags |= 1; + } + } + return result; +} + +/*! + * Write to a file stream. + */ +s32 kwrite(u64 fs, u64 buffer, s32 size) { + s32 result = -1; + auto file_stream = Ptr(fs).c(); + if ((file_stream->flags ^ 1) & 1) { + result = ee::sceWrite(file_stream->file, Ptr(buffer).c(), size); + if (result < 0) { + file_stream->flags |= 1; + } + } + return result; +} + +/*! + * Close a file stream. + */ +u64 kclose(u64 fs) { + auto file_stream = Ptr(fs).c(); + if ((file_stream->flags ^ 1) & 1) { + ee::sceClose(file_stream->file); + file_stream->file = -1; + } + file_stream->flags = 0; + return fs; +} + +// TODO dma_to_iop +void dma_to_iop() { + ASSERT(false); +} + +u64 DecodeLanguage() { + return masterConfig.language; +} + +u64 DecodeAspect() { + return masterConfig.aspect; +} + +u64 DecodeVolume() { + return masterConfig.volume; +} + +// NOTE: this is originally hardcoded, and returns different values depending on the disc region. +// it returns 0 for NTSC-U, 1 for PAL and 2 for NTSC-J +u64 DecodeTerritory() { + return GAME_TERRITORY_SCEA; +} + +u64 DecodeTimeout() { + return masterConfig.timeout; +} + +u64 DecodeInactiveTimeout() { + return masterConfig.inactive_timeout; +} + +void DecodeTime(u32 ptr) { + Ptr clock(ptr); + // in jak2, if this fails, they do a sceScfGetLocalTimefromRTC + sceCdReadClock(clock.c()); +} + +/*! + * PC PORT FUNCTIONS BEGIN + */ +/*! + * Get a 300MHz timer value. + */ +u64 read_ee_timer() { + u64 ns = ee_clock_timer.getNs(); + return (ns * 3) / 10; +} + +/*! + * Do a fast memory copy. + */ +void c_memmove(u32 dst, u32 src, u32 size) { + memmove(Ptr(dst).c(), Ptr(src).c(), size); +} + +/*! + * Returns size of window. + */ +void get_window_size(u32 w_ptr, u32 h_ptr) { + if (w_ptr) { + auto w = Ptr(w_ptr).c(); + *w = Gfx::get_window_width(); + } + if (h_ptr) { + auto h = Ptr(h_ptr).c(); + *h = Gfx::get_window_height(); + } +} + +/*! + * Returns scale of window. This is for DPI stuff. + */ +void get_window_scale(u32 x_ptr, u32 y_ptr) { + float* x = x_ptr ? Ptr(x_ptr).c() : NULL; + float* y = y_ptr ? Ptr(y_ptr).c() : NULL; + Gfx::get_window_scale(x, y); +} + +/*! + * Returns resolution of the monitor. + */ +void get_screen_size(s64 vmode_idx, u32 w_ptr, u32 h_ptr) { + s32 *w_out = 0, *h_out = 0; + if (w_ptr) { + w_out = Ptr(w_ptr).c(); + } + if (h_ptr) { + h_out = Ptr(h_ptr).c(); + } + Gfx::get_screen_size(vmode_idx, w_out, h_out); +} + +/*! + * Returns refresh rate of the monitor. + */ +s64 get_screen_rate(s64 vmode_idx) { + return Gfx::get_screen_rate(vmode_idx); +} + +/*! + * Returns amount of video modes of the monitor. + */ +s64 get_screen_vmode_count() { + return Gfx::get_screen_vmode_count(); +} + +void mkdir_path(u32 filepath) { + auto filepath_str = std::string(Ptr(filepath).c()->data()); + file_util::create_dir_if_needed_for_file(filepath_str); +} + +u64 filepath_exists(u32 filepath) { + auto filepath_str = std::string(Ptr(filepath).c()->data()); + if (std::filesystem::exists(filepath_str)) { + return s7.offset + true_symbol_offset(g_game_version); + } + return s7.offset; +} + +void prof_event(u32 name, u32 kind) { + prof().event(Ptr(name).c()->data(), (ProfNode::Kind)kind); +} + +void set_frame_rate(s64 rate) { + Gfx::set_frame_rate(rate); +} + +void set_vsync(u32 symptr) { + Gfx::set_vsync(symptr != s7.offset); +} + +void set_window_lock(u32 symptr) { + Gfx::set_window_lock(symptr == s7.offset); +} + +void set_collision(u32 symptr) { + Gfx::g_global_settings.collision_enable = symptr != s7.offset; +} + +void set_collision_wireframe(u32 symptr) { + Gfx::g_global_settings.collision_wireframe = symptr != s7.offset; +} + +void set_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask, u32 symptr) { + if (symptr != s7.offset) { + Gfx::CollisionRendererSetMask(mode, mask); + } else { + Gfx::CollisionRendererClearMask(mode, mask); + } +} + +u32 get_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask) { + return Gfx::CollisionRendererGetMask(mode, mask) ? s7.offset + true_symbol_offset(g_game_version) + : s7.offset; +} + +/*! + * PC PORT FUNCTIONS END + */ + +void vif_interrupt_callback() { + // added for the PC port for faking VIF interrupts from the graphics system. + if (vif1_interrupt_handler && MasterExit == RuntimeExitStatus::RUNNING) { + call_goal(Ptr(vif1_interrupt_handler), 0, 0, 0, s7.offset, g_ee_main_mem); + } +} + +/*! + * Added in PC port. + */ +u32 offset_of_s7() { + return s7.offset; +} \ No newline at end of file diff --git a/game/kernel/common/kmachine.h b/game/kernel/common/kmachine.h new file mode 100644 index 0000000000..61e1290616 --- /dev/null +++ b/game/kernel/common/kmachine.h @@ -0,0 +1,77 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/graphics/gfx.h" + +/*! + * Where does OVERLORD load its data from? + */ +enum OverlordDataSource : u32 { + fakeiso = 0, //! some sort of development way of getting data + deviso = 1, //! some sort of development way of getting data + iso_cd = 2, //! use the actual DVD drive +}; + +extern OverlordDataSource isodrv; + +// Get IOP modules from DVD or from dsefilesv +extern u32 modsrc; + +// Reboot IOP on start? +extern u32 reboot; + +extern const char* init_types[]; + +void kmachine_init_globals_common(); + +/*! + * Initialize the CD Drive + */ +void InitCD(); + +/*! + * Initialize the GS and display the splash screen. + */ +void InitVideo(); + +/*! + * Flush caches. Does all the memory, regardless of what you specify + */ +void CacheFlush(void* mem, int size); +u64 CPadOpen(u64 cpad_info, s32 pad_number); +u64 CPadGetData(u64 cpad_info); +void InstallHandler(u32 handler_idx, u32 handler_func); +void InstallDebugHandler(); +s32 klength(u64 fs); +s32 kseek(u64 fs, s32 offset, s32 where); +s32 kread(u64 fs, u64 buffer, s32 size); +s32 kwrite(u64 fs, u64 buffer, s32 size); +u64 kclose(u64 fs); +void dma_to_iop(); +u64 DecodeLanguage(); +u64 DecodeAspect(); +u64 DecodeVolume(); +u64 DecodeTerritory(); +u64 DecodeTimeout(); +u64 DecodeInactiveTimeout(); +void DecodeTime(u32 ptr); +u64 read_ee_timer(); +void c_memmove(u32 dst, u32 src, u32 size); +void get_window_size(u32 w_ptr, u32 h_ptr); +void get_window_scale(u32 x_ptr, u32 y_ptr); +void get_screen_size(s64 vmode_idx, u32 w_ptr, u32 h_ptr); +s64 get_screen_rate(s64 vmode_idx); +s64 get_screen_vmode_count(); +void mkdir_path(u32 filepath); +u64 filepath_exists(u32 filepath); +void prof_event(u32 name, u32 kind); +void set_frame_rate(s64 rate); +void set_vsync(u32 symptr); +void set_window_lock(u32 symptr); +void set_collision(u32 symptr); +void set_collision_wireframe(u32 symptr); +void set_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask, u32 symptr); +u32 get_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask); +u32 offset_of_s7(); +void vif_interrupt_callback(); \ No newline at end of file diff --git a/game/kernel/kmalloc.cpp b/game/kernel/common/kmalloc.cpp similarity index 90% rename from game/kernel/kmalloc.cpp rename to game/kernel/common/kmalloc.cpp index 95017426d1..996f73545a 100644 --- a/game/kernel/kmalloc.cpp +++ b/game/kernel/common/kmalloc.cpp @@ -1,25 +1,19 @@ -/*! - * @file kmalloc.cpp - * GOAL Kernel memory allocator. - * Simple two-sided bump allocator. - * DONE - */ - #include "kmalloc.h" #include #include -#include "kprint.h" -#include "kscheme.h" - #include "common/goal_constants.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/common/memory_layout.h" + // global and debug kernel heaps Ptr kglobalheap; Ptr kdebugheap; -void kmalloc_init_globals() { +void kmalloc_init_globals_common() { // _globalheap and _debugheap kglobalheap.offset = GLOBAL_HEAP_INFO_ADDR; kdebugheap.offset = DEBUG_HEAP_INFO_ADDR; @@ -60,7 +54,7 @@ Ptr ksmalloc(Ptr heap, s32 size, u32 flags, char const* name) { * which will not be sent to the Listener. * DONE, EXACT */ -void kheapstatus(Ptr heap) { +Ptr kheapstatus(Ptr heap) { Msg(6, "[%8x] kheap\n" "\tbase: #x%x\n" @@ -69,16 +63,20 @@ void kheapstatus(Ptr heap) { "\ttop: #x%x\n", heap.offset, heap->base.offset, heap->top_base.offset, heap->current.offset, heap->top.offset); + // note: max symbols here is game-version dependent Msg(6, "\t used bot: %d of %d bytes\n" "\t used top: %d of %d bytes\n" "\t symbols: %d of %d\n", heap->current - heap->base, heap->top_base - heap->base, heap->top_base - heap->top, - heap->top_base - heap->base, NumSymbols, GOAL_MAX_SYMBOLS); + heap->top_base - heap->base, NumSymbols, max_symbols(g_game_version)); if (heap == kglobalheap) { Msg(6, "\t %d bytes before stack\n", GLOBAL_HEAP_END - heap->current.offset); } + + // might not have returned heap in jak 1 + return heap; } /*! @@ -116,7 +114,8 @@ Ptr kmalloc(Ptr heap, s32 size, u32 flags, char const* name) { // if we got a null heap, put it on the global heap, but warn about it if (!heap.offset) { - Msg(6, "-----------> kmalloc: alloc %s, mem %s #x%x (a:%d %dbytes)\n", "DEBUG", name, -1, + // the 0 is uninitialized in jak1, set to zero in jak 2. might just be compiler differences. + Msg(6, "-----------> kmalloc: alloc %s, mem %s #x%x (a:%d %dbytes)\n", "DEBUG", name, 0, alignment_flag, size); heap = kglobalheap; } diff --git a/game/kernel/kmalloc.h b/game/kernel/common/kmalloc.h similarity index 75% rename from game/kernel/kmalloc.h rename to game/kernel/common/kmalloc.h index 1e6937cf38..464fc4cbc9 100644 --- a/game/kernel/kmalloc.h +++ b/game/kernel/common/kmalloc.h @@ -1,20 +1,9 @@ #pragma once -/*! - * @file kmalloc.h - * GOAL Kernel memory allocator. - * Simple two-sided bump allocator. - * DONE - */ - -#ifndef JAK_KMALLOC_H -#define JAK_KMALLOC_H - -#include "Ptr.h" -#include "kmachine.h" - #include "common/common_types.h" +#include "game/kernel/common/Ptr.h" + /*! * A kheap has a top/bottom linear allocator */ @@ -36,14 +25,11 @@ constexpr u32 KMALLOC_ALIGN_256 = 0x100; constexpr u32 KMALLOC_ALIGN_64 = 0x40; constexpr u32 KMALLOC_ALIGN_16 = 0x10; -// kmalloc funcions +void kmalloc_init_globals_common(); + Ptr ksmalloc(Ptr heap, s32 size, u32 flags, char const* name); -void kheapstatus(Ptr heap); +Ptr kheapstatus(Ptr heap); Ptr kinitheap(Ptr heap, Ptr mem, s32 size); u32 kheapused(Ptr heap); Ptr kmalloc(Ptr heap, s32 size, u32 flags, char const* name); -void kfree(Ptr a); - -void kmalloc_init_globals(); - -#endif // JAK_KMALLOC_H +void kfree(Ptr a); \ No newline at end of file diff --git a/game/kernel/kmemcard.cpp b/game/kernel/common/kmemcard.cpp similarity index 99% rename from game/kernel/kmemcard.cpp rename to game/kernel/common/kmemcard.cpp index 3fe5a700c6..425b7bea9a 100644 --- a/game/kernel/kmemcard.cpp +++ b/game/kernel/common/kmemcard.cpp @@ -2,6 +2,8 @@ * @file kmemcard.cpp * Memory card interface. Very messy code. Most of it is commented out now, as we've switched away * from memory cards to just raw saves. + * + * Not checked carefully for differences in jak 2. */ #include "kmemcard.h" @@ -14,9 +16,6 @@ #include "common/util/FileUtil.h" #include "common/util/Timer.h" -#include "game/common/ramdisk_rpc_types.h" -#include "game/kernel/fileio.h" -#include "game/kernel/kdgo.h" #include "game/sce/sif_ee.h" #include "game/sce/sif_ee_memcard.h" diff --git a/game/kernel/kmemcard.h b/game/kernel/common/kmemcard.h similarity index 98% rename from game/kernel/kmemcard.h rename to game/kernel/common/kmemcard.h index 640abc30df..c28386b711 100644 --- a/game/kernel/kmemcard.h +++ b/game/kernel/common/kmemcard.h @@ -5,10 +5,10 @@ * Memory card interface. Very messy code. */ -#include "kmachine.h" - #include "common/common_types.h" +#include "game/kernel/common/Ptr.h" + void kmemcard_init_globals(); constexpr s32 SAVE_SIZE = 691; // likely different between versions! 692 on PAL/JPN diff --git a/game/kernel/common/kprint.cpp b/game/kernel/common/kprint.cpp new file mode 100644 index 0000000000..f53ed0536d --- /dev/null +++ b/game/kernel/common/kprint.cpp @@ -0,0 +1,572 @@ +#include "kprint.h" + +#include +#include +#include +#include + +#include "common/cross_os_debug/xdbg.h" +#include "common/listener_common.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kscheme.h" + +// Pointer set to something in the middle of the output buffer, if there is something in the buffer. +Ptr OutputPending; + +// Pointer set to something in the middle of the print buffer, if there is something in the buffer +Ptr PrintPending; + +// Size of incoming message. +s32 MessCount; + +// Pointer to message buffer, the compiler to target buffer +Ptr MessBufArea; + +// Pointer to the output buffer, the runtime to compiler buffer +Ptr OutputBufArea; + +// Pointer to print buffer, the buffer for printing and string formatting. +Ptr PrintBufArea; + +// integer printing conversion table +char ConvertTable[16]; + +// buffer for sending an "acknowledge" message to the compiler +char AckBufArea[40]; + +/*! + * Initialize global variables for kprint + */ +void kprint_init_globals_common() { + OutputPending.offset = 0; + PrintPending.offset = 0; + MessCount = 0; + MessBufArea.offset = 0; + OutputBufArea.offset = 0; + PrintBufArea.offset = 0; + memcpy(ConvertTable, "0123456789abcdef", 16); + memset(AckBufArea, 0, sizeof(AckBufArea)); +} + +/*! + * Initialize GOAL Kernel printing/messaging system. + * Allocates buffers. + */ +void init_output() { + // Note: slightly different behavior in jak 1/jak 2 here. + // I think the jak 2 version makes clear_wrong do the wrong thing if you are masterdebug but not + // debugsegment. + bool use_debug; + switch (g_game_version) { + case GameVersion::Jak1: + use_debug = MasterDebug; + break; + case GameVersion::Jak2: + use_debug = MasterDebug && DebugSegment; + break; + default: + ASSERT(false); + } + + if (use_debug) { + MessBufArea = kmalloc(kdebugheap, DEBUG_MESSAGE_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, + "mess-buf"); + OutputBufArea = kmalloc(kdebugheap, DEBUG_OUTPUT_BUFFER_SIZE, + KMALLOC_MEMSET | KMALLOC_ALIGN_256, "output-buf"); + PrintBufArea = kmalloc(kdebugheap, DEBUG_PRINT_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, + "print-buf"); + } else { + // no compiler connection, so we do not allocate buffers + MessBufArea = Ptr(0); + OutputBufArea = Ptr(0); + + // we still need a (small) print buffer for string maniuplation and debugging prints. + PrintBufArea = + kmalloc(kglobalheap, PRINT_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, "print-buf"); + } +} + +/*! + * Empty output buffer (only if MasterDebug) + */ +void clear_output() { + if (MasterDebug) { + kstrcpy((char*)Ptr(OutputBufArea + sizeof(ListenerMessageHeader)).c(), ""); + OutputPending = Ptr(0); + } +} + +/*! + * Clear all data in the print buffer + */ +void clear_print() { + *Ptr(PrintBufArea + sizeof(ListenerMessageHeader)) = 0; + PrintPending = Ptr(0); +} + +/*! + * Buffer message to compiler indicating the target has reset. + * Write to the beginning of the output buffer. + */ +void reset_output() { + if (MasterDebug) { +// original GOAL: +// sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), "reset #x%x\n", +// s7.offset); + +// modified for OpenGOAL: +#ifdef _WIN32 + sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), + "reset #x%x #x%llx %s\n", s7.offset, (unsigned long long)g_ee_main_mem, // grr + xdbg::get_current_thread_id().to_string().c_str()); +#else + sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), "reset #x%x #x%lx %s\n", + s7.offset, (uintptr_t)g_ee_main_mem, xdbg::get_current_thread_id().to_string().c_str()); +#endif + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); + } +} + +/*! + * Buffer message to compiler indicating some object file has been unloaded. + * DONE, EXACT + */ +void output_unload(const char* name) { + if (MasterDebug) { + sprintf(strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)), + "unload \"%s\"\n", name); + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); + } +} + +/*! + * Buffer message to compiler indicating some object file has been loaded. + */ +void output_segment_load(const char* name, Ptr link_block, u32 flags) { + if (MasterDebug) { + char* buffer = strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)); + char true_str[] = "t"; + char false_str[] = "nil"; + char* flag_str = (flags & LINK_FLAG_OUTPUT_TRUE) ? true_str : false_str; + auto lbp = link_block.cast(); + // modified to also include segment sizes, and work from opengoal linker + // original game used link_block_v3's here. + sprintf(buffer, "load \"%s\" %s #x%x #x%x #x%x #x%x #x%x #x%x\n", name, flag_str, + lbp->code_infos[0].offset, lbp->code_infos[1].offset, lbp->code_infos[2].offset, + lbp->code_infos[0].size, lbp->code_infos[1].size, lbp->code_infos[2].size); + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); + } +} + +/*! + * Print to the GOAL print buffer from C + * seeks PrintPending to begining of what was just printed. + * This is a different behavior from all the other prints! + * DONE, EXACT + */ +void cprintf(const char* format, ...) { + va_list args; + va_start(args, format); + char* str = PrintPending.cast().c(); + if (!PrintPending.offset) + str = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); + PrintPending = make_ptr(strend(str)).cast(); + vsprintf((char*)PrintPending.c(), format, args); + + va_end(args); +} + +/*! + * Print directly to the C stdout + * The "k" parameter is ignored, so this is just like printf + * DONE, EXACT + */ +void Msg(s32 k, const char* format, ...) { + (void)k; + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +/*! + * Print directly to the C stdout + * This is idential to Msg + * DONE, EXACT + */ +void MsgWarn(const char* format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +/*! + * Print directly to the C stdout + * This is idential to Msg + * DONE, EXACT + */ +void MsgErr(const char* format, ...) { + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); +} + +/*! + * Reverse string in place. + * DONE, EXACT + */ +void reverse(char* str) { + s32 i = 0; + s32 end = (s32)strlen(str); + while (end--, i < end) { + char c = str[i]; + str[i] = str[end]; + str[end] = c; + i++; + } +} + +/*! + * Some sort of rounding for printing floating point numbers. + * It is unused and believed to be not correct. + * Currently copy-pasta from GHIDRA + * (not checked in jak2) + */ +char* round(float x, s32* param1, char* start, char* sEnd, char padchar, s32* param4) { + char cVar1; + char* local_58; + float f; + + if (x == 0.00000000) { + f = padchar - '0'; + } else { + modff(x * 10.00000000, &f); + } + if (4.00000000 < f) { + while (true) { + if (*sEnd == '.') { + sEnd = sEnd + -1; + } + cVar1 = *sEnd; + *sEnd = cVar1 + 1; + if ((char)(cVar1 + 1) <= '9') { + return start; + } + *sEnd = '0'; + if (sEnd == start) + break; + sEnd = sEnd + -1; + } + if (param1 == (int*)0x0) { + sEnd[-1] = '1'; + local_58 = start + -1; + } else { + *sEnd = '1'; + *param1 = *param1 + 1; + local_58 = start; + } + } else { + local_58 = start; + if (*param4 == 0x2d) { + while (true) { + if (*sEnd == '.') { + sEnd = sEnd + -1; + } + local_58 = start; + if (*sEnd != '0') + break; + if (sEnd == start) { + *param4 = 0; + } + sEnd = sEnd + -1; + } + } + } + return local_58; +} + +/*! + * Convert floating point to string intermediate function. + * Places a null character as the first character, then the integers, decimal, fraction digits + * Presumably, if rounding worked, it would sometimes add an additional digit in front instead of + * the null but rounding doesn't, so you get a null in front every time. There is no null terminator + * added, you have to do it yourself. returned value is length : buff[last_char] - buff[1] (not + * including the leading null) if the number is negative, *lead_char = '-'. Otherwise it's '\0'. + * The negative is not inserted for you. + * The precision is how many digits after decimal to print. + * The flag could have a 1 to enable rounding, but this doesn't work, so don't use it. + * Without rounding the printing is a little bit off but you don't notice unless you look too + * closely. + * + * DONE, added some sanity checks and removed support for "rounding" as round isn't implemented and + * rounding is never used in the game. + * + * Not checked closely in jak 2. + */ +s32 cvt_float(float x, s32 precision, s32* lead_char, char* buff_start, char* buff_end, u32 flags) { + // put a null at the beginning of the output + *buff_start = 0; + s32 forward_count = 0; + + // compute absolute value and set lead char to `-` if needed. + float abs_x; + if (x < 0.0f) { + abs_x = -x; + *lead_char = '-'; + } else { + *lead_char = 0; + abs_x = x; + } + + // nan check + u32 abs_x_u32 = *(u32*)&abs_x; + if ((abs_x_u32 & 0x7fffffff) == 0x7fffffff) { + kstrcpy(buff_start, "NaN"); + return 3; // the length of NaN + } + + // find fraction and integer parts of absolute value. + float integer_part; + float fraction_part = std::modf(abs_x, &integer_part); + + char* start_ptr = buff_start + 1; // the null terminator is at buff_start[0]. + char* end_ptr = buff_end - 1; // the last char we can write to. + + // loop over integer digits (increasing significance) + while (start_ptr <= end_ptr && (integer_part != 0.0f)) { + // the fractional part will be the lowest place integer divided by 10 + float next_int = std::modf(integer_part / 10.f, &integer_part); + + // the float to round to get the integer + float rounder = next_int * 10.f + 0.5f; + + // but wait, the PS2 is stupid and maybe has weird floats that might not convert well. + // this checks for very large exponent or negative sign bit, which would only happen if the + // float is all messed up. + u32 ru32 = *(u32*)&rounder; + s32 value; + if (((ru32 >> 0x17) & 0xff) < 0x9e) { // exponent + value = (char)rounder; + } else if (!(ru32 >> 31)) { // sign bit + value = 0; + // ASSERT(false); // not sure what happens here. + } else { + value = -1; // happens on NaN's + } + + // place number at the end of the buffer and move pointer back + *end_ptr = '0' + value; + end_ptr--; + + // track how many integers we've written + forward_count++; + } + + char* count_chrp = start_ptr; // one after the buffer start (null) + if (forward_count == 0) { // no integers + *start_ptr = '0'; // put leading zero + count_chrp = buff_start + 2; // and set count to two after buffer start + } else { + while (end_ptr = end_ptr + 1, + end_ptr < buff_end) { // copy back to the beginning (will be in right order) + *count_chrp = *end_ptr; + count_chrp++; + } + } + + // if we have digits after decimal, place decimal. + if (precision) { + *count_chrp = '.'; + count_chrp++; + } + + s32 prec = precision; + + // if we have a fractional part, we may need to either print it, or do rounding. + if (fraction_part != 0.0f) { + prec = precision; + if (precision) { + // same loop as before, but only over the number of digits we actually want to print. + do { + float next_int; + fraction_part = std::modf(fraction_part * 10.f, &next_int); + u32 ru32 = *(u32*)&next_int; + s32 value; + if (((ru32 >> 0x17) & 0xff) < 0x9e) { + value = (char)next_int; + } else if (!(ru32 >> 0x1f)) { + value = 0; + // ASSERT(false); // not sure what happens here. + } else { + value = -1; // happens on NaN's + } + *count_chrp = value + '0'; + count_chrp++; + prec--; + } while ((prec) && (fraction_part != 0.f)); + } + + // if the rounding flag is enabled, we would round here. + // however, the rounding flag is always disabled and the rounding code doesn't work. + if ((fraction_part != 0.f) && ((flags & 1) != 0)) { + start_ptr = round(fraction_part, nullptr, start_ptr, count_chrp - 1, 0, lead_char); + ASSERT(false); + } + } + + // if there are any left over digits (fraction part = 0 before we got to the end), append zeros. + while (prec = prec - 1, prec != -1) { + *count_chrp = '0'; + count_chrp++; + } + // return length. not including the null character at the beginning. + return count_chrp - start_ptr; +} + +/*! + * Convert floating point to a string. + * Don't set the precision too high or you get NaN (if float is longer than 31 digits without + * negative sign) Don't set the flags. Rounding is a little bit off, but it adds character. Highly + * recommended to pad with spaces, not zeros. If you pad with spaces you get " -1.23" If you pad + * with zeros, you get "0000000-1.23" which most people consider to be wrong. + * @param out_str : output buffer to write null terminated string into + * @param x : number + * @param desired_len : length (will pad if under, nothing if over) + * @param pad_char : character to pad with + * @param precision + * @param flags + * + * DONE + * Not checked super closely in jak 2. + */ +void ftoa(char* out_str, float x, s32 desired_len, char pad_char, s32 precision, u32 flags) { + char buff[0x100]; + char* current_buff = buff; + s32 lead_char; + + // do conversion, but only write into the first half of the buffer (128 chars) + s32 count = cvt_float(x, precision, &lead_char, current_buff, buff + 0x7f, flags); + + // if it ends up larger than 31 characters, it's probably gone horribly wrong and we should just + // put NaN instead. Or maybe somebody requested a lot of precision. That would be stupid and they + // deserve to see NaN. + if (count > 0x3f) { + kstrcpy(current_buff, "NaN"); + count = 3; + lead_char = 0; + } + + // always true because we don't round, + if (buff[0] == 0) { + current_buff = buff + 1; + } + + // length, including the leading negative (if we need it). + s32 real_count = (lead_char != 0) + count; + + char* out_ptr = out_str; + + // pad + if ((desired_len > 0) && (desired_len > real_count)) { + for (s32 i = 0; i < (desired_len - real_count); i++) { + *out_ptr = pad_char; + out_ptr++; + } + } + + // leading + if (lead_char) { + *out_ptr = lead_char; + out_ptr++; + } + + // copy numbers + for (s32 i = 0; i < count; i++) { + *out_ptr = *current_buff; + out_ptr++; + current_buff++; + } + + // null terminate! + *out_ptr = 0; +} + +/*! + * Convert integer to string. + * @param buffer : buffer to print into. Must be at least as long as the longest possible number to + * print + * @param value : value to print. + * @param base : base to print in (2, 10, 16 supported) + * @param length : length. if shorter than length, pad with pad. If longer, only truncate leading + * f's 1's in base 16 or 2 + * @param pad : character to pad with + * @param flags : flag. Only the 2nd bit is used, which will disable negative sings on + * binary/hexadecimal truncated numbers. Something like -1 (0xffffffff) will print as -fffffff.... + * Not checked super closely in jak 2 + */ +char* kitoa(char* buffer, s64 value, u64 base, s32 length, char pad, u32 flag) { + s64 negativeValue = 0; + s64 value_to_print = value; + + // if negative and base ten, we print the opposite of the value and add a negative sign + if ((value < 0) && base == 10) { + negativeValue = value; + value_to_print = -value; + } + + // write number in reverse + int count = 0; + do { + buffer[count++] = ConvertTable[(u64)value_to_print % (u64)base]; + value_to_print = (u64)value_to_print / (u64)base; + } while (value_to_print); + + // append negative if we need to + if (negativeValue < 0) { + buffer[count++] = '-'; + } + + // pad (probably some sort of for loop) + s32 rLen = length; + if (0 < length - count) { + rLen = length - count; + while (0 < rLen) { + buffer[count++] = pad; + rLen--; + } + } + + // truncate f's / 1's + if (rLen > 0 && value < 0 && (base == 2 || base == 16) && rLen < count) { + char c = (base == 16) ? 'f' : '1'; + + while (rLen < count && (buffer[count - 1] == c)) { + count--; + } + + if (!(flag & 2)) { + buffer[count++] = '-'; + } + } + + // null terminate, reverse, return! + buffer[count] = 0; + reverse(buffer); + return buffer; +} + +/*! + * Convert 128-bit integer to string. Not implemented because it is never used in the game. + * It would also require passing 128-bit values between GOAL and C++ and this is not worth + * implementing. It is only used by the "format" function, which cannot use it properly. "format" + * uses C varags, but 128-bit varags don't work, so "format" always passes 0 for quadword printing. + */ +void kqtoa() { + ASSERT(false); +} \ No newline at end of file diff --git a/game/kernel/kprint.h b/game/kernel/common/kprint.h similarity index 90% rename from game/kernel/kprint.h rename to game/kernel/common/kprint.h index 6364ab3e2c..515f7f1c39 100644 --- a/game/kernel/kprint.h +++ b/game/kernel/common/kprint.h @@ -1,20 +1,8 @@ #pragma once -/*! - * @file kprint.h - * GOAL Print. Contains GOAL I/O, Print, Format... - */ - -#ifndef RUNTIME_KPRINT_H -#define RUNTIME_KPRINT_H - -#include "kmachine.h" - -constexpr u32 DEBUG_MESSAGE_BUFFER_SIZE = 0x80000; -constexpr u32 DEBUG_OUTPUT_BUFFER_SIZE = 0x80000; -constexpr u32 DEBUG_PRINT_BUFFER_SIZE = 0x200000; -constexpr u32 PRINT_BUFFER_SIZE = 0x2000; +#include "common/common_types.h" +#include "game/kernel/common/Ptr.h" /////////// // SDATA /////////// @@ -23,14 +11,25 @@ extern Ptr PrintPending; extern s32 MessCount; extern char AckBufArea[40]; +extern char ConvertTable[16]; // todo rm extern Ptr MessBufArea; extern Ptr OutputBufArea; extern Ptr PrintBufArea; -/*! - * Initialize global variables for kprint - */ -void kprint_init_globals(); +constexpr u32 DEBUG_MESSAGE_BUFFER_SIZE = 0x80000; +constexpr u32 DEBUG_OUTPUT_BUFFER_SIZE = 0x80000; +constexpr u32 DEBUG_PRINT_BUFFER_SIZE = 0x200000; +constexpr u32 PRINT_BUFFER_SIZE = 0x2000; + +struct format_struct { + char data[0x40]; + void reset() { + for (auto& c : data) + c = -1; + } +}; + +void kprint_init_globals_common(); /*! * Initialize GOAL Kernel printing/messaging system. @@ -76,6 +75,11 @@ void cprintf(const char* format, ...) __attribute__((format(printf, 1, 2))); void cprintf(const char* format, ...); #endif +/*! + * Reverse string in place. + */ +void reverse(char* s); + #ifdef __linux__ /*! * Print directly to the C stdout @@ -103,7 +107,6 @@ void MsgWarn(const char* format, ...) __attribute__((format(printf, 1, 2))); */ void MsgWarn(const char* format, ...); #endif - #ifdef __linux__ /*! * Print directly to the C stdout @@ -118,11 +121,6 @@ void MsgErr(const char* format, ...) __attribute__((format(printf, 1, 2))); void MsgErr(const char* format, ...); #endif -/*! - * Reverse string in place. - */ -void reverse(char* s); - /*! * Helper function for floating point to string conversion. */ @@ -143,10 +141,4 @@ char* kitoa(char* buffer, s64 value, u64 base, s32 length, char pad, u32 flag); * The format function does have the ability to call it, but it always passes a zero because * getting a 128-bit integer in PS2 gcc's varargs doesn't work. */ -void kqtoa(); - -extern "C" { -s32 format_impl(uint64_t* args); -} - -#endif // RUNTIME_KPRINT_H +void kqtoa(); \ No newline at end of file diff --git a/game/kernel/common/kscheme.cpp b/game/kernel/common/kscheme.cpp new file mode 100644 index 0000000000..a701696ec3 --- /dev/null +++ b/game/kernel/common/kscheme.cpp @@ -0,0 +1,289 @@ +#include "kscheme.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kprint.h" + +// total number of symbols in the table +s32 NumSymbols; + +// value of the GOAL s7 register, pointing to the middle of the symbol table +Ptr s7; + +// used for crc32 calculation +u32 crc_table[0x100]; + +// pointer to the "second" symbol table +Ptr SymbolTable2; + +// pointer to the last symbol +Ptr LastSymbol; + +//! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a +//! warning message if EnableMethodSet = 1 +u32 FastLink; + +// set to true to enable propagating method overrides to child types +// this is an O(N_max_symbols) operation, so it is avoided when loading DGOs for levels. +// but is enabled when loading the engine. +Ptr EnableMethodSet; + +void kscheme_init_globals_common() { + SymbolTable2.offset = 0; + LastSymbol.offset = 0; + NumSymbols = 0; + s7.offset = 0; + for (auto& x : crc_table) { + x = 0; + } + EnableMethodSet.offset = 0; + FastLink = 0; +} + +/*! + * Initialize CRC Table. + */ +void init_crc() { + for (u32 i = 0; i < 0x100; i++) { + u32 n = i << 24; + for (u32 j = 0; j < 8; j++) { + n = n & 0x80000000 ? (n << 1) ^ CRC_POLY : (n << 1); + } + crc_table[i] = n; + } +} + +/*! + * Take the CRC32 hash of some data + */ +u32 crc32(const u8* data, s32 size) { + uint32_t crc = 0; + for (int i = size; i != 0; i--, data++) { + crc = crc_table[crc >> 24] ^ ((crc << 8) | *data); + } + + ASSERT(~crc); + return ~crc; +} + +/*! + * Delete method for types which cannot have "delete" used on them. + * Prints an error to stdout and returns false. + */ +u64 delete_illegal(u32 obj) { + MsgErr("dkernel: illegal attempt to call delete method of static object @ #x%x\n", obj); + return s7.offset; // todo, maybe don't return anything? +} + +/*! + * Wrapper around kmalloc to allow GOAL programs to allocate on kernel heaps. + */ +u64 goal_malloc(u32 heap, u32 size, u32 flags, u32 name) { + return kmalloc(Ptr(heap), size, flags, Ptr(name)->data()).offset; +} + +extern "C" { +// defined in asm_funcs.asm +#ifdef __linux__ +uint64_t _call_goal_asm_linux(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); +uint64_t _call_goal_on_stack_asm_linux(u64 rsp, + u64 u0, + u64 u1, + void* fptr, + void* st_ptr, + void* offset); +#elif _WIN32 +uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); +uint64_t _call_goal_on_stack_asm_win32(u64 rsp, void* fptr, void* st_ptr, void* offset); +#endif +} + +/*! + * Wrapper around _call_goal_asm for calling a GOAL function from C. + * Calls from the parent stack. + */ +u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset) { + // auto st_ptr = (void*)((uint8_t*)(offset) + st); updated for the new compiler! + void* st_ptr = (void*)st; + + void* fptr = f.c(); +#ifdef __linux__ + return _call_goal_asm_linux(a, b, c, fptr, st_ptr, offset); +#elif _WIN32 + return _call_goal_asm_win32(a, b, c, fptr, st_ptr, offset); +#endif +} + +/*! + * Wrapper around _call_goal_asm_on_stack for switching stacks and calling a GOAL function there. + */ +u64 call_goal_on_stack(Ptr f, u64 rsp, u64 st, void* offset) { + void* st_ptr = (void*)st; + + void* fptr = f.c(); +#ifdef __linux__ + return _call_goal_on_stack_asm_linux(rsp, 0, 0, fptr, st_ptr, offset); +#elif _WIN32 + return _call_goal_on_stack_asm_win32(rsp, fptr, st_ptr, offset); +#endif +} + +/*! + * Call a GOAL function with no arguments. + */ +u64 call_goal_function(Ptr func) { + return call_goal(func, 0, 0, 0, s7.offset, g_ee_main_mem); +} + +/*! + * Default print method for structures. + * Structures have no runtime type info, so there's not much we can do here. + */ +u64 print_structure(u32 s) { + cprintf("#", s); + return s; +} + +/*! + * Print an integer. Works correctly for 64-bit integers. + */ +u64 print_integer(u64 obj) { + // not sure why this is any better than cprintf("%ld") or similar. Maybe a tiny bit faster? + char* str = PrintPending.cast().c(); + if (!str) { + str = (PrintBufArea + 0x18).cast().c(); + } + + PrintPending = make_ptr(strend(str)).cast(); + kitoa((char*)PrintPending.c(), obj, 10, 0xffffffff, '0', 0); + return obj; +} + +/*! + * Print a boxed integer. Works correctly for 64-bit integers. Assumes signed. + */ +u64 print_binteger(u64 obj) { + char* str = PrintPending.cast().c(); + if (!PrintPending.offset) { + str = (PrintBufArea + 0x18).cast().c(); + } + + PrintPending = make_ptr(strend(str)).cast(); + kitoa((char*)PrintPending.c(), ((s64)obj) >> 3, 10, 0xffffffff, '0', 0); + return obj; +} + +/*! + * Print floating point number. + */ +u64 print_float(u32 f) { + // again not sure why this is any better than cprintf("%f") or similar. Maybe a tiny bit faster? + float ff; + *(u32*)&ff = f; + char* str = PrintPending.cast().c(); + if (!PrintPending.offset) { + str = (PrintBufArea + 0x18).cast().c(); + } + + PrintPending = make_ptr(strend(str)).cast(); + + ftoa((char*)PrintPending.c(), ff, 0xffffffff, ' ', 4, 0); + return f; +} + +/*! + * Print method for VU functions. Again, just prints address. + */ +u64 print_vu_function(u32 obj) { + cprintf("#", obj); + return obj; +} + +/*! + * Copy method that does no copying. + */ +u64 copy_fixed(u32 it) { + return it; +} + +/*! + * Default copy for a structure. Since this has no idea of the actual type, it doesn't know what + * size to copy. So we do no copy and return a reference to the original data. + */ +u64 copy_structure(u32 it, u32 unknown) { + (void)unknown; + return it; +} + +/*! + * Inspect an integer (works correctly on 64-bit integers) + */ +u64 inspect_integer(u64 obj) { + // and now we're using cprintf. Why doesn't print do this? + cprintf("[%16lx] fixnum %ld\n", obj, obj); + return obj; +} + +/*! + * Inspect a boxed integer (works correctly on 64-integers) + */ +u64 inspect_binteger(u64 obj) { + cprintf("[%16lx] boxed-fixnum %ld\n", obj, s64(obj) >> 3); + return obj; +} + +/*! + * Inspect a floating point number + */ +u64 inspect_float(u32 f) { + float ff; + ff = *(float*)(&f); + cprintf("[%8x] float ", f); + + // likely copy-pasta - no need for this check because of the cprintf immediately before. + char* str = PrintPending.cast().c(); + if (!str) { + str = (PrintBufArea + 0x18).cast().c(); + } + + PrintPending = make_ptr(strend(str)).cast(); + + ftoa(PrintPending.cast().c(), ff, -1, ' ', 4, 0); + cprintf("\n"); + return f; +} + +/*! + * Inspect a structure. + */ +u64 inspect_structure(u32 obj) { + cprintf("[%8x] structure\n", obj); + return obj; +} + +/*! + * Inspect a VU Function. Doesn't seem to be used. Also the concept of "vu-function" + * isn't really used. VU0 macro mode stuff goes in normal functions, and micro-mode stuff goes + * in a giant dump of many functions thats loaded and unloaded all at the same time. + */ +u64 inspect_vu_function(u32 obj) { + struct VuFunction { + u32 length; + u32 origin; + u32 qlength; + }; + + auto vf = Ptr(obj); + cprintf("[%8x] vu-function\n\tlength: %d\n\torigin: #x%x\n\tqlength: %d\n", obj, vf->length, + vf->origin, vf->qlength); + return obj; +} + +/*! + * This doesn't exist in the game, but we add it as a wrapper around kheapstatus. + * Note that this isn't a great inspect as it prints to stdout instead of the printbuffer. + */ +u64 inspect_kheap(u32 obj) { + kheapstatus(Ptr(obj)); + return obj; +} \ No newline at end of file diff --git a/game/kernel/common/kscheme.h b/game/kernel/common/kscheme.h new file mode 100644 index 0000000000..b78d1e759a --- /dev/null +++ b/game/kernel/common/kscheme.h @@ -0,0 +1,49 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" + +extern s32 NumSymbols; +extern Ptr s7; +extern Ptr SymbolTable2; +extern Ptr LastSymbol; +extern u32 FastLink; +extern Ptr EnableMethodSet; + +void kscheme_init_globals_common(); + +constexpr u32 CRC_POLY = 0x04c11db7; +constexpr u32 EMPTY_HASH = 0x8454B6E6; +constexpr u32 OFFSET_MASK = 7; + +constexpr uint32_t UNKNOWN_PP = UINT32_MAX; + +struct String { + u32 len; + char* data() { return ((char*)this) + sizeof(String); } +}; + +struct Function {}; + +void init_crc(); +u32 crc32(const u8* data, s32 size); +u64 delete_illegal(u32 obj); +u64 goal_malloc(u32 heap, u32 size, u32 flags, u32 name); + +u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset); +u64 call_goal_on_stack(Ptr f, u64 rsp, u64 st, void* offset); +u64 call_goal_function(Ptr func); +u64 print_structure(u32 s); +u64 print_integer(u64 obj); +u64 print_binteger(u64 obj); +u64 print_float(u32 f); +u64 print_vu_function(u32 obj); +u64 copy_fixed(u32 it); +u64 copy_structure(u32 it, u32 unknown); +u64 inspect_integer(u64 obj); +u64 inspect_binteger(u64 obj); +u64 inspect_float(u32 f); +u64 inspect_structure(u32 obj); +u64 inspect_vu_function(u32 obj); +u64 inspect_kheap(u32 obj); \ No newline at end of file diff --git a/game/kernel/ksocket.cpp b/game/kernel/common/ksocket.cpp similarity index 93% rename from game/kernel/ksocket.cpp rename to game/kernel/common/ksocket.cpp index 5a24ff20b9..cf6042eca4 100644 --- a/game/kernel/ksocket.cpp +++ b/game/kernel/common/ksocket.cpp @@ -1,16 +1,8 @@ -/*! - * @file ksocket.cpp - * GOAL Socket connection to listener using DECI2/DSNET - * DONE! - */ - #include "ksocket.h" -#include "fileio.h" -#include "kboot.h" -#include "kdsnetm.h" -#include "klisten.h" -#include "kprint.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/kprint.h" /*! * Update GOAL message header after receiving and verify message is ok. diff --git a/game/kernel/ksocket.h b/game/kernel/common/ksocket.h similarity index 74% rename from game/kernel/ksocket.h rename to game/kernel/common/ksocket.h index 2eb2e5227f..fd7d51e7bd 100644 --- a/game/kernel/ksocket.h +++ b/game/kernel/common/ksocket.h @@ -1,18 +1,18 @@ #pragma once -/*! - * @file ksocket.h - * GOAL Socket connection to listener using DECI2/DSNET - */ - -#ifndef JAK_KSOCKET_H -#define JAK_KSOCKET_H - -#include "Ptr.h" -#include "kmachine.h" - #include "common/common_types.h" +#include "game/kernel/common/Ptr.h" + +/*! + * Update GOAL message header after receiving and verify message is ok. + * Return the size of the message in bytes (not including DECI or GOAL headers) + * Return -1 on error. + * The buffer parameter is unused. + * DONE, removed call to FlushCache(0); + */ +u32 ReceiveToBuffer(char* buff); + /*! * Update GOAL message header after receiving and verify message is ok. * Return the size of the message in bytes (not including DECI or GOAL headers) @@ -49,6 +49,4 @@ Ptr WaitForMessageAndAck(); /*! * Doesn't close anything, just print a closed message. */ -void CloseListener(); - -#endif // JAK_KSOCKET_H +void CloseListener(); \ No newline at end of file diff --git a/game/kernel/common/ksound.cpp b/game/kernel/common/ksound.cpp new file mode 100644 index 0000000000..35d49e4329 --- /dev/null +++ b/game/kernel/common/ksound.cpp @@ -0,0 +1,27 @@ +#include "ksound.h" + +#include "common/common_types.h" + +#include "game/overlord/srpc.h" +#include "game/sound/989snd/ame_handler.h" + +/*! + * Does nothing! + */ +void InitSound() {} + +/*! + * Does nothing! + */ +void ShutdownSound() {} + +/*! + * PC port functions + */ +void set_flava_hack(u64 val) { + snd::SoundFlavaHack = val; +} + +void set_fade_hack(u64 val) { + gMusicFadeHack = val; +} diff --git a/game/kernel/common/ksound.h b/game/kernel/common/ksound.h new file mode 100644 index 0000000000..2e167338a7 --- /dev/null +++ b/game/kernel/common/ksound.h @@ -0,0 +1,7 @@ +#pragma once +#include "common/common_types.h" + +void InitSound(); +void ShutdownSound(); +void set_flava_hack(u64 val); +void set_fade_hack(u64 val); \ No newline at end of file diff --git a/game/kernel/common/memory_layout.h b/game/kernel/common/memory_layout.h new file mode 100644 index 0000000000..2a808d7f85 --- /dev/null +++ b/game/kernel/common/memory_layout.h @@ -0,0 +1,31 @@ +#pragma once + +#include "common/common_types.h" + +//! Toggle to use more memory. To simulate the original game's memory layout, set this to false. +// Make sure this matches the const in gcommon.gc. +constexpr bool BIG_MEMORY = true; + +//! How much space to leave for the stack when creating the debug heap +// In the game, it's 16 kB, but we increase it to 64 kB. +// ASAN builds + fmt stuff uses a _ton_ of stack when no optimizations are on and we +// need more. +constexpr u32 DEBUG_HEAP_SPACE_FOR_STACK = 0x10000; + +//! First free address for the GOAL heap +constexpr u32 HEAP_START = 0x13fd20; + +//! Where to end the global heap so it doesn't overlap with the stack. +constexpr u32 GLOBAL_HEAP_END = 0x1ffc000 + (BIG_MEMORY ? (0x1ffc000 - HEAP_START) : 0); // doubled + +//! Location of kglobalheap, kdebugheap kheapinfo structures. +constexpr u32 GLOBAL_HEAP_INFO_ADDR = 0x13AD00; +constexpr u32 DEBUG_HEAP_INFO_ADDR = 0x13AD10; +constexpr u32 LINK_CONTROL_NAME_ADDR = 0x13AD80; + +//! Where to place the debug heap +constexpr u32 DEBUG_HEAP_START = 0x5000000; + +namespace jak2 { +constexpr u32 DEBUG_HEAP_SIZE = 0x2f00000; +} \ No newline at end of file diff --git a/game/kernel/fileio.cpp b/game/kernel/jak1/fileio.cpp similarity index 51% rename from game/kernel/fileio.cpp rename to game/kernel/jak1/fileio.cpp index 0caac14aba..f491a54807 100644 --- a/game/kernel/fileio.cpp +++ b/game/kernel/jak1/fileio.cpp @@ -1,237 +1,14 @@ -/*! - * @file fileio.cpp - * GOAL Low-Level File I/O and String Utilities - * DONE! - */ - #include "fileio.h" #include #include -#include "kprint.h" - -#include "common/util/Assert.h" #include "common/versions.h" -#include "game/sce/sif_ee.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kmalloc.h" -namespace { -// buffer for file paths. This might be static char buffer[512]. Maybe 633 is the line number? -char buffer_633[512]; -} // namespace - -void fileio_init_globals() { - memset(buffer_633, 0, 512); -} - -using namespace ee; - -/*! - * Return pointer to null terminator of string. - * const is for losers. - * DONE, EXACT - */ -char* strend(char* str) { - while (*str) - str++; - return str; -} - -/*! - * An implementation of Huffman decoding. - * In this limited decoder, your data must have lower two bits equal to zero. - * @param loc_ptr pointer to pointer to data to read (will be modified to point to next word) - * @return decoded word - * UNUSED, EXACT - */ -u32 ReadHufWord(u8** loc_ptr) { - u8* loc = *loc_ptr; // pointer to data to read - u32 value = *(u32*)loc; // read word - u8* next_loc = loc + 1; // next data to read - u32 length = value & 3; // length of word is stored in lower two bits. - switch (length) { - case 0: // already all set. - break; - - case 1: - value = (value & 0xfc) | (loc[1] << 8); - next_loc = loc + 2; - break; - - case 2: - value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10); - next_loc = loc + 3; - break; - - case 3: - value = (value & 0xfc) | (loc[1] << 8) | (loc[2] << 0x10) | (loc[3] << 0x18); - next_loc = loc + 4; - break; - - default: - ASSERT(false); - } - - // update location pointer - *loc_ptr = next_loc; - return value; -} - -/*! - * Copy a string from src to dst. The null terminator is copied too. - * This is identical to normal strcpy. - * DONE, EXACT - */ -void kstrcpy(char* dst, const char* src) { - char* dst_ptr = dst; - const char* src_ptr = src; - - while (*src_ptr != 0) { - *dst_ptr = *src_ptr; - src_ptr++; - dst_ptr++; - } - *dst_ptr = 0; -} - -/*! - * Copy a string from src to dst, making all letters upper case. - * The null terminator is copied too. - * DONE, EXACT - */ -void kstrcpyup(char* dst, const char* src) { - while (*src) { - char c = *src; - if (c >= 'a' && c <= 'z') { // A-Z,a-z - c -= 0x20; - } - *dst = c; - dst++; - src++; - } - *dst = 0; -} - -/*! - * Concatenate two strings. Src is added to dest. - * The new string is null terminated. No bounds checking is done. - * DONE, EXACT - */ -void kstrcat(char* dest, const char* src) { - // seek to end of first string - while (*dest) { - dest++; - } - // copy second string - while (*src) { - *dest = *src; - src++; - dest++; - } - // null terminate - *dest = 0; -} - -/*! - * Concatenate two strings with a maximum length for the resulting string - * The maximum length should be larger than the length of the original string. - * The resulting string will be truncated when it reaches the given length. - * The null terminator is added, but doesn't count toward the length. - * DONE, EXACT - */ -void kstrncat(char* dest, const char* src, s32 count) { - // seek to null terminator of first string, count length - s32 i = 0; - while (*dest) { - dest++; - i++; - } - - // append second string, not exceeding length - while (*src && (i < count)) { - *dest = *src; - src++; - dest++; - i++; - } - - // null terminate - *dest = 0; -} - -/*! - * Insert the pad char at the beginning of a string, count times. - * DONE, EXACT - */ -char* kstrinsert(char* str, char pad, s32 count) { - // shift string+null terminator to the right. - s32 len = strlen(str); - while (len > -1) { - str[len + count] = str[len]; - len--; - } - - // pad - len = 0; - while (len < count) { - str[len++] = pad; - } - return str; -} - -/*! - * Get filename from path. - * This function is renamed to basename_goal so it doesn't conflict with "basename" that is - * already defined on my computer. - * For example: - * a/b/c.e will return c.e - * a\b\c.e will return c.e - * asdf.asdf will return asdf.asdf - * DONE, EXACT - */ -char* basename_goal(char* s) { - char* input = s; - char* pt = s; - - // seek to end - for (;;) { - char c = *pt; - if (c) { - pt++; - } else { - break; - } - } - - /* Original code, has memory bug. - // back up... - for (;;) { - if (pt < input) { - return input; - } - pt--; - char c = *pt; - // until we hit a slash. - if (c == '\\' || c == '/') { // slashes - return pt + 1; // and return one past - } - } - */ - - // back up... - for (;;) { - if (pt <= input) { - return input; - } - pt--; - char c = *pt; - // until we hit a slash. - if (c == '\\' || c == '/') { // slashes - return pt + 1; // and return one past - } - } -} +namespace jak1 { /*! * Turn file name into file's path. @@ -408,122 +185,4 @@ char* MakeFileName(int type, const char* name, int new_string) { return result; } -/*! - * Does the file exist? No. It doesn't. - * @return 0 always, even if the file exists. - * DONE, EXACT, UNUSED - */ -u32 FileExists(const char* name) { - (void)name; - return 0; -} - -/*! - * Does nothing. Likely is supposed to delete a file. - * @param name - * DONE, EXACT, UNUSED - */ -void FileDelete(const char* name) { - (void)name; -} - -/*! - * Does nothing. Likely is supposed to copy a file. - * @param a - * @param b - * DONE, EXACT, UNUSED - */ -void FileCopy(const char* a, const char* b) { - (void)a; - (void)b; -} - -/*! - * Determine the file length in bytes. - * DONE, EXACT - */ -s32 FileLength(char* filename) { - s32 fd = sceOpen(filename, SCE_RDONLY); - if (fd < 0) { - MsgErr("dkernel: file length !open \'%s\' (%d)\n", filename, fd); - sceClose(fd); - return 0xfffffffb; - } else { - s32 rv = sceLseek(fd, 0, SCE_SEEK_END); - sceClose(fd); - return rv; - } -} - -/*! - * Load a file into memory - * @param name : file name - * @param heap : heap to allocate into, if memory is null - * @param memory : memory to load into. If null, allocates on the given kheap (with 64 extra bytes) - * @param malloc_flags : flags for the kmalloc - * @param size_out : file size is written here, if it's not null - * @return pointer to file data - * DONE, EXACT - */ -Ptr FileLoad(char* name, Ptr heap, Ptr memory, u32 malloc_flags, s32* size_out) { - s32 fd = sceOpen(name, SCE_RDONLY); - if (fd < 0) { - MsgErr("dkernel: file read !open \'%s\' (%d)\n", name, fd); - sceClose(fd); - return Ptr(0xfffffffb); - } - - // determine size - s32 initial_pos = sceLseek(fd, 0, SCE_SEEK_CUR); - s32 size = sceLseek(fd, 0, SCE_SEEK_END); - sceLseek(fd, initial_pos, SCE_SEEK_SET); - - if (size > 0) { - if (memory.offset == 0) { - memory = kmalloc(heap, size + 0x40, malloc_flags, name); - } - if (memory.offset == 0) { - MsgErr("dkernel: mem full for file read: '%s' (%d bytes)\n", name, size); - return Ptr(0xfffffffd); - } - - s32 read_amount = sceRead(fd, memory.c(), size); - if (read_amount == size) { - sceClose(fd); - if (size_out) - *size_out = size; - return memory; - } else { - MsgErr("dkernel: can't read full file (%d of %d): '%s'\n", read_amount, size, name); - sceClose(fd); - return Ptr(0xfffffffb); - } - } else { - return Ptr(0); - } -} - -/*! - * Write a file. - * DONE, EXACT - */ -s32 FileSave(char* name, u8* data, s32 size) { - s32 fd = sceOpen(name, SCE_WRONLY | SCE_TRUNC | SCE_CREAT); - if (fd < 0) { - MsgErr("dkernel: file write !open '%s'\n", name); - sceClose(fd); - return 0xfffffffa; - } - - if (size != 0) { - s32 written = sceWrite(fd, data, size); - if (written != size) { - MsgErr("dkernel: can't write full file '%s'\n", name); - sceClose(fd); - return 0xfffffffa; - } - } - - sceClose(fd); - return 0; -} +} // namespace jak1 diff --git a/game/kernel/jak1/fileio.h b/game/kernel/jak1/fileio.h new file mode 100644 index 0000000000..4daad7a1e1 --- /dev/null +++ b/game/kernel/jak1/fileio.h @@ -0,0 +1,6 @@ +#pragma once + +namespace jak1 { +char* MakeFileName(int type, const char* name, int new_string); +char* DecodeFileName(const char* name); +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/kboot.cpp b/game/kernel/jak1/kboot.cpp similarity index 82% rename from game/kernel/kboot.cpp rename to game/kernel/jak1/kboot.cpp index a9544f2b30..029b524af6 100644 --- a/game/kernel/kboot.cpp +++ b/game/kernel/jak1/kboot.cpp @@ -12,66 +12,24 @@ #include #include -#include "klisten.h" -#include "kmachine.h" -#include "kprint.h" -#include "kscheme.h" -#include "ksocket.h" - #include "common/common_types.h" #include "common/util/Timer.h" #include "game/common/game_common_types.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/jak1/klisten.h" +#include "game/kernel/jak1/kmachine.h" #include "game/sce/libscf.h" -#ifdef _WIN32 -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include - -#include "Windows.h" -#elif __linux__ -#include -#endif - using namespace ee; +namespace jak1 { VideoMode BootVideoMode; -// Level to load on boot -char DebugBootLevel[64]; - -// Pass to GOAL kernel on boot -char DebugBootMessage[64]; - -// game configuration -MasterConfig masterConfig; - -// Set to 1 to kill GOAL kernel -RuntimeExitStatus MasterExit; - -// Set to 1 to enable debug heap -u32 MasterDebug; - -// Set to 1 to load debug code -u32 DebugSegment; - -// Set to 1 to load game engine after boot automatically -u32 DiskBoot; - -u32 MasterUseKernel; - -void kboot_init_globals() { - strcpy(DebugBootLevel, "#f"); // no specified level - strcpy(DebugBootMessage, "play"); // play mode, the default retail mode - - MasterExit = RuntimeExitStatus::RUNNING; - MasterDebug = 1; - MasterUseKernel = 1; - DebugSegment = 1; - DiskBoot = 0; - memset(&masterConfig, 0, sizeof(MasterConfig)); -} +void kboot_init_globals() {} /*! * Launch the GOAL Kernel (EE). @@ -205,3 +163,4 @@ void KernelCheckAndDispatch() { void KernelShutdown() { MasterExit = RuntimeExitStatus::EXIT; // GOAL Kernel Dispatch loop will stop now. } +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/jak1/kboot.h b/game/kernel/jak1/kboot.h new file mode 100644 index 0000000000..cf1c452d67 --- /dev/null +++ b/game/kernel/jak1/kboot.h @@ -0,0 +1,41 @@ +#pragma once + +/*! + * @file kboot.h + * GOAL Boot. Contains the "main" function to launch GOAL runtime. + */ + +#include "common/common_types.h" + +#include "game/kernel/common/kboot.h" + +namespace jak1 { + +// Video Mode that's set based on display refresh rate on boot +extern VideoMode BootVideoMode; + +/*! + * Initialize global variables for kboot + */ +void kboot_init_globals(); + +/*! + * Launch the GOAL Kernel (EE). + * See InitParms for launch argument details. + * @param argc : argument count + * @param argv : argument list + * @return 0 on success, otherwise failure. + */ +s32 goal_main(int argc, const char* const* argv); + +/*! + * Run the GOAL Kernel. + */ +void KernelCheckAndDispatch(); + +/*! + * Stop running the GOAL Kernel. + */ +void KernelShutdown(); + +} // namespace jak1 diff --git a/game/kernel/jak1/kdgo.cpp b/game/kernel/jak1/kdgo.cpp new file mode 100644 index 0000000000..f165b7556e --- /dev/null +++ b/game/kernel/jak1/kdgo.cpp @@ -0,0 +1,93 @@ +#include "kdgo.h" + +#include "common/common_types.h" +#include "common/log/log.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/jak1/klink.h" + +namespace jak1 { +/*! + * 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) { + 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); + link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now! + + // inform IOP we are done + if (!lastObjectLoaded) { + ContinueLoadingDGO(Ptr((heap->current + 0x3f).offset & 0xffffffc0)); + } + } + sShowStallMsg = oldShowStall; +} +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/jak1/kdgo.h b/game/kernel/jak1/kdgo.h new file mode 100644 index 0000000000..65b38c0af6 --- /dev/null +++ b/game/kernel/jak1/kdgo.h @@ -0,0 +1,13 @@ +#pragma once +#include "common/common_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, + 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); +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/klink.cpp b/game/kernel/jak1/klink.cpp similarity index 64% rename from game/kernel/klink.cpp rename to game/kernel/jak1/klink.cpp index 422556feb4..de2b56f9d4 100644 --- a/game/kernel/klink.cpp +++ b/game/kernel/jak1/klink.cpp @@ -1,266 +1,40 @@ -/*! - * @file klink.cpp - * GOAL Linker for x86-64 - * Note - this is significantly different from the MIPS linker because the object file format is - * different. - * DONE! - */ - #include "klink.h" -#include -#include - -#include "fileio.h" -#include "kboot.h" -#include "kprint.h" -#include "kscheme.h" - -#include "common/goal_constants.h" #include "common/log/log.h" #include "common/symbols.h" -#include "common/util/Assert.h" -#include +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/kmachine.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/common/memory_layout.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_table.h" #include "third-party/fmt/core.h" -using namespace jak1_symbols; - -namespace { -// turn on printf's for debugging linking issues. -constexpr bool link_debug_printfs = false; - -bool is_opengoal_object(const void* data) { - auto* header = (const LinkHeaderV2*)data; - return !(header->type_tag == 0xffffffff && (header->version == 2 || header->version == 4)); -} -} // namespace - -// space to store a single in-progress linking state. -link_control saved_link_control; - -// pointer to GOAL *ultimate-memcpy*, if its loaded. -Ptr gfunc_774; - -void klink_init_globals() { - saved_link_control.reset(); - gfunc_774.offset = 0; -} - -/*! - * Initialize the link control. - */ -void link_control::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; - 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; - - 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 - ultimate_memcpy(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; - // not an open goal object. - if (link_debug_printfs) { - printf("Linking GOAL style object\n"); - } - - // initialize - m_object_data = object_file; - kstrcpy(m_object_name, name); - m_object_size = size; - m_heap = heap; - m_flags = flags; - m_entry.offset = 0; - m_heap_top = m_heap->top; - m_keep_debug = false; - m_link_block_ptr = object_file + BASIC_OFFSET; - m_code_size = 0; - m_code_start = object_file; - m_state = 0; - m_segment_process = 0; - - const auto* header = (LinkHeaderV2*)(m_link_block_ptr.c() - 4); - - m_version = header->version; - if (header->version < 4) { - // seek past header - m_object_data.offset += header->length; - m_code_size = m_object_size - header->length; - 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, header->length, KMALLOC_TOP, "link-block"); - auto old_link_block = m_link_block_ptr - BASIC_OFFSET; - - // copy it - ultimate_memcpy(new_link_block.c(), old_link_block.c(), header->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 { - auto header_v4 = (const LinkHeaderV4*)header; - auto old_object_data = m_object_data; - m_link_block_ptr = - old_object_data + header_v4->code_size + sizeof(LinkHeaderV4) + BASIC_OFFSET; - m_object_data = old_object_data + sizeof(LinkHeaderV4); - m_code_size = header_v4->code_size; - } - - if ((m_flags & LINK_FLAG_FORCE_DEBUG) && MasterDebug && !DiskBoot) { - m_keep_debug = true; - } - } -} - +static constexpr bool link_debug_printfs = false; /*! * Make progress on linking. */ -uint32_t link_control::work() { +uint32_t link_control::jak1_work() { auto old_debug_segment = DebugSegment; if (m_keep_debug) { - DebugSegment = s7.offset + FIX_SYM_TRUE; + DebugSegment = s7.offset + true_symbol_offset(g_game_version); } // set type tag of link block - *((m_link_block_ptr - 4).cast()) = *((s7 + FIX_SYM_LINK_BLOCK).cast()); + *((m_link_block_ptr - 4).cast()) = *((s7 + jak1_symbols::FIX_SYM_LINK_BLOCK).cast()); uint32_t rv; if (m_version == 3) { ASSERT(m_opengoal); - rv = work_v3(); + rv = jak1_work_v3(); } else if (m_version == 2 || m_version == 4) { ASSERT(!m_opengoal); - rv = work_v2(); + rv = jak1_work_v2(); } else { ASSERT_MSG(false, fmt::format("UNHANDLED OBJECT FILE VERSION {} IN WORK!", m_version)); return 0; @@ -269,88 +43,7 @@ uint32_t link_control::work() { DebugSegment = old_debug_segment; return rv; } - -/*! - * 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 - uint8_t method_count = link.c()[seek++]; - - // intern the GOAL type, creating the vtable if it doesn't exist. - auto type_ptr = intern_type_from_c(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 = intern_from_c(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 { - // otherwise store the offset to st. Eventually this should become an s16 instead. - *(data + offset).cast() = sym_offset; - } - } - - return seek; -} - +namespace { /*! * Link a single relative offset (used for RIP) */ @@ -399,11 +92,93 @@ uint32_t ptr_link_v3(Ptr link, ObjectFileHeader* ofh, int current_seg) { 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 + uint8_t method_count = link.c()[seek++]; + + // intern the GOAL type, creating the vtable if it doesn't exist. + auto type_ptr = jak1::intern_type_from_c(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 = jak1::intern_from_c(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 { + // otherwise store the offset to st. Eventually this should become an s16 instead. + *(data + offset).cast() = sym_offset; + } + } + + return seek; +} + +} // namespace /*! * Run the linker. For now, all linking is done in two runs. If this turns out to be too slow, * this should be modified to do incremental linking over multiple runs. */ -uint32_t link_control::work_v3() { +uint32_t link_control::jak1_work_v3() { ObjectFileHeader* ofh = m_link_block_ptr.cast().c(); if (m_state == 0) { // state 0 <- copying data. @@ -434,8 +209,8 @@ uint32_t link_control::work_v3() { ofh->code_infos[seg_id].size); return 1; } - ultimate_memcpy(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), - ofh->code_infos[seg_id].size); + jak1::ultimate_memcpy(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), + ofh->code_infos[seg_id].size); } } } else if (seg_id == MAIN_SEGMENT) { @@ -450,8 +225,8 @@ uint32_t link_control::work_v3() { ofh->code_infos[seg_id].size); return 1; } - ultimate_memcpy(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), - ofh->code_infos[seg_id].size); + jak1::ultimate_memcpy(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) { @@ -466,8 +241,8 @@ uint32_t link_control::work_v3() { ofh->code_infos[seg_id].size); return 1; } - ultimate_memcpy(Ptr(ofh->code_infos[seg_id].offset).c(), src.c(), - ofh->code_infos[seg_id].size); + jak1::ultimate_memcpy(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"); @@ -531,49 +306,13 @@ uint32_t link_control::work_v3() { } } -Ptr c_symlink2(Ptr objData, Ptr linkObj, Ptr relocTable) { - u8* relocPtr = relocTable.c(); - Ptr objPtr = objData; - - do { - u8 table_value = *relocPtr; - u32 result = table_value; - u8* next_reloc = relocPtr + 1; - - if (result & 3) { - result = (relocPtr[1] << 8) | table_value; - next_reloc = relocPtr + 2; - if (result & 2) { - result = (relocPtr[2] << 16) | result; - next_reloc = relocPtr + 3; - if (result & 1) { - result = (relocPtr[3] << 24) | result; - next_reloc = relocPtr + 4; - } - } - } - - relocPtr = next_reloc; - objPtr = objPtr + (result & 0xfffffffc); - u32 objValue = *(objPtr.cast()); - if (objValue == 0xffffffff) { - *(objPtr.cast()) = linkObj.offset; - } else { - // I don't think we should hit this ever. - ASSERT(false); - } - } while (*relocPtr); - - return make_ptr(relocPtr + 1); -} - #define LINK_V2_STATE_INIT_COPY 0 #define LINK_V2_STATE_OFFSETS 1 #define LINK_V2_STATE_SYMBOL_TABLE 2 #define OBJ_V2_CLOSE_ENOUGH 0x90 #define OBJ_V2_MAX_TRANSFER 0x80000 -uint32_t link_control::work_v2() { +uint32_t link_control::jak1_work_v2() { // u32 startCycle = kernel.read_clock(); todo if (m_state == LINK_V2_STATE_INIT_COPY) { // initialization and copying to heap @@ -620,14 +359,15 @@ uint32_t link_control::work_v2() { u32 size = m_code_size - m_segment_process; if (size > OBJ_V2_MAX_TRANSFER) { // around .5 MB - ultimate_memcpy((m_object_data + m_segment_process).c(), source.c(), OBJ_V2_MAX_TRANSFER); + jak1::ultimate_memcpy((m_object_data + m_segment_process).c(), source.c(), + OBJ_V2_MAX_TRANSFER); m_segment_process += OBJ_V2_MAX_TRANSFER; return 0; // return, don't want to take too long. } // if we have bytes to copy, but they are less than the max transfer, do it in one shot! if (size) { - ultimate_memcpy((m_object_data + m_segment_process).c(), source.c(), size); + jak1::ultimate_memcpy((m_object_data + m_segment_process).c(), source.c(), size); if (m_segment_process > 0) { // if we did a previous copy, we return now.... m_state = LINK_V2_STATE_OFFSETS; m_segment_process = 0; @@ -737,7 +477,7 @@ uint32_t link_control::work_v2() { if (link_debug_printfs) { printf("[work_v2] symlink: %s\n", name); } - goalObj = intern_from_c(name).cast(); + goalObj = jak1::intern_from_c(name).cast(); } else { // type! u8 nMethods = relocation & 0x7f; @@ -748,7 +488,7 @@ uint32_t link_control::work_v2() { if (link_debug_printfs) { printf("[work_v2] symlink -type: %s\n", name); } - goalObj = intern_type_from_c(name, nMethods).cast(); + goalObj = jak1::intern_type_from_c(name, nMethods).cast(); } m_reloc_ptr.offset += strlen(name) + 1; // DECOMPILER->hookStartSymlinkV3(_state - 1, _objectData, std::string(name)); @@ -773,12 +513,12 @@ uint32_t link_control::work_v2() { /*! * Complete linking. This will execute the top-level code for v3 object files, if requested. */ -void link_control::finish(bool jump_from_c_to_goal) { +void link_control::jak1_finish(bool jump_from_c_to_goal) { CacheFlush(m_code_start.c(), 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 + FIX_SYM_TRUE; + DebugSegment = s7.offset + jak1_symbols::FIX_SYM_TRUE; } if (m_flags & LINK_FLAG_FORCE_FAST_LINK) { FastLink = 1; @@ -814,9 +554,9 @@ void link_control::finish(bool jump_from_c_to_goal) { auto entry = m_entry; auto name = basename_goal(m_object_name); strcpy(Ptr(LINK_CONTROL_NAME_ADDR).c(), name); - call_method_of_type_arg2(entry.offset, Ptr(*((entry - 4).cast())), - GOAL_RELOC_METHOD, m_heap.offset, - Ptr(LINK_CONTROL_NAME_ADDR).offset); + jak1::call_method_of_type_arg2(entry.offset, Ptr(*((entry - 4).cast())), + GOAL_RELOC_METHOD, m_heap.offset, + Ptr(LINK_CONTROL_NAME_ADDR).offset); } } @@ -826,6 +566,8 @@ void link_control::finish(bool jump_from_c_to_goal) { DebugSegment = old_debug_segment; } +namespace jak1 { + /*! * Immediately link and execute an object file. * DONE, EXACT @@ -840,9 +582,9 @@ Ptr link_and_exec(Ptr data, lc.begin(data, name, size, heap, flags); uint32_t done; do { - done = lc.work(); + done = lc.jak1_work(); } while (!done); - lc.finish(jump_from_c_to_goal); + lc.jak1_finish(jump_from_c_to_goal); return lc.m_entry; } @@ -865,11 +607,11 @@ 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]); - auto work_result = saved_link_control.work(); + auto work_result = saved_link_control.jak1_work(); // if we managed to finish in one shot, take care of calling finish if (work_result) { // called from goal - saved_link_control.finish(false); + saved_link_control.jak1_finish(false); } return work_result != 0; @@ -879,10 +621,10 @@ uint64_t link_begin(u64* args) { * GOAL exported function for doing a small amount of linking work on the saved_link_control */ uint64_t link_resume() { - auto work_result = saved_link_control.work(); + auto work_result = saved_link_control.jak1_work(); if (work_result) { // called from goal - saved_link_control.finish(false); + saved_link_control.jak1_finish(false); } return work_result != 0; } @@ -898,7 +640,7 @@ void ultimate_memcpy(void* dst, void* src, uint32_t size) { 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 = find_symbol_from_c("ultimate-memcpy"); + auto sym = jak1::find_symbol_from_c("ultimate-memcpy"); if (sym->value == 0) { memmove(dst, src, size); return; @@ -913,16 +655,4 @@ void ultimate_memcpy(void* dst, void* src, uint32_t size) { memmove(dst, src, size); } } - -// The functions below are not ported because they are specific to the MIPS implementation. -// In the MIPS implementation, the c_ functions are used until GOAL loads its GOAL-implemented -// versions of the same functions. The update_goal_fns detects this and causes the linker to use -// the GOAL versions once possible. The GOAL version is much faster, but functionally equivalent to -// the C version. The C version is compiled without optimization, so this isn't too surprising. -// the rellink function is unused. -/* -c_rellink3__FPvP12link_segmentPUc -c_symlink2__FPvUiPUc -c_symlink3__FPvUiPUc -update_goal_fns__Fv - */ +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/jak1/klink.h b/game/kernel/jak1/klink.h new file mode 100644 index 0000000000..f3b16c0e83 --- /dev/null +++ b/game/kernel/jak1/klink.h @@ -0,0 +1,19 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/kmalloc.h" + +namespace jak1 { +Ptr link_and_exec(Ptr data, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags, + bool jump_from_c_to_goal); +void ultimate_memcpy(void* dst, void* src, uint32_t size); +u64 link_and_exec_wrapper(u64* args); +uint64_t link_begin(u64* args); +uint64_t link_resume(); +} // namespace jak1 diff --git a/game/kernel/klisten.cpp b/game/kernel/jak1/klisten.cpp similarity index 58% rename from game/kernel/klisten.cpp rename to game/kernel/jak1/klisten.cpp index d8837eff5d..8e514a2d4e 100644 --- a/game/kernel/klisten.cpp +++ b/game/kernel/jak1/klisten.cpp @@ -1,41 +1,29 @@ -/*! - * @file klisten.cpp - * Implementation of the Listener protocol - * Done - */ - #include "klisten.h" #include #include #include -#include "kboot.h" -#include "kdsnetm.h" -#include "klink.h" -#include "kmalloc.h" -#include "kprint.h" -#include "kscheme.h" -#include "ksocket.h" - #include "common/symbols.h" -using namespace jak1_symbols; +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/jak1/klink.h" +namespace jak1 { +using namespace jak1_symbols; Ptr ListenerLinkBlock; Ptr ListenerFunction; Ptr kernel_dispatcher; Ptr kernel_packages; -Ptr print_column; -u32 ListenerStatus; void klisten_init_globals() { ListenerLinkBlock.offset = 0; ListenerFunction.offset = 0; kernel_dispatcher.offset = 0; kernel_packages.offset = 0; - print_column.offset = 0; - ListenerStatus = 0; } /*! @@ -60,63 +48,6 @@ void InitListener() { // } } -/*! - * Flush pending messages. If debugging, will send to compiler, otherwise to stdout. - */ -void ClearPending() { - if (!MasterDebug) { - // if we aren't debugging print the print buffer to stdout. - if (PrintPending.offset != 0) { - auto size = strlen(PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); - if (size > 0) { - printf("%s", PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); - } - clear_print(); - } - } else { - if (ListenerStatus) { - if (OutputPending.offset != 0) { - Ptr msg = OutputBufArea.cast() + sizeof(ListenerMessageHeader); - auto size = strlen(msg.c()); - // note - if size is ever greater than 2^16 this will cause an issue. - SendFromBuffer(msg.c(), size); - clear_output(); - } - - if (PrintPending.offset != 0) { - char* msg = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); - auto size = strlen(msg); - while (size > 0) { - // sends larger than 64 kB are broken by the GoalProtoBuffer thing, so they are split - auto send_size = size; - if (send_size > 64000) { - send_size = 64000; - } - SendFromBufferD(2, 0, msg, send_size); - size -= send_size; - msg += send_size; - } - clear_print(); - } - } - } -} - -/*! - * Send an "ack" message. The original game had the AckBufArea which stores "ack", but did not - * calculate the length correctly, so the message would not actually contain the "ack" text. - * The "ack" text is unimportant, as the compiler can recognize the messages as ACK due to the - * ListenerMessageKind::MSG_ACK field. Both the type and msg_id fields are sent, which is enough - * for it to work. - */ -void SendAck() { - if (MasterDebug) { - SendFromBufferD(u16(ListenerMessageKind::MSG_ACK), protoBlock.msg_id, - AckBufArea + sizeof(ListenerMessageHeader), - strlen(AckBufArea + sizeof(ListenerMessageHeader))); - } -} - /*! * Handle an incoming listener message */ @@ -169,3 +100,5 @@ void ProcessListenerMessage(Ptr msg) { } SendAck(); } + +} // namespace jak1 diff --git a/game/kernel/jak1/klisten.h b/game/kernel/jak1/klisten.h new file mode 100644 index 0000000000..8caa9d8173 --- /dev/null +++ b/game/kernel/jak1/klisten.h @@ -0,0 +1,14 @@ +#pragma once + +#include "game/kernel/jak1/kscheme.h" + +namespace jak1 { +extern Ptr ListenerFunction; +extern Ptr kernel_dispatcher; +extern Ptr kernel_packages; + +void klisten_init_globals(); + +void InitListener(); +void ProcessListenerMessage(Ptr msg); +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/kmachine.cpp b/game/kernel/jak1/kmachine.cpp similarity index 66% rename from game/kernel/kmachine.cpp rename to game/kernel/jak1/kmachine.cpp index 2cf5f805ae..e387a98b77 100644 --- a/game/kernel/kmachine.cpp +++ b/game/kernel/jak1/kmachine.cpp @@ -1,82 +1,45 @@ -/*! - * @file kmachine.cpp - * GOAL Machine. Contains low-level hardware interfaces for GOAL. - * Not yet done - some controller stuff isn't implemented, and also many of the SCE functions - * are just stubs or commented out for now. Legal splash screen stuff is also missing. - */ - #include "kmachine.h" -#include +#include #include -#include "fileio.h" -#include "kboot.h" -#include "kdgo.h" -#include "kdsnetm.h" -#include "klink.h" -#include "klisten.h" -#include "kmalloc.h" -#include "kprint.h" -#include "kscheme.h" -#include "ksocket.h" -#include "ksound.h" -#include "svnrev.h" - -#include "common/dma/dma_chain_read.h" -#include "common/dma/dma_copy.h" -#include "common/global_profiler/GlobalProfiler.h" #include "common/log/log.h" #include "common/symbols.h" -#include "common/util/Assert.h" #include "common/util/FileUtil.h" -#include "common/util/Timer.h" #include "game/discord.h" #include "game/graphics/gfx.h" #include "game/graphics/sceGraphicsInterface.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kboot.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/kscheme.h" +#include "game/kernel/common/ksocket.h" +#include "game/kernel/common/ksound.h" +#include "game/kernel/common/memory_layout.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/jak1/ksound.h" +#include "game/kernel/svnrev.h" #include "game/mips2c/mips2c_table.h" #include "game/sce/libcdvd_ee.h" #include "game/sce/libdma.h" #include "game/sce/libgraph.h" -#include "game/sce/libpad.h" -#include "game/sce/libscf.h" #include "game/sce/sif_ee.h" #include "game/sce/stubs.h" -#include "game/system/newpad.h" #include "game/system/vm/vm.h" -using namespace jak1_symbols; using namespace ee; -/*! - * Where does OVERLORD load its data from? - */ -OverlordDataSource isodrv; - -// Get IOP modules from DVD or from dsefilesv -u32 modsrc; - -// Reboot IOP with IOP kernel from DVD/CD on boot -u32 reboot; - -u8 pad_dma_buf[2 * SCE_PAD_DMA_BUFFER_SIZE]; - -const char* init_types[] = {"fakeiso", "deviso", "iso_cd"}; - -Timer ee_clock_timer; - -// added -u32 vif1_interrupt_handler = 0; - -void kmachine_init_globals() { - isodrv = fakeiso; // changed. fakeiso is the only one that works in opengoal. - modsrc = 1; - reboot = 1; - memset(pad_dma_buf, 0, sizeof(pad_dma_buf)); - ee_clock_timer = Timer(); - vif1_interrupt_handler = 0; -} +namespace jak1 { /*! * Initialize global variables based on command line parameters. Not called in retail versions, @@ -187,20 +150,6 @@ void InitParms(int argc, const char* const* argv) { } } -/*! - * Initialize the CD Drive - * DONE, EXACT - */ -void InitCD() { - lg::info("Initializing CD drive. This may take a while..."); - sceCdInit(SCECdINIT); - sceCdMmode(SCECdDVD); - while (sceCdDiskReady(0) == SCECdNotReady) { - lg::debug("Drive not ready... insert a disk!"); - } - lg::debug("Disk type {}\n", sceCdGetDiskType()); -} - /*! * Initialize the I/O Processor * Removed calls to exit(0) if loading modules fails. @@ -335,12 +284,6 @@ void InitIOP() { } } -/*! - * Initialize the GS and display the splash screen. - * Not yet implemented. TODO - */ -void InitVideo() {} - /*! * Initialize GOAL Runtime. This is the main initialization which is called before entering * the GOAL kernel dispatch loop (KernelCheckAndDispatch). @@ -369,20 +312,20 @@ int InitMachine() { kdebugheap.offset = 0; } - init_output(); // GOAL input/output buffer setup - InitIOP(); // start IOP/OVERLORD, loading our legal splash screen + init_output(); // GOAL input/output buffer setup + jak1::InitIOP(); // start IOP/OVERLORD, loading our legal splash screen - // sceGsResetPath(); // reset VIF1, VU1, GIF + // sceGsResetPath(); // reset VIF1, VU1, GIF InitVideo(); // display legal splash screen - // FlushCache(WRITEBACK_DCACHE); - // FlushCache(INVALIDATE_ICACHE); - // sceGsSyncV(0); // wait for it to show up on the screen + // FlushCache(WRITEBACK_DCACHE); + // FlushCache(INVALIDATE_ICACHE); + // sceGsSyncV(0); // wait for it to show up on the screen // - // if(scePadInit(0) != 1) { // init controllers - // MsgErr("dkernel: !init pad\n"); - // } + // if(scePadInit(0) != 1) { // init controllers + // MsgErr("dkernel: !init pad\n"); + // } // do this always // if (MasterDebug) { // connect to GOAL compiler @@ -427,155 +370,7 @@ int ShutdownMachine() { return 0; } -/*! - * Flush caches. Does all the memory, regardless of what you specify - */ -void CacheFlush(void* mem, int size) { - (void)mem; - (void)size; - // FlushCache(0); - // FlushCache(2); -} - -/*! - * Open a new controller pad. - * Set the new_pad flag to 1 and state to 0. - * Prints an error if it fails to open. - */ -u64 CPadOpen(u64 cpad_info, s32 pad_number) { - auto cpad = Ptr(cpad_info).c(); - if (cpad->cpad_file == 0) { - // not open, so we will open it - cpad->cpad_file = - ee::scePadPortOpen(pad_number, 0, pad_dma_buf + pad_number * SCE_PAD_DMA_BUFFER_SIZE); - if (cpad->cpad_file < 1) { - MsgErr("dkernel: !open cpad #%d (%d)\n", pad_number, cpad->cpad_file); - } - cpad->new_pad = 1; - cpad->state = 0; - } - return cpad_info; -} - -u64 CPadGetData(u64 cpad_info) { - auto cpad = Ptr(cpad_info).c(); - auto pad_state = scePadGetState(cpad->number, 0); - if (pad_state == scePadStateDiscon) { - cpad->state = 0; - } - cpad->valid = pad_state | 0x80; - switch (cpad->state) { - // case 99: // functional - default: // controller is functioning as normal - if (pad_state == scePadStateStable || pad_state == scePadStateFindCTP1) { - scePadRead(cpad->number, 0, (u8*)cpad); - // ps2 controllers would send an enabled bit if the button was NOT pressed, but we don't do - // that here. removed code that flipped the bits. - - if (cpad->change_time != 0) { - scePadSetActDirect(cpad->number, 0, cpad->direct); - } - cpad->valid = pad_state; - } - break; - case 0: // unavailable - if (pad_state == scePadStateStable || pad_state == scePadStateFindCTP1) { - auto pad_mode = scePadInfoMode(cpad->number, 0, InfoModeCurID, 0); - if (pad_mode != 0) { - auto vibration_mode = scePadInfoMode(cpad->number, 0, InfoModeCurExID, 0); - if (vibration_mode > 0) { - // vibration supported - pad_mode = vibration_mode; - } - if (pad_mode == 4) { - // controller mode - cpad->state = 40; - } else if (pad_mode == 7) { - // dualshock mode - cpad->state = 70; - } else { - // who knows mode - cpad->state = 90; - } - } - } - break; - case 40: // controller mode - check for extra modes - cpad->change_time = 0; - if (scePadInfoMode(cpad->number, 0, InfoModeIdTable, -1) == 0) { - // no controller modes - cpad->state = 90; - return cpad_info; - } - cpad->state = 41; - case 41: // controller mode - change to dualshock mode! - // try to enter the 2nd controller mode (dualshock for ds2's) - if (scePadSetMainMode(cpad->number, 0, 1, 3) == 1) { - cpad->state = 42; - } - break; - case 42: // controller mode change check - if (scePadGetReqState(cpad->number, 0) == scePadReqStateFailed) { - // failed to change to DS2 - cpad->state = 41; - } - if (scePadGetReqState(cpad->number, 0) == scePadReqStateComplete) { - // change successful. go back to the beginning. - cpad->state = 0; - } - break; - case 70: // dualshock mode - check vibration - // get number of actuators (2 for DS2) - if (scePadInfoAct(cpad->number, 0, -1, 0) < 1) { - // no actuators means no vibration. skip to end! - cpad->change_time = 0; - cpad->state = 99; - } else { - // we have actuators to use. - cpad->change_time = 1; // remember to update pad times. - cpad->state = 75; - } - break; - case 75: // set actuator vib param info - if (scePadSetActAlign(cpad->number, 0, cpad->align) != 0) { - if (scePadInfoPressMode(cpad->number, 0) == 1) { - // pressure buttons supported - cpad->state = 76; - } else { - // no pressure buttons, done with controller setup - cpad->state = 99; - } - } - break; - case 76: // enter pressure mode - if (scePadEnterPressMode(cpad->number, 0) == 1) { - cpad->state = 78; - } - break; - case 78: // pressure mode request check - if (scePadGetReqState(cpad->number, 0) == scePadReqStateFailed) { - cpad->state = 76; - } - if (scePadGetReqState(cpad->number, 0) == scePadReqStateComplete) { - cpad->state = 99; - } - break; - case 90: - break; // unsupported controller. too bad! - } - return cpad_info; -} - -// TODO InstallHandler -void InstallHandler(u32 handler_idx, u32 handler_func) { - ASSERT(handler_idx == 5); // vif1 - vif1_interrupt_handler = handler_func; -} -// TODO InstallDebugHandler -void InstallDebugHandler() { - ASSERT(false); -} - +// todo, these could probably be moved to common void send_gfx_dma_chain(u32 /*bank*/, u32 chain) { Gfx::send_chain(g_ee_main_mem, chain); } @@ -632,145 +427,13 @@ u64 kopen(u64 fs, u64 name, u64 mode) { return fs; } -/*! - * Get length of a file. - */ -s32 klength(u64 fs) { - auto file_stream = Ptr(fs).c(); - if ((file_stream->flags ^ 1) & 1) { - // first flag bit not set. This means no errors - auto end_seek = sceLseek(file_stream->file, 0, SCE_SEEK_END); - auto reset_seek = sceLseek(file_stream->file, 0, SEEK_SET); - if (reset_seek < 0 || end_seek < 0) { - // seeking failed, flag it - file_stream->flags |= 1; - } - return end_seek; - } else { - return 0; - } -} - -/*! - * Seek a file stream. - */ -s32 kseek(u64 fs, s32 offset, s32 where) { - s32 result = -1; - auto file_stream = Ptr(fs).c(); - if ((file_stream->flags ^ 1) & 1) { - result = sceLseek(file_stream->file, offset, where); - if (result < 0) { - file_stream->flags |= 1; - } - } - return result; -} - -/*! - * Read from a file stream. - */ -s32 kread(u64 fs, u64 buffer, s32 size) { - s32 result = -1; - auto file_stream = Ptr(fs).c(); - if ((file_stream->flags ^ 1) & 1) { - result = sceRead(file_stream->file, Ptr(buffer).c(), size); - if (result < 0) { - file_stream->flags |= 1; - } - } - return result; -} - -/*! - * Write to a file stream. - */ -s32 kwrite(u64 fs, u64 buffer, s32 size) { - s32 result = -1; - auto file_stream = Ptr(fs).c(); - if ((file_stream->flags ^ 1) & 1) { - result = sceWrite(file_stream->file, Ptr(buffer).c(), size); - if (result < 0) { - file_stream->flags |= 1; - } - } - return result; -} - -/*! - * Close a file stream. - */ -u64 kclose(u64 fs) { - auto file_stream = Ptr(fs).c(); - if ((file_stream->flags ^ 1) & 1) { - sceClose(file_stream->file); - file_stream->file = -1; - } - file_stream->flags = 0; - return fs; -} - -// TODO dma_to_iop -void dma_to_iop() { - ASSERT(false); -} - -u64 DecodeLanguage() { - return masterConfig.language; -} - -u64 DecodeAspect() { - return masterConfig.aspect; -} - -u64 DecodeVolume() { - return masterConfig.volume; -} - -// NOTE: this is originally hardcoded, and returns different values depending on the disc region. -// it returns 0 for NTSC-U, 1 for PAL and 2 for NTSC-J -u64 DecodeTerritory() { - return GAME_TERRITORY_SCEA; -} - -u64 DecodeTimeout() { - return masterConfig.timeout; -} - -u64 DecodeInactiveTimeout() { - return masterConfig.inactive_timeout; -} - -void DecodeTime(u32 ptr) { - Ptr clock(ptr); - sceCdReadClock(clock.c()); -} - -// TODO PutDisplayEnv void PutDisplayEnv(u32 ptr) { + // we can mostly ignore this, except for one value that sets the 'blackout' amount. u8 alp = Ptr(ptr).c()[1]; auto* renderer = Gfx::GetCurrentRenderer(); if (renderer) { renderer->set_pmode_alp(alp / 255.f); } - // ASSERT(false); -} - -/*! - * PC PORT FUNCTIONS BEGIN - */ -/*! - * Get a 300MHz timer value. - */ -u64 read_ee_timer() { - u64 ns = ee_clock_timer.getNs(); - return (ns * 3) / 10; -} - -/*! - * Do a fast memory copy. - */ -void c_memmove(u32 dst, u32 src, u32 size) { - memmove(Ptr(dst).c(), Ptr(src).c(), size); } /*! @@ -786,57 +449,6 @@ u64 get_os() { #endif } -/*! - * Returns size of window. - */ -void get_window_size(u32 w_ptr, u32 h_ptr) { - if (w_ptr) { - auto w = Ptr(w_ptr).c(); - *w = Gfx::get_window_width(); - } - if (h_ptr) { - auto h = Ptr(h_ptr).c(); - *h = Gfx::get_window_height(); - } -} - -/*! - * Returns scale of window. This is for DPI stuff. - */ -void get_window_scale(u32 x_ptr, u32 y_ptr) { - float* x = x_ptr ? Ptr(x_ptr).c() : NULL; - float* y = y_ptr ? Ptr(y_ptr).c() : NULL; - Gfx::get_window_scale(x, y); -} - -/*! - * Returns resolution of the monitor. - */ -void get_screen_size(s64 vmode_idx, u32 w_ptr, u32 h_ptr) { - s32 *w_out = 0, *h_out = 0; - if (w_ptr) { - w_out = Ptr(w_ptr).c(); - } - if (h_ptr) { - h_out = Ptr(h_ptr).c(); - } - Gfx::get_screen_size(vmode_idx, w_out, h_out); -} - -/*! - * Returns refresh rate of the monitor. - */ -s64 get_screen_rate(s64 vmode_idx) { - return Gfx::get_screen_rate(vmode_idx); -} - -/*! - * Returns amount of video modes of the monitor. - */ -s64 get_screen_vmode_count() { - return Gfx::get_screen_vmode_count(); -} - void update_discord_rpc(u32 discord_info) { if (gDiscordRpcEnabled) { DiscordRichPresence rpc; @@ -942,23 +554,6 @@ void update_discord_rpc(u32 discord_info) { } } -u64 filepath_exists(u32 filepath) { - auto filepath_str = std::string(Ptr(filepath).c()->data()); - if (std::filesystem::exists(filepath_str)) { - return intern_from_c("#t").offset; - } - return s7.offset; -} - -void mkdir_path(u32 filepath) { - auto filepath_str = std::string(Ptr(filepath).c()->data()); - file_util::create_dir_if_needed_for_file(filepath_str); -} - -void prof_event(u32 name, u32 kind) { - prof().event(Ptr(name).c()->data(), (ProfNode::Kind)kind); -} - u32 get_fullscreen() { switch (Gfx::get_fullscreen()) { default: @@ -981,38 +576,6 @@ void set_fullscreen(u32 symptr, s64 screen) { } } -void set_frame_rate(s64 rate) { - Gfx::set_frame_rate(rate); -} - -void set_vsync(u32 symptr) { - Gfx::set_vsync(symptr != s7.offset); -} - -void set_window_lock(u32 symptr) { - Gfx::set_window_lock(symptr == s7.offset); -} - -void set_collision(u32 symptr) { - Gfx::g_global_settings.collision_enable = symptr != s7.offset; -} - -void set_collision_wireframe(u32 symptr) { - Gfx::g_global_settings.collision_wireframe = symptr != s7.offset; -} - -void set_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask, u32 symptr) { - if (symptr != s7.offset) { - Gfx::CollisionRendererSetMask(mode, mask); - } else { - Gfx::CollisionRendererClearMask(mode, mask); - } -} - -u32 get_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask) { - return Gfx::CollisionRendererGetMask(mode, mask) ? s7.offset + FIX_SYM_TRUE : s7.offset; -} - void InitMachine_PCPort() { // PC Port added functions @@ -1082,23 +645,6 @@ void InitMachine_PCPort() { intern_from_c("*pc-settings-folder*")->value = make_string_from_c(settings_path.string().c_str()); intern_from_c("*pc-settings-built-sha*")->value = make_string_from_c(GIT_VERSION); } -/*! - * PC PORT FUNCTIONS END - */ - -void vif_interrupt_callback() { - // added for the PC port for faking VIF interrupts from the graphics system. - if (vif1_interrupt_handler && MasterExit == RuntimeExitStatus::RUNNING) { - call_goal(Ptr(vif1_interrupt_handler), 0, 0, 0, s7.offset, g_ee_main_mem); - } -} - -/*! - * Added in PC port. - */ -u32 offset_of_s7() { - return s7.offset; -} /*! * Final initialization of the system after the kernel is loaded. @@ -1136,8 +682,8 @@ void InitMachineScheme() { make_function_symbol_from_c("scf-get-timeout", (void*)DecodeTimeout); // used make_function_symbol_from_c("scf-get-inactive-timeout", (void*)DecodeInactiveTimeout); // used make_function_symbol_from_c("dma-to-iop", (void*)dma_to_iop); // unused - make_function_symbol_from_c("kernel-shutdown", (void*)KernelShutdown); // used - make_function_symbol_from_c("aybabtu", (void*)sceCdMmode); // used + make_function_symbol_from_c("kernel-shutdown", (void*)jak1::KernelShutdown); // used TODO jak1 + make_function_symbol_from_c("aybabtu", (void*)sceCdMmode); // used InitMachine_PCPort(); InitSoundScheme(); @@ -1158,7 +704,7 @@ void InitMachineScheme() { LINK_FLAG_OUTPUT_LOAD | LINK_FLAG_EXECUTE | LINK_FLAG_PRINT_LOGIN, 0x400000, true); *EnableMethodSet = (*EnableMethodSet) - 1; - + using namespace jak1_symbols; kernel_packages->value = new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast()), make_string_from_c("engine"), kernel_packages->value); @@ -1169,7 +715,9 @@ void InitMachineScheme() { new_pair(s7.offset + FIX_SYM_GLOBAL_HEAP, *((s7 + FIX_SYM_PAIR_TYPE).cast()), make_string_from_c("common"), kernel_packages->value); - lg::info("calling fake play"); + lg::info("calling play"); call_goal_function_by_name("play"); } } + +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/jak1/kmachine.h b/game/kernel/jak1/kmachine.h new file mode 100644 index 0000000000..b27062f7c2 --- /dev/null +++ b/game/kernel/jak1/kmachine.h @@ -0,0 +1,42 @@ +#pragma once + +#include "common/common_types.h" +// Discord RPC +struct DiscordRichPresence; +extern int gDiscordRpcEnabled; +extern int64_t gStartTime; +namespace jak1 { +/*! + * Initialize global variables based on command line parameters + */ +void InitParms(int argc, const char* const* argv); +/*! + * Initialize the I/O Processor + */ +void InitIOP(); +/*! + * Initialze GOAL Runtime + */ +int InitMachine(); +/*! + * Shutdown GOAL runtime. + */ +int ShutdownMachine(); + +void InitMachineScheme(); + +struct DiscordInfo { + u32 fuel; + u32 money_total; + u32 buzzer_total; + u32 deaths; + u32 status; + u32 level; + u32 cutscene; // check if cutscene is playing + u32 ogreboss; // are we fighting ogreboss? + u32 plantboss; // are we fighting plant-boss? + u32 racer; // are we driving the zoomer? + u32 flutflut; // are we riding on flut flut? + u32 time_of_day; +}; +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/kprint.cpp b/game/kernel/jak1/kprint.cpp similarity index 50% rename from game/kernel/kprint.cpp rename to game/kernel/jak1/kprint.cpp index 81a53d54cc..942b9aa511 100644 --- a/game/kernel/kprint.cpp +++ b/game/kernel/jak1/kprint.cpp @@ -1,30 +1,17 @@ -/*! - * @file kprint.cpp - * GOAL Print. Contains GOAL I/O, Print, Format... - */ - #include "kprint.h" -#include -#include #include #include -#include "fileio.h" -#include "kboot.h" -#include "kdsnetm.h" -#include "klink.h" -#include "klisten.h" -#include "kmachine.h" -#include "kmalloc.h" -#include "kscheme.h" - -#include "common/common_types.h" -#include "common/cross_os_debug/xdbg.h" -#include "common/goal_constants.h" +#include "common/listener_common.h" #include "common/symbols.h" -#include "common/util/Assert.h" +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/sce/sif_ee.h" using namespace jak1_symbols; @@ -33,558 +20,6 @@ using namespace jak1_symbols; // SDATA /////////// -// Pointer set to something in the middle of the output buffer, if there is something in the buffer. -Ptr OutputPending; - -// Pointer set to something in the middle of the print buffer, if there is something in the buffer -Ptr PrintPending; - -// Size of incoming message. -s32 MessCount; - -// Pointer to message buffer, the compiler to target buffer -Ptr MessBufArea; - -// Pointer to the output buffer, the runtime to compiler buffer -Ptr OutputBufArea; - -// Pointer to print buffer, the buffer for printing and string formatting. -Ptr PrintBufArea; - -// integer printing conversion table -char ConvertTable[16]; - -// buffer for sending an "acknowledge" message to the compiler -char AckBufArea[40]; - -/*! - * Initialize global variables for kprint - */ -void kprint_init_globals() { - OutputPending.offset = 0; - PrintPending.offset = 0; - MessCount = 0; - MessBufArea.offset = 0; - OutputBufArea.offset = 0; - PrintBufArea.offset = 0; - memcpy(ConvertTable, "0123456789abcdef", 16); - memset(AckBufArea, 0, sizeof(AckBufArea)); -} - -/*! - * Initialize GOAL Kernel printing/messaging system. - * Allocates buffers. - * DONE, EXACT - */ -void init_output() { - if (MasterDebug) { - MessBufArea = kmalloc(kdebugheap, DEBUG_MESSAGE_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, - "mess-buf"); - OutputBufArea = kmalloc(kdebugheap, DEBUG_OUTPUT_BUFFER_SIZE, - KMALLOC_MEMSET | KMALLOC_ALIGN_256, "output-buf"); - PrintBufArea = kmalloc(kdebugheap, DEBUG_PRINT_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, - "print-buf"); - } else { - // no compiler connection, so we do not allocate buffers - MessBufArea = Ptr(0); - OutputBufArea = Ptr(0); - - // we still need a (small) print buffer for string maniuplation and debugging prints. - PrintBufArea = - kmalloc(kglobalheap, PRINT_BUFFER_SIZE, KMALLOC_MEMSET | KMALLOC_ALIGN_256, "print-buf"); - } -} - -/*! - * Empty output buffer (only if MasterDebug) - * DONE - * EXACT - */ -void clear_output() { - if (MasterDebug) { - kstrcpy((char*)Ptr(OutputBufArea + sizeof(ListenerMessageHeader)).c(), ""); - OutputPending = Ptr(0); - } -} - -/*! - * Clear all data in the print buffer - * DONE - * EXACT - */ -void clear_print() { - *Ptr(PrintBufArea + sizeof(ListenerMessageHeader)) = 0; - PrintPending = Ptr(0); -} - -/*! - * Buffer message to compiler indicating the target has reset. - * Write to the beginning of the output buffer. - * DONE, EXACT - */ -void reset_output() { - if (MasterDebug) { -// original GOAL: -// sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), "reset #x%x\n", -// s7.offset); - -// modified for OpenGOAL: -#ifdef _WIN32 - sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), - "reset #x%x #x%llx %s\n", s7.offset, (unsigned long long)g_ee_main_mem, // grr - xdbg::get_current_thread_id().to_string().c_str()); -#else - sprintf(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader), "reset #x%x #x%lx %s\n", - s7.offset, (uintptr_t)g_ee_main_mem, xdbg::get_current_thread_id().to_string().c_str()); -#endif - OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); - } -} - -/*! - * Buffer message to compiler indicating some object file has been unloaded. - * DONE, EXACT - */ -void output_unload(const char* name) { - if (MasterDebug) { - sprintf(strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)), - "unload \"%s\"\n", name); - OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); - } -} - -/*! - * Buffer message to compiler indicating some object file has been loaded. - */ -void output_segment_load(const char* name, Ptr link_block, u32 flags) { - if (MasterDebug) { - char* buffer = strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)); - char true_str[] = "t"; - char false_str[] = "nil"; - char* flag_str = (flags & LINK_FLAG_OUTPUT_TRUE) ? true_str : false_str; - auto lbp = link_block.cast(); - // modified to also include segment sizes. - sprintf(buffer, "load \"%s\" %s #x%x #x%x #x%x #x%x #x%x #x%x\n", name, flag_str, - lbp->code_infos[0].offset, lbp->code_infos[1].offset, lbp->code_infos[2].offset, - lbp->code_infos[0].size, lbp->code_infos[1].size, lbp->code_infos[2].size); - OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); - } -} - -/*! - * Print to the GOAL print buffer from C - * seeks PrintPending to begining of what was just printed. - * This is a different behavior from all the other prints! - * DONE, EXACT - */ -void cprintf(const char* format, ...) { - va_list args; - va_start(args, format); - char* str = PrintPending.cast().c(); - if (!PrintPending.offset) - str = PrintBufArea.cast().c() + sizeof(ListenerMessageHeader); - PrintPending = make_ptr(strend(str)).cast(); - vsprintf((char*)PrintPending.c(), format, args); - - va_end(args); -} - -/*! - * Print directly to the C stdout - * The "k" parameter is ignored, so this is just like printf - * DONE, EXACT - */ -void Msg(s32 k, const char* format, ...) { - (void)k; - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); -} - -/*! - * Print directly to the C stdout - * This is idential to Msg - * DONE, EXACT - */ -void MsgWarn(const char* format, ...) { - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); -} - -/*! - * Print directly to the C stdout - * This is idential to Msg - * DONE, EXACT - */ -void MsgErr(const char* format, ...) { - va_list args; - va_start(args, format); - vprintf(format, args); - va_end(args); -} - -/*! - * Reverse string in place. - * DONE, EXACT - */ -void reverse(char* str) { - s32 i = 0; - s32 end = (s32)strlen(str); - while (end--, i < end) { - char c = str[i]; - str[i] = str[end]; - str[end] = c; - i++; - } -} - -/*! - * Some sort of rounding for printing floating point numbers. - * It is unused and believed to be not correct. - * Currently copy-pasta from GHIDRA - */ -char* round(float x, s32* param1, char* start, char* sEnd, char padchar, s32* param4) { - char cVar1; - char* local_58; - float f; - - if (x == 0.00000000) { - f = padchar - '0'; - } else { - modff(x * 10.00000000, &f); - } - if (4.00000000 < f) { - while (true) { - if (*sEnd == '.') { - sEnd = sEnd + -1; - } - cVar1 = *sEnd; - *sEnd = cVar1 + 1; - if ((char)(cVar1 + 1) <= '9') { - return start; - } - *sEnd = '0'; - if (sEnd == start) - break; - sEnd = sEnd + -1; - } - if (param1 == (int*)0x0) { - sEnd[-1] = '1'; - local_58 = start + -1; - } else { - *sEnd = '1'; - *param1 = *param1 + 1; - local_58 = start; - } - } else { - local_58 = start; - if (*param4 == 0x2d) { - while (true) { - if (*sEnd == '.') { - sEnd = sEnd + -1; - } - local_58 = start; - if (*sEnd != '0') - break; - if (sEnd == start) { - *param4 = 0; - } - sEnd = sEnd + -1; - } - } - } - return local_58; -} - -/*! - * Convert floating point to string intermediate function. - * Places a null character as the first character, then the integers, decimal, fraction digits - * Presumably, if rounding worked, it would sometimes add an additional digit in front instead of - * the null but rounding doesn't, so you get a null in front every time. There is no null terminator - * added, you have to do it yourself. returned value is length : buff[last_char] - buff[1] (not - * including the leading null) if the number is negative, *lead_char = '-'. Otherwise it's '\0'. - * The negative is not inserted for you. - * The precision is how many digits after decimal to print. - * The flag could have a 1 to enable rounding, but this doesn't work, so don't use it. - * Without rounding the printing is a little bit off but you don't notice unless you look too - * closely. - * - * DONE, added some sanity checks and removed support for "rounding" as round isn't implemented and - * rounding is never used in the game. - */ -s32 cvt_float(float x, s32 precision, s32* lead_char, char* buff_start, char* buff_end, u32 flags) { - // put a null at the beginning of the output - *buff_start = 0; - s32 forward_count = 0; - - // compute absolute value and set lead char to `-` if needed. - float abs_x; - if (x < 0.0f) { - abs_x = -x; - *lead_char = '-'; - } else { - *lead_char = 0; - abs_x = x; - } - - // nan check - u32 abs_x_u32 = *(u32*)&abs_x; - if ((abs_x_u32 & 0x7fffffff) == 0x7fffffff) { - kstrcpy(buff_start, "NaN"); - return 3; // the length of NaN - } - - // find fraction and integer parts of absolute value. - float integer_part; - float fraction_part = std::modf(abs_x, &integer_part); - - char* start_ptr = buff_start + 1; // the null terminator is at buff_start[0]. - char* end_ptr = buff_end - 1; // the last char we can write to. - - // loop over integer digits (increasing significance) - while (start_ptr <= end_ptr && (integer_part != 0.0f)) { - // the fractional part will be the lowest place integer divided by 10 - float next_int = std::modf(integer_part / 10.f, &integer_part); - - // the float to round to get the integer - float rounder = next_int * 10.f + 0.5f; - - // but wait, the PS2 is stupid and maybe has weird floats that might not convert well. - // this checks for very large exponent or negative sign bit, which would only happen if the - // float is all messed up. - u32 ru32 = *(u32*)&rounder; - s32 value; - if (((ru32 >> 0x17) & 0xff) < 0x9e) { // exponent - value = (char)rounder; - } else if (!(ru32 >> 31)) { // sign bit - value = 0; - // ASSERT(false); // not sure what happens here. - } else { - value = -1; // happens on NaN's - } - - // place number at the end of the buffer and move pointer back - *end_ptr = '0' + value; - end_ptr--; - - // track how many integers we've written - forward_count++; - } - - char* count_chrp = start_ptr; // one after the buffer start (null) - if (forward_count == 0) { // no integers - *start_ptr = '0'; // put leading zero - count_chrp = buff_start + 2; // and set count to two after buffer start - } else { - while (end_ptr = end_ptr + 1, - end_ptr < buff_end) { // copy back to the beginning (will be in right order) - *count_chrp = *end_ptr; - count_chrp++; - } - } - - // if we have digits after decimal, place decimal. - if (precision) { - *count_chrp = '.'; - count_chrp++; - } - - s32 prec = precision; - - // if we have a fractional part, we may need to either print it, or do rounding. - if (fraction_part != 0.0f) { - prec = precision; - if (precision) { - // same loop as before, but only over the number of digits we actually want to print. - do { - float next_int; - fraction_part = std::modf(fraction_part * 10.f, &next_int); - u32 ru32 = *(u32*)&next_int; - s32 value; - if (((ru32 >> 0x17) & 0xff) < 0x9e) { - value = (char)next_int; - } else if (!(ru32 >> 0x1f)) { - value = 0; - // ASSERT(false); // not sure what happens here. - } else { - value = -1; // happens on NaN's - } - *count_chrp = value + '0'; - count_chrp++; - prec--; - } while ((prec) && (fraction_part != 0.f)); - } - - // if the rounding flag is enabled, we would round here. - // however, the rounding flag is always disabled and the rounding code doesn't work. - if ((fraction_part != 0.f) && ((flags & 1) != 0)) { - start_ptr = round(fraction_part, nullptr, start_ptr, count_chrp - 1, 0, lead_char); - ASSERT(false); - } - } - - // if there are any left over digits (fraction part = 0 before we got to the end), append zeros. - while (prec = prec - 1, prec != -1) { - *count_chrp = '0'; - count_chrp++; - } - // return length. not including the null character at the beginning. - return count_chrp - start_ptr; -} - -/*! - * Convert floating point to a string. - * Don't set the precision too high or you get NaN (if float is longer than 31 digits without - * negative sign) Don't set the flags. Rounding is a little bit off, but it adds character. Highly - * recommended to pad with spaces, not zeros. If you pad with spaces you get " -1.23" If you pad - * with zeros, you get "0000000-1.23" which most people consider to be wrong. - * @param out_str : output buffer to write null terminated string into - * @param x : number - * @param desired_len : length (will pad if under, nothing if over) - * @param pad_char : character to pad with - * @param precision - * @param flags - * - * DONE - * - */ -void ftoa(char* out_str, float x, s32 desired_len, char pad_char, s32 precision, u32 flags) { - char buff[0x100]; - char* current_buff = buff; - s32 lead_char; - - // do conversion, but only write into the first half of the buffer (128 chars) - s32 count = cvt_float(x, precision, &lead_char, current_buff, buff + 0x7f, flags); - - // if it ends up larger than 31 characters, it's probably gone horribly wrong and we should just - // put NaN instead. Or maybe somebody requested a lot of precision. That would be stupid and they - // deserve to see NaN. - if (count > 0x3f) { - kstrcpy(current_buff, "NaN"); - count = 3; - lead_char = 0; - } - - // always true because we don't round, - if (buff[0] == 0) { - current_buff = buff + 1; - } - - // length, including the leading negative (if we need it). - s32 real_count = (lead_char != 0) + count; - - char* out_ptr = out_str; - - // pad - if ((desired_len > 0) && (desired_len > real_count)) { - for (s32 i = 0; i < (desired_len - real_count); i++) { - *out_ptr = pad_char; - out_ptr++; - } - } - - // leading - if (lead_char) { - *out_ptr = lead_char; - out_ptr++; - } - - // copy numbers - for (s32 i = 0; i < count; i++) { - *out_ptr = *current_buff; - out_ptr++; - current_buff++; - } - - // null terminate! - *out_ptr = 0; -} - -/*! - * Convert integer to string. - * @param buffer : buffer to print into. Must be at least as long as the longest possible number to - * print - * @param value : value to print. - * @param base : base to print in (2, 10, 16 supported) - * @param length : length. if shorter than length, pad with pad. If longer, only truncate leading - * f's 1's in base 16 or 2 - * @param pad : character to pad with - * @param flags : flag. Only the 2nd bit is used, which will disable negative sings on - * binary/hexadecimal truncated numbers. Something like -1 (0xffffffff) will print as -fffffff.... - */ - -char* kitoa(char* buffer, s64 value, u64 base, s32 length, char pad, u32 flag) { - s64 negativeValue = 0; - s64 value_to_print = value; - - // if negative and base ten, we print the opposite of the value and add a negative sign - if ((value < 0) && base == 10) { - negativeValue = value; - value_to_print = -value; - } - - // write number in reverse - int count = 0; - do { - buffer[count++] = ConvertTable[(u64)value_to_print % (u64)base]; - value_to_print = (u64)value_to_print / (u64)base; - } while (value_to_print); - - // append negative if we need to - if (negativeValue < 0) { - buffer[count++] = '-'; - } - - // pad (probably some sort of for loop) - s32 rLen = length; - if (0 < length - count) { - rLen = length - count; - while (0 < rLen) { - buffer[count++] = pad; - rLen--; - } - } - - // truncate f's / 1's - if (rLen > 0 && value < 0 && (base == 2 || base == 16) && rLen < count) { - char c = (base == 16) ? 'f' : '1'; - - while (rLen < count && (buffer[count - 1] == c)) { - count--; - } - - if (!(flag & 2)) { - buffer[count++] = '-'; - } - } - - // null terminate, reverse, return! - buffer[count] = 0; - reverse(buffer); - return buffer; -} - -/*! - * Convert 128-bit integer to string. Not implemented because it is never used in the game. - * It would also require passing 128-bit values between GOAL and C++ and this is not worth - * implementing. It is only used by the "format" function, which cannot use it properly. "format" - * uses C varags, but 128-bit varags don't work, so "format" always passes 0 for quadword printing. - */ -void kqtoa() { - ASSERT(false); -} - -struct format_struct { - char data[0x40]; - void reset() { - for (auto& c : data) - c = -1; - } -}; - /*! * 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 @@ -597,7 +32,8 @@ struct format_struct { * defined in format_wrapper.nasm that places the GOAL arguments on the stack and calls this * format_impl function with a single argument that is a pointer to the argument array. */ -s32 format_impl(uint64_t* args) { +s32 format_impl_jak1(uint64_t* args) { + using namespace jak1; // first two args are dest, format string uint64_t* arg_regs = args + 2; diff --git a/game/kernel/jak1/kprint.h b/game/kernel/jak1/kprint.h new file mode 100644 index 0000000000..2e896eb899 --- /dev/null +++ b/game/kernel/jak1/kprint.h @@ -0,0 +1,12 @@ +#pragma once + +/*! + * @file kprint.h + * GOAL Print. Contains GOAL I/O, Print, Format... + */ + +#include "common/common_types.h" + +extern "C" { +s32 format_impl_jak1(uint64_t* args); +} diff --git a/game/kernel/kscheme.cpp b/game/kernel/jak1/kscheme.cpp similarity index 87% rename from game/kernel/kscheme.cpp rename to game/kernel/jak1/kscheme.cpp index 932a190261..8ee98872a5 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/jak1/kscheme.cpp @@ -1,98 +1,36 @@ -/*! - * @file kscheme.cpp - * Implementation of GOAL runtime. - */ - #include "kscheme.h" #include -#include "fileio.h" -#include "kboot.h" -#include "kdgo.h" -#include "kdsnetm.h" -#include "klink.h" -#include "klisten.h" -#include "kmachine.h" -#include "kmalloc.h" -#include "kmemcard.h" -#include "kprint.h" - #include "common/common_types.h" -#include "common/goal_constants.h" #include "common/log/log.h" #include "common/symbols.h" -#include "common/util/Assert.h" #include "common/util/Timer.h" -#include "common/versions.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdgo.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/jak1/fileio.h" +#include "game/kernel/jak1/kdgo.h" +#include "game/kernel/jak1/klink.h" +#include "game/kernel/jak1/klisten.h" +#include "game/kernel/jak1/kmachine.h" +#include "game/kernel/jak1/kprint.h" #include "game/mips2c/mips2c_table.h" using namespace jak1_symbols; -//! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a -//! warning message if EnableMethodSet = 1 -u32 FastLink; - +namespace jak1 { // where to put a new symbol for the most recently searched for symbol that wasn't found u32 symbol_slot; -// pointer to the "second" symbol table -Ptr SymbolTable2; - -// pointer to the last symbol -Ptr LastSymbol; - -// total number of symbols in the table -s32 NumSymbols; - -// set to true to enable propagating method overrides to child types -// this is an O(N_max_symbols) operation, so it is avoided when loading DGOs for levels. -// but is enabled when loading the engine. -Ptr EnableMethodSet; - -// used for crc32 calculation -u32 crc_table[0x100]; - -// value of the GOAL s7 register, pointing to the middle of the symbol table -Ptr s7; - void kscheme_init_globals() { - for (auto& x : crc_table) { - x = 0; - } - NumSymbols = 0; - s7.offset = 0; - SymbolTable2.offset = 0; - LastSymbol.offset = 0; - EnableMethodSet.offset = 0; - FastLink = 0; -} - -/*! - * Initialize CRC Table. - */ -void init_crc() { - for (u32 i = 0; i < 0x100; i++) { - u32 n = i << 24; - for (u32 j = 0; j < 8; j++) { - n = n & 0x80000000 ? (n << 1) ^ CRC_POLY : (n << 1); - } - crc_table[i] = n; - } -} - -/*! - * Take the CRC32 hash of some data - */ -u32 crc32(const u8* data, s32 size) { - uint32_t crc = 0; - for (int i = size; i != 0; i--, data++) { - crc = crc_table[crc >> 24] ^ ((crc << 8) | *data); - } - - ASSERT(~crc); - return ~crc; + symbol_slot = 0; } /*! @@ -106,22 +44,6 @@ u64 new_illegal(u32 allocation, u32 type) { return s7.offset; } -/*! - * Delete method for types which cannot have "delete" used on them. - * Prints an error to stdout and returns false. - */ -u64 delete_illegal(u32 obj) { - MsgErr("dkernel: illegal attempt to call delete of static object @ #x%x\n", obj); - return s7.offset; // todo, maybe don't return anything? -} - -/*! - * Wrapper around kmalloc to allow GOAL programs to allocate on kernel heaps. - */ -u64 goal_malloc(u32 heap, u32 size, u32 flags, u32 name) { - return kmalloc(Ptr(heap), size, flags, Ptr(name)->data()).offset; -} - /*! * Allocate memory from the specified heap. If symbol is 'process, does a process allocation. * If symbol is 'scratch, does a scratch allocation (this is not used). @@ -134,6 +56,7 @@ u64 goal_malloc(u32 heap, u32 size, u32 flags, u32 name) { * UNKNOWN_PROCESS (UINT32_MAX). */ u64 alloc_from_heap(u32 heapSymbol, u32 type, s32 size, u32 pp) { + using namespace jak1_symbols; ASSERT(size > 0); // align to 16 bytes (part one) @@ -300,6 +223,7 @@ void delete_pair(u32 s) { * Allocates from the global heap. */ u64 make_string(u32 size) { + using namespace jak1_symbols; auto mem_size = size + 1; // null if (mem_size < 8) { mem_size = 8; // min size of string @@ -710,7 +634,7 @@ Ptr find_symbol_from_c(const char* name) { } } - auto bits = bits_for_sym() - 1; + auto bits = jak1::bits_for_sym() - 1; s32 sh1 = hash << (0x20 - bits); s32 sh2 = sh1 >> (0x20 - bits - 3); // will be signed, bottom 3 bits 0 (for alignment, symbols are every 8 bytes) @@ -852,7 +776,6 @@ Ptr intern_type_from_c(const char* name, u64 methods) { return type; } } - /*! * Wrapper of intern_type_from_c to use with GOAL. It accepts a gstring as a name. */ @@ -860,6 +783,23 @@ u64 intern_type(u32 name, u64 methods) { return intern_type_from_c(Ptr(name)->data(), methods).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; + if (type->num_methods < new_methods) { + type->num_methods = new_methods; + } + + return type; +} + /*! * Setup a type which is located in a fixed spot of the symbol table. */ @@ -939,13 +879,6 @@ u64 new_type(u32 symbol, u32 parent, u64 flags) { Ptr* child_slots = &(new_type->new_method); Ptr* parent_slots = &(Ptr(parent)->new_method); - // if (Ptr(parent)->num_methods < n_methods) { - // printf("%s %d %d\n", info(Ptr(symbol))->str.c()->data(), - // Ptr(parent)->num_methods, - // n_methods); - // ASSERT(false); - // } - // BUG! This uses the child method count, but should probably use the parent method count. for (u32 i = 0; i < n_methods; i++) { // for (u32 i = 0; i < Ptr(parent)->num_methods; i++) { @@ -955,23 +888,6 @@ u64 new_type(u32 symbol, u32 parent, u64 flags) { return set_type_values(new_type, Ptr(parent), flags).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; - if (type->num_methods < new_methods) { - type->num_methods = new_methods; - } - - return type; -} - /*! * Is t1 a t2? */ @@ -1115,52 +1031,6 @@ u64 method_set(u32 type_, u32 method_id, u32 method) { return method; } -extern "C" { -// defined in asm_funcs.asm -#ifdef __linux__ -uint64_t _call_goal_asm_linux(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); -uint64_t _call_goal_on_stack_asm_linux(u64 rsp, - u64 u0, - u64 u1, - void* fptr, - void* st_ptr, - void* offset); -#elif _WIN32 -uint64_t _call_goal_asm_win32(u64 a0, u64 a1, u64 a2, void* fptr, void* st_ptr, void* offset); -uint64_t _call_goal_on_stack_asm_win32(u64 rsp, void* fptr, void* st_ptr, void* offset); -#endif -} - -/*! - * Wrapper around _call_goal_asm for calling a GOAL function from C. - * Calls from the parent stack. - */ -u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset) { - // auto st_ptr = (void*)((uint8_t*)(offset) + st); updated for the new compiler! - void* st_ptr = (void*)st; - - void* fptr = f.c(); -#ifdef __linux__ - return _call_goal_asm_linux(a, b, c, fptr, st_ptr, offset); -#elif _WIN32 - return _call_goal_asm_win32(a, b, c, fptr, st_ptr, offset); -#endif -} - -/*! - * Wrapper around _call_goal_asm_on_stack for switching stacks and calling a GOAL function there. - */ -u64 call_goal_on_stack(Ptr f, u64 rsp, u64 st, void* offset) { - void* st_ptr = (void*)st; - - void* fptr = f.c(); -#ifdef __linux__ - return _call_goal_on_stack_asm_linux(rsp, 0, 0, fptr, st_ptr, offset); -#elif _WIN32 - return _call_goal_on_stack_asm_win32(rsp, fptr, st_ptr, offset); -#endif -} - /*! * Call a GOAL method of a given type. */ @@ -1183,13 +1053,6 @@ u64 call_method_of_type(u32 arg, Ptr type, u32 method_id) { return arg; } -/*! - * Call a GOAL function with no arguments. - */ -u64 call_goal_function(Ptr func) { - return call_goal(func, 0, 0, 0, s7.offset, g_ee_main_mem); -} - /*! * Call a global GOAL function by name. */ @@ -1219,6 +1082,9 @@ u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 return arg; } +u64 print_object(u32 obj); +u64 print_pair(u32 obj); + /*! * Print an object with a newline after it to the GOAL PrintBuffer (not stdout) */ @@ -1251,15 +1117,6 @@ u64 print_object(u32 obj) { return obj; } -/*! - * Default print method for structures. - * Structures have no runtime type info, so there's not much we can do here. - */ -u64 print_structure(u32 s) { - cprintf("#", s); - return s; -} - /*! * Default print method a basic. * Confirms basic is valid and prints the type name. @@ -1310,53 +1167,6 @@ u64 print_pair(u32 obj) { return obj; } -/*! - * Print an integer. Works correctly for 64-bit integers. - */ -u64 print_integer(u64 obj) { - // not sure why this is any better than cprintf("%ld") or similar. Maybe a tiny bit faster? - char* str = PrintPending.cast().c(); - if (!str) { - str = (PrintBufArea + 0x18).cast().c(); - } - - PrintPending = make_ptr(strend(str)).cast(); - kitoa((char*)PrintPending.c(), obj, 10, 0xffffffff, '0', 0); - return obj; -} - -/*! - * Print a boxed integer. Works correctly for 64-bit integers. Assumes signed. - */ -u64 print_binteger(u64 obj) { - char* str = PrintPending.cast().c(); - if (!PrintPending.offset) { - str = (PrintBufArea + 0x18).cast().c(); - } - - PrintPending = make_ptr(strend(str)).cast(); - kitoa((char*)PrintPending.c(), ((s64)obj) >> 3, 10, 0xffffffff, '0', 0); - return obj; -} - -/*! - * Print floating point number. - */ -u64 print_float(u32 f) { - // again not sure why this is any better than cprintf("%f") or similar. Maybe a tiny bit faster? - float ff; - *(u32*)&ff = f; - char* str = PrintPending.cast().c(); - if (!PrintPending.offset) { - str = (PrintBufArea + 0x18).cast().c(); - } - - PrintPending = make_ptr(strend(str)).cast(); - - ftoa((char*)PrintPending.c(), ff, 0xffffffff, ' ', 4, 0); - return f; -} - /*! * Print method for symbol. Just prints the name without quotes or anything fancy. */ @@ -1410,14 +1220,6 @@ u64 print_function(u32 obj) { return obj; } -/*! - * Print method for VU functions. Again, just prints address. - */ -u64 print_vu_function(u32 obj) { - cprintf("#", 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. @@ -1426,22 +1228,6 @@ u64 asize_of_basic(u32 it) { return Ptr(*Ptr(it - BASIC_OFFSET))->allocated_size; } -/*! - * Copy method that does no copying. - */ -u64 copy_fixed(u32 it) { - return it; -} - -/*! - * Default copy for a structure. Since this has no idea of the actual type, it doesn't know what - * size to copy. So we do no copy and return a reference to the original data. - */ -u64 copy_structure(u32 it, u32 unknown) { - (void)unknown; - return it; -} - /*! * 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 @@ -1468,6 +1254,8 @@ u64 copy_basic(u32 obj, u32 heap, u32 /*unused*/, u32 pp) { return result; } +u64 inspect_pair(u32 obj); + /*! * Highest level inspect method. Won't inspect 64-bit bintegers correctly. */ @@ -1500,44 +1288,6 @@ u64 inspect_pair(u32 obj) { return obj; } -/*! - * Inspect an integer (works correctly on 64-bit integers) - */ -u64 inspect_integer(u64 obj) { - // and now we're using cprintf. Why doesn't print do this? - cprintf("[%16lx] fixnum %ld\n", obj, obj); - return obj; -} - -/*! - * Inspect a boxed integer (works correctly on 64-integers) - */ -u64 inspect_binteger(u64 obj) { - cprintf("[%16lx] boxed-fixnum %ld\n", obj, s64(obj) >> 3); - return obj; -} - -/*! - * Inspect a floating point number - */ -u64 inspect_float(u32 f) { - float ff; - ff = *(float*)(&f); - cprintf("[%8x] float ", f); - - // likely copy-pasta - no need for this check because of the cprintf immediately before. - char* str = PrintPending.cast().c(); - if (!str) { - str = (PrintBufArea + 0x18).cast().c(); - } - - PrintPending = make_ptr(strend(str)).cast(); - - ftoa(PrintPending.cast().c(), ff, -1, ' ', 4, 0); - cprintf("\n"); - return f; -} - /*! * Inspect a string. There's a typo in allocated_length (has underscore instead of dash). * This typo is fixed in later games. @@ -1599,14 +1349,6 @@ u64 inspect_type(u32 obj) { return obj; } -/*! - * Inspect a structure. - */ -u64 inspect_structure(u32 obj) { - cprintf("[%8x] structure\n", obj); - 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. @@ -1641,33 +1383,6 @@ u64 inspect_link_block(u32 ob) { return ob; } -/*! - * Inspect a VU Function. Doesn't seem to be used. Also the concept of "vu-function" - * isn't really used. VU0 macro mode stuff goes in normal functions, and micro-mode stuff goes - * in a giant dump of many functions thats loaded and unloaded all at the same time. - */ -u64 inspect_vu_function(u32 obj) { - struct VuFunction { - u32 length; - u32 origin; - u32 qlength; - }; - - auto vf = Ptr(obj); - cprintf("[%8x] vu-function\n\tlength: %d\n\torigin: #x%x\n\tqlength: %d\n", obj, vf->length, - vf->origin, vf->qlength); - return obj; -} - -/*! - * This doesn't exist in the game, but we add it as a wrapper around kheapstatus. - * Note that this isn't a great inspect as it prints to stdout instead of the printbuffer. - */ -u64 inspect_kheap(u32 obj) { - kheapstatus(Ptr(obj)); - return obj; -} - /*! * Doesn't exist in the game. Maybe it was a macro? */ @@ -1675,31 +1390,6 @@ u64 pack_type_flag(u64 methods, u64 heap_base, u64 size) { return (methods << 32) + (heap_base << 16) + (size); } -// void iterate_symbol_table(std::function f) { -// auto sym = s7.offset; -// for (; sym < LastSymbol.offset; sym += 8) { -// f(sym); -// } -// -// sym = SymbolTable2.offset; -// for (; sym < s7.offset; sym += 8) { -// f(sym); -// } -//} -// -// void print_symbol_table() { -// u32 sym_cnt = 0; -// iterate_symbol_table([&](u32 sym) { -// if (info(Ptr(sym))->hash) { -// sym_cnt++; -// inspect_object(sym); -// // inspect_object(Ptr(sym)->value); -// // printf("\n"); -// } -// }); -// cprintf("Total %d symbols\n", sym_cnt); -//} - /*! * TODO remove me! */ @@ -1719,10 +1409,10 @@ s32 InitHeapAndSymbol() { Mips2C::gLinkedFunctionTable = {}; // allocate memory for the symbol table auto symbol_table = - kmalloc(kglobalheap, SYM_TABLE_MEM_SIZE, KMALLOC_MEMSET, "symbol-table").cast(); + kmalloc(kglobalheap, jak1::SYM_TABLE_MEM_SIZE, KMALLOC_MEMSET, "symbol-table").cast(); // pointer to the middle symbol is stored in the s7 register. - s7 = symbol_table + (GOAL_MAX_SYMBOLS / 2) * 8 + BASIC_OFFSET; + s7 = symbol_table + (jak1::GOAL_MAX_SYMBOLS / 2) * 8 + BASIC_OFFSET; // pointer to the first symbol (SymbolTable2 is the "lower" symbol table) SymbolTable2 = symbol_table + BASIC_OFFSET; // the last symbol we will ever access. @@ -1958,7 +1648,7 @@ s32 InitHeapAndSymbol() { make_function_symbol_from_c("load", (void*)load); 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); + make_stack_arg_function_symbol_from_c("_format", (void*)format_impl_jak1); // allocations make_function_symbol_from_c("malloc", (void*)alloc_heap_memory); @@ -2010,7 +1700,6 @@ s32 InitHeapAndSymbol() { lg::info("Initialized GOAL heap in {:.2} ms", heap_init_timer.getMs()); // load the kernel! - // todo, remove MasterUseKernel if (MasterUseKernel) { Timer kernel_load_timer; method_set_symbol->value++; @@ -2042,7 +1731,7 @@ s32 InitHeapAndSymbol() { InitListener(); // Do final initialization, including loading and initializing the engine. - InitMachineScheme(); + jak1::InitMachineScheme(); // testing stuff: make_function_symbol_from_c("test-function", (void*)test_function); @@ -2143,4 +1832,4 @@ s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 return (s32)rv.offset; } -// todo, read lcock code +} // namespace jak1 diff --git a/game/kernel/kscheme.h b/game/kernel/jak1/kscheme.h similarity index 66% rename from game/kernel/kscheme.h rename to game/kernel/jak1/kscheme.h index 8e7e15c02b..facf58077b 100644 --- a/game/kernel/kscheme.h +++ b/game/kernel/jak1/kscheme.h @@ -1,35 +1,11 @@ #pragma once -/*! - * @file kscheme.h - * Implementation of GOAL runtime. - */ - -#include "kmachine.h" -#include "kmalloc.h" - -#include "common/common_types.h" #include "common/goal_constants.h" -extern u32 FastLink; -extern s32 NumSymbols; -extern Ptr EnableMethodSet; -extern Ptr s7; -extern Ptr SymbolTable2; -extern Ptr LastSymbol; +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kscheme.h" -constexpr u32 EMPTY_HASH = 0x8454B6E6; -constexpr u32 OFFSET_MASK = 7; -constexpr u32 CRC_POLY = 0x04c11db7; -constexpr u32 SYM_TABLE_END = GOAL_MAX_SYMBOLS - 32; - -constexpr u32 DEFAULT_METHOD_COUNT = 12; -constexpr u32 FALLBACK_UNKNOWN_METHOD_COUNT = 44; - -struct String { - u32 len; - char* data() { return ((char*)this) + sizeof(String); } -}; +namespace jak1 { struct SymInfo { u32 hash; @@ -41,10 +17,12 @@ struct Symbol { }; inline Ptr info(Ptr s) { - return s.cast() + SYM_INFO_OFFSET; + return s.cast() + jak1::SYM_INFO_OFFSET; } -struct Function {}; +constexpr u32 DEFAULT_METHOD_COUNT = 12; +constexpr u32 FALLBACK_UNKNOWN_METHOD_COUNT = 44; +constexpr u32 SYM_TABLE_END = jak1::GOAL_MAX_SYMBOLS - 32; /*! * GOAL Type @@ -74,37 +52,25 @@ struct Type { } }; -constexpr uint32_t UNKNOWN_PP = UINT32_MAX; - -u32 crc32(const u8* data, s32 size); void kscheme_init_globals(); -void init_crc(); -u64 alloc_from_heap(u32 heapSymbol, u32 type, s32 size, u32 pp); -u64 alloc_heap_object(u32 heap, u32 type, u32 size, u32 pp); Ptr intern_from_c(const char* name); -Ptr intern_type_from_c(const char* name, u64 methods); -Ptr set_type_values(Ptr type, Ptr parent, u64 flags); -u64 print_object(u32 obj); -u64 print_pair(u32 obj); -u64 print_binteger(u64 obj); -u64 inspect_pair(u32 obj); -u64 inspect_binteger(u64 obj); -s32 InitHeapAndSymbol(); -u64 call_goal(Ptr f, u64 a, u64 b, u64 c, u64 st, void* offset); -u64 call_goal_on_stack(Ptr f, u64 rsp, u64 st, void* offset); -void print_symbol_table(); -u64 make_string_from_c(const char* c_str); -Ptr find_symbol_from_c(const char* name); -u64 call_method_of_type(u32 arg, Ptr type, u32 method_id); -u64 inspect_object(u32 obj); -u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr); -s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags); u64 load(u32 file_name_in, u32 heap_in); u64 loado(u32 file_name_in, u32 heap_in); u64 unload(u32 name); +s64 load_and_link(const char* filename, char* decode_name, kheapinfo* heap, u32 flags); +u64 make_string_from_c(const char* c_str); +u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr); +u64 inspect_object(u32 obj); +u64 print_object(u32 obj); +Ptr find_symbol_from_c(const char* name); +u64 call_method_of_type(u32 arg, Ptr type, u32 method_id); +Ptr intern_type_from_c(const char* name, u64 methods); +u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 a2); +u64 alloc_heap_object(u32 heap, u32 type, u32 size, u32 pp); Ptr make_function_symbol_from_c(const char* name, void* f); Ptr make_stack_arg_function_symbol_from_c(const char* name, void* f); +s32 InitHeapAndSymbol(); u64 call_goal_function_by_name(const char* name); -u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 a2); Ptr alloc_and_init_type(Ptr sym, u32 method_count); Ptr set_fixed_symbol(u32 offset, const char* name, u32 value); +} // namespace jak1 diff --git a/game/kernel/ksound.cpp b/game/kernel/jak1/ksound.cpp similarity index 50% rename from game/kernel/ksound.cpp rename to game/kernel/jak1/ksound.cpp index 4d2c4e0c11..433407244c 100644 --- a/game/kernel/ksound.cpp +++ b/game/kernel/jak1/ksound.cpp @@ -1,40 +1,10 @@ -/*! - * @file ksound.cpp - * There's not much here. My guess is this was set up as framework to match the kmachine.cpp format, - * but whoever did the sound didn't use this. - */ - #include "ksound.h" -#include "kdgo.h" -#include "kscheme.h" - -#include "common/common_types.h" - -#include "game/overlord/srpc.h" -#include "game/sound/989snd/ame_handler.h" - -/*! - * Does nothing! - */ -void InitSound() {} - -/*! - * Does nothing! - */ -void ShutdownSound() {} - -/*! - * PC port functions - */ -void set_flava_hack(u64 val) { - snd::SoundFlavaHack = val; -} - -void set_fade_hack(u64 val) { - gMusicFadeHack = val; -} +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/ksound.h" +#include "game/kernel/jak1/kscheme.h" +namespace jak1 { /*! * Set up some functions which are somewhat related to sound. */ @@ -48,3 +18,4 @@ void InitSoundScheme() { make_function_symbol_from_c("pc-sound-set-flava-hack", (void*)set_flava_hack); make_function_symbol_from_c("pc-sound-set-fade-hack", (void*)set_fade_hack); } +} // namespace jak1 \ No newline at end of file diff --git a/game/kernel/jak1/ksound.h b/game/kernel/jak1/ksound.h new file mode 100644 index 0000000000..dc12758a1e --- /dev/null +++ b/game/kernel/jak1/ksound.h @@ -0,0 +1,5 @@ +#pragma once + +namespace jak1 { +void InitSoundScheme(); +} \ No newline at end of file diff --git a/game/kernel/jak2/fileio.cpp b/game/kernel/jak2/fileio.cpp new file mode 100644 index 0000000000..13aea7328c --- /dev/null +++ b/game/kernel/jak2/fileio.cpp @@ -0,0 +1,206 @@ +#include "fileio.h" + +#include +#include + +#include "common/versions.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kmalloc.h" + +namespace jak2 { + +/*! + * Turn file name into file's path. + * DONE, EXACT + */ +char* DecodeFileName(const char* name) { + char* result; + // names starting with $ are special: + 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 { + // if no special prefix is given, assume $CODE + result = MakeFileName(CODE_FILE_TYPE, name, 0); + } + return result; +} + +/*! + * Build a file name based on type. + * @param type: the file type. + * @param name: the file name + * @param new_string: if true, allocate a new global string for file name. + * will otherwise use a static buffer. + * DONE, Had unused int, char*, and MakeFileNameInfo params. + * PC PORT NOTE : Changed some paths so that they work for us (namely, got rid of 'host') + */ +char* MakeFileName(int type, const char* name, int new_string) { + using namespace versions::jak2; + // 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); + + // build file name + if (type == LISTENER_TO_KERNEL_FILE_TYPE) { + kstrcpy(buf, + "kernel/LISTENERTOKERNEL"); // unused (I guess this is an old method to transfer data?) + } else if (type == KERNEL_TO_LISTENER_FILE_TYPE) { + kstrcpy(buf, + "kernel/KERNELTOLISTENER"); // unused (I guess this is an old method to transfer data?) + } else if (type == CODE_FILE_TYPE) { + sprintf(buf, "game/obj/%s.o", name); // game object file (CODE) + } else if (type == GAMEPAD_FILE_TYPE) { + sprintf(buffer_633, "pad:0"); // I guess the gamepad could be opened like a file at some point? + } else if (type == LISTENER_TO_KERNEL_LOCK_FILE_TYPE) { + kstrcpy(buf, "kernel/LISTENERTOKERNEL_LOCK"); // unused (likely used for LISTENERTOKERNEL?) + } else if (type == KERNEL_TO_LISTENER_LOCK_FILE_TYPE) { + kstrcpy(buf, "kernel/KERNELTOLISTENER_LOCK"); // unused (likley used for KERNELTOLISTENER?) + } else if (type == IOP_MODULE_FILE_TYPE) { // IOP module, overwrite the whole thing. + // this is unused, even by the remaining code to load IOP modules from the network. + // note this uses host0, which I believe is the PS2 TOOL's built in Linux SBC. + sprintf(buffer_633, "host0:/usr/local/sce/iop/modules/%s.irx", name); + } else if (type == DATA_FILE_TYPE) { + // GOAL object file, but containing data instead of code. + // likely packed by a tool that isn't the GOAL compiler. + // sprintf(buf, "%sfinal/%s.go", prefix, name); + sprintf(buf, "%sout/obj/%s.go", prefix, name); + } else if (type == TX_PAGE_FILE_TYPE) { + // Texture Page + // part of level files, so it has a version number. + // sprintf(buf, "%sdata/texture-page%d/%s.go", prefix, TX_PAGE_VERSION, name); + sprintf(buf, "%sout/obj/%s.go", prefix, name); + } else if (type == JA_FILE_TYPE) { + // Art JA (joint animation? no idea) + // part of level files, so it has a version number + sprintf(buf, "%sdb/artdata%d/%s-ja.go", prefix, ART_FILE_VERSION, name); + } else if (type == JG_FILE_TYPE) { + // Art JG (joint group? no idea) + // part of level files, so it has a version number + sprintf(buf, "%sdb/artdata%d/%s-jg.go", prefix, ART_FILE_VERSION, name); + } else if (type == MA_FILE_TYPE) { + // Art MA (??) + // part of level files, so it has a version number + sprintf(buf, "%sdb/artdata%d/%s-ma.go", prefix, ART_FILE_VERSION, name); + } else if (type == MG_FILE_TYPE) { + // Art MG (??) + // part of level files, so it has a version number + sprintf(buf, "%sdb/artdata%d/%s-mg.go", prefix, ART_FILE_VERSION, name); + } else if (type == TG_FILE_TYPE) { + // unused, DATA TG file + sprintf(buf, "%sdb/%s-tg.go", prefix, name); + } else if (type == LEVEL_FILE_TYPE) { + // Level main file. + // part of level files, so it has a version number (a high one, 30!) + sprintf(buf, "%sdb/level%d/%s-bt.go", prefix, LEVEL_FILE_VERSION, name); + // -ag missing in jak2???? + // } else if (type == ART_GROUP_FILE_TYPE) { + // // Level art group file. + // // part of level files, so it has a version number + // sprintf(buf, "%sdata/art-group%d/%s-ag.go", prefix, ART_FILE_VERSION, name); + } else if (type == VS_FILE_TYPE) { + // Level vs file, unused, unknown + // possibly early visibility file? + sprintf(buf, "%sfinal/level%d/%s-vs.go", prefix, LEVEL_FILE_VERSION, name); + } else if (type == TX_FILE_TYPE) { + // Resource? TX file? some sort of texture? + sprintf(buf, "%sfinal/res%d/%s-tx.go", prefix, 1, name); + } else if (type == VS_BIN_FILE_TYPE) { + // level VS bin + // perhaps another format of early visibility data + sprintf(buf, "%sfinal/level%d/%s-vs.bin", prefix, LEVEL_FILE_VERSION, name); + } else if (type == DGO_TXT_FILE_TYPE) { + // Text file in the DGO directory? + // Could have contained a list of files inside the DGO. + sprintf(buf, "%sfinal/dgo%d/%s.txt", prefix, DGO_FILE_VERSION, name); + } else if (type == LEVEL_WITH_EXTENSION_FILE_TYPE) { + // Level file, but with an extension already on it. + sprintf(buf, "%sfinal/level%d/%s", prefix, LEVEL_FILE_VERSION, name); + } else if (type == DATA_DGO_FILE_TYPE) { + // data DGO file (unused, all DGO/CGOs loaded through IOP) + sprintf(buf, "%sfinal/dgo%d/%s.dgo", prefix, DGO_FILE_VERSION, name); + } else if (type == GAME_DGO_FILE_TYPE) { + // game DGO file (unused, all DGO/CGOs loaded through IOP) + sprintf(buf, "game/dgo%d/%s.dgo", DGO_FILE_VERSION, name); + } else if (type == DATA_CGO_FILE_TYPE) { + // data CGO file (unused, all DGO/CGOs loaded through IOP) + sprintf(buf, "%sfinal/dgo%d/%s.cgo", prefix, DGO_FILE_VERSION, name); + } else if (type == GAME_CGO_FILE_TYPE) { + // 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?) + sprintf(buf, "%sfinal/res%d/game-cnt.go", prefix, 1); + } else if (type == RES_FILE_TYPE) { + // RES go file? + sprintf(buf, "%sfinal/res%d/%s.go", prefix, 1, name); + } else if (type == SND_BNK_FILE_TYPE) { + // sound bank + sprintf(buf, "%sfinal/sound%d/%s.bnk", prefix, 1, name); // v1 + } else if (type == MUSIC_BNK_FILE_TYPE) { + // sound bank + sprintf(buf, "%sfinal/music%d/%s.bnk", prefix, 1, name); // v1 + } else if (type == VAG_FILE_TYPE) { + // vagwad file (why the 2?) + sprintf(buf, "%sfinal/vagwad2/%s.%s", prefix, name, ""); // v1, memory bug here + } else if (type == MISC_FILE_TYPE) { + // sound bank + sprintf(buf, "%sfinal/misc/%s", prefix, name); + } else if (type == MAP_FILE_TYPE) { + // map + sprintf(buf, "%sfinal/map%d/%s", prefix, 1, name); // v1 + } else if (type == REFPLANT_FILE_TYPE) { + // REFPLANT? no idea. it's different in jak2. + sprintf(buf, "%sdb/refplant/%s", prefix, name); + } else if (type == ISO_FILE_TYPE) { + sprintf(buffer_633, "/out/iso/%s", name); + } else { + 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 jak2 diff --git a/game/kernel/jak2/fileio.h b/game/kernel/jak2/fileio.h new file mode 100644 index 0000000000..bf7101172f --- /dev/null +++ b/game/kernel/jak2/fileio.h @@ -0,0 +1,5 @@ +#pragma once + +namespace jak2 { +char* MakeFileName(int type, const char* name, int new_string); +} \ No newline at end of file diff --git a/game/kernel/jak2/kboot.cpp b/game/kernel/jak2/kboot.cpp new file mode 100644 index 0000000000..149632615f --- /dev/null +++ b/game/kernel/jak2/kboot.cpp @@ -0,0 +1,153 @@ +#include "kboot.h" + +#include +#include +#include + +#include "common/goal_constants.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/klisten.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/common/ksocket.h" +#include "game/kernel/jak2/klisten.h" +#include "game/kernel/jak2/kmachine.h" +#include "game/sce/libscf.h" + +namespace jak2 { + +char DebugBootUser[64]; +char DebugBootArtGroup[64]; + +void kboot_init_globals() { + memset(DebugBootUser, 0, sizeof(DebugBootUser)); + memset(DebugBootArtGroup, 0, sizeof(DebugBootArtGroup)); + strcpy(DebugBootUser, "unknown"); +} + +void KernelCheckAndDispatch(); + +s32 goal_main(int argc, const char* const* argv) { + // only in PC port + InitParms(argc, argv); + + init_crc(); + 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 = (u16)Language::Japanese; + break; + case SCE_FRENCH_LANGUAGE: + masterConfig.language = (u16)Language::French; + break; + case SCE_SPANISH_LANGUAGE: + masterConfig.language = (u16)Language::Spanish; + break; + case SCE_GERMAN_LANGUAGE: + masterConfig.language = (u16)Language::German; + break; + case SCE_ITALIAN_LANGUAGE: + masterConfig.language = (u16)Language::Italian; + 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 jak2 diff --git a/game/kernel/jak2/kboot.h b/game/kernel/jak2/kboot.h new file mode 100644 index 0000000000..4d00e59214 --- /dev/null +++ b/game/kernel/jak2/kboot.h @@ -0,0 +1,14 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/kernel/common/kboot.h" + +namespace jak2 { +extern char DebugBootUser[64]; +extern char DebugBootArtGroup[64]; +s32 goal_main(int argc, const char* const* argv); +void kboot_init_globals(); +void KernelDispatch(u32 dispatcher_func); +void KernelShutdown(u32 reason); +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kdgo.cpp b/game/kernel/jak2/kdgo.cpp new file mode 100644 index 0000000000..246186b37a --- /dev/null +++ b/game/kernel/jak2/kdgo.cpp @@ -0,0 +1,94 @@ +#include "kdgo.h" + +#include "common/log/log.h" + +#include "game/kernel/common/Ptr.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/jak2/klink.h" + +namespace jak2 { + +/*! + * 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) { + 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); + link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now! + + // inform IOP we are done + if (!lastObjectLoaded) { + ContinueLoadingDGO(Ptr((heap->current + 0x3f).offset & 0xffffffc0)); + } + } + sShowStallMsg = oldShowStall; +} + +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kdgo.h b/game/kernel/jak2/kdgo.h new file mode 100644 index 0000000000..912a901868 --- /dev/null +++ b/game/kernel/jak2/kdgo.h @@ -0,0 +1,13 @@ +#pragma once +#include "common/common_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, + 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); +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/klink.cpp b/game/kernel/jak2/klink.cpp new file mode 100644 index 0000000000..f32f483f46 --- /dev/null +++ b/game/kernel/jak2/klink.cpp @@ -0,0 +1,676 @@ +#include "klink.h" + +#include "common/goal_constants.h" +#include "common/log/log.h" +#include "common/symbols.h" + +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/kmachine.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/common/memory_layout.h" +#include "game/kernel/jak2/kscheme.h" + +#include "third-party/fmt/core.h" +static constexpr bool link_debug_printfs = false; + +/*! + * Make progress on linking. + */ +uint32_t link_control::jak2_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 + *((m_link_block_ptr - 4).cast()) = + *((s7 + jak2_symbols::FIX_SYM_LINK_BLOCK - 1).cast()); + + uint32_t rv; + + if (m_version == 3) { + ASSERT(m_opengoal); + rv = jak2_work_v3(); + } else if (m_version == 2 || m_version == 4) { + ASSERT(!m_opengoal); + rv = jak2_work_v2(); + } else { + ASSERT_MSG(false, fmt::format("UNHANDLED OBJECT FILE VERSION {} IN WORK!", m_version)); + return 0; + } + + DebugSegment = old_debug_segment; + return rv; +} + +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 = jak2::intern_type_from_c(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 = jak2::intern_from_c(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 { + // otherwise store the offset to st. Eventually this should become an s16 instead. + *(data + offset).cast() = sym_offset; + } + } + + return seek; +} +} // namespace + +/*! + * Run the linker. For now, all linking is done in two runs. If this turns out to be too slow, + * this should be modified to do incremental linking over multiple runs. + */ +uint32_t link_control::jak2_work_v3() { + 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; + } + jak2::ultimate_memcpy(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; + } + jak2::ultimate_memcpy(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; + } + jak2::ultimate_memcpy(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; + } +} + +#define LINK_V2_STATE_INIT_COPY 0 +#define LINK_V2_STATE_OFFSETS 1 +#define LINK_V2_STATE_SYMBOL_TABLE 2 +#define OBJ_V2_CLOSE_ENOUGH 0x90 +#define OBJ_V2_MAX_TRANSFER 0x80000 + +uint32_t link_control::jak2_work_v2() { + // u32 startCycle = kernel.read_clock(); todo + + if (m_state == LINK_V2_STATE_INIT_COPY) { // initialization and copying to heap + // we move the data segment to eliminate gaps + // very small gaps can be tolerated, as it is not worth the time penalty to move large objects + // many bytes. if this requires copying a large amount of data, we will do it in smaller chunks, + // allowing the copy to be spread over multiple game frames + + // state initialization + if (m_segment_process == 0) { + m_heap_gap = + m_object_data - m_heap->current; // distance between end of heap and start of object + } + + if (m_heap_gap < + OBJ_V2_CLOSE_ENOUGH) { // close enough, don't relocate the object, just expand the heap + if (link_debug_printfs) { + printf("[work_v2] close enough, not moving\n"); + } + m_heap->current = m_object_data + m_code_size; + if (m_heap->top.offset <= m_heap->current.offset) { + MsgErr("dkernel: heap overflow\n"); // game has ~% instead of \n :P + return 1; + } + } else { // not close enough, need to move the object + + // on the first run of this state... + if (m_segment_process == 0) { + m_original_object_location = m_object_data; + // allocate on heap, will have no gap + m_object_data = kmalloc(m_heap, m_code_size, 0, "data-segment"); + if (link_debug_printfs) { + printf("[work_v2] moving from 0x%x to 0x%x\n", m_original_object_location.offset, + m_object_data.offset); + } + if (!m_object_data.offset) { + MsgErr("dkernel: unable to malloc %d bytes for data-segment\n", m_code_size); + return 1; + } + } + + // the actual copy + Ptr source = m_original_object_location + m_segment_process; + u32 size = m_code_size - m_segment_process; + + if (size > OBJ_V2_MAX_TRANSFER) { // around .5 MB + jak2::ultimate_memcpy((m_object_data + m_segment_process).c(), source.c(), + OBJ_V2_MAX_TRANSFER); + m_segment_process += OBJ_V2_MAX_TRANSFER; + return 0; // return, don't want to take too long. + } + + // if we have bytes to copy, but they are less than the max transfer, do it in one shot! + if (size) { + jak2::ultimate_memcpy((m_object_data + m_segment_process).c(), source.c(), size); + if (m_segment_process > 0) { // if we did a previous copy, we return now.... + m_state = LINK_V2_STATE_OFFSETS; + m_segment_process = 0; + return 0; + } + } + } + + // otherwise go straight into the next state. + m_state = LINK_V2_STATE_OFFSETS; + m_segment_process = 0; + } + + // init offset phase + if (m_state == LINK_V2_STATE_OFFSETS && m_segment_process == 0) { + m_reloc_ptr = m_link_block_ptr + 8; // seek to link table + if (*m_reloc_ptr == 0) { // do we have pointer links to do? + m_reloc_ptr.offset++; // if not, seek past the \0, and go to next state + m_state = LINK_V2_STATE_SYMBOL_TABLE; + m_segment_process = 0; + } else { + m_base_ptr = m_object_data; // base address for offsetting. + m_loc_ptr = m_object_data; // pointer which seeks thru the code + m_table_toggle = 0; // are we seeking or fixing? + m_segment_process = 1; // we've done first time setup + } + } + + if (m_state == LINK_V2_STATE_OFFSETS) { // pointer fixup + // this state reads through a table. Values alternate between "seek amount" and "number of + // consecutive 4-byte + // words to fix up". The counts are encoded using a variable length encoding scheme. They use + // a very stupid + // method of encoding values which requires O(n) bytes to store the value n. + + // to avoid dropping a frame, we check every 0x400 relocations to see if 0.5 milliseconds have + // elapsed. + u32 relocCounter = 0x400; + while (true) { // loop over entire table + while (true) { // loop over current mode + + // read and seek table + u8 count = *m_reloc_ptr; + m_reloc_ptr.offset++; + + if (!m_table_toggle) { // seek mode + m_loc_ptr.offset += + 4 * + count; // perform seek (MIPS instructions are 4 bytes, so we >> 2 the seek amount) + } else { // offset mode + for (u32 i = 0; i < count; i++) { + if (m_loc_ptr.offset % 4) { + ASSERT(false); + } + u32 code = *(m_loc_ptr.cast()); + code += m_base_ptr.offset; + *(m_loc_ptr.cast()) = code; + m_loc_ptr.offset += 4; + } + } + + if (count != 0xff) { + break; + } + + if (*m_reloc_ptr == 0) { + m_reloc_ptr.offset++; + m_table_toggle = m_table_toggle ^ 1; + } + } + + // reached the end of the tableToggle mode + m_table_toggle = m_table_toggle ^ 1; + if (*m_reloc_ptr == 0) { + break; // end of the state + } + relocCounter--; + if (relocCounter == 0) { + // u32 clock_value = kernel.read_clock(); + // if(clock_value - startCycle > 150000) { // 0.5 milliseconds + // return 0; + // } + relocCounter = 0x400; + } + } + m_reloc_ptr.offset++; + m_state = 2; + m_segment_process = 0; + } + + if (m_state == 2) { // GOAL object fixup + if (*m_reloc_ptr == 0) { + m_state = 3; + m_segment_process = 0; + } else { + while (true) { + u32 relocation = *m_reloc_ptr; + m_reloc_ptr.offset++; + Ptr goalObj; + char* name; + if ((relocation & 0x80) == 0) { + // symbol! + if (relocation > 9) { + m_reloc_ptr.offset--; // no idea what this is. + } + name = m_reloc_ptr.cast().c(); + if (link_debug_printfs) { + printf("[work_v2] symlink: %s\n", name); + } + goalObj = jak2::intern_from_c(name).cast(); + } else { + // type! + u8 nMethods = relocation & 0x7f; + if (nMethods == 0) { + nMethods = 1; + } + name = m_reloc_ptr.cast().c(); + if (link_debug_printfs) { + printf("[work_v2] symlink -type: %s\n", name); + } + goalObj = jak2::intern_type_from_c(name, nMethods).cast(); + } + m_reloc_ptr.offset += strlen(name) + 1; + // DECOMPILER->hookStartSymlinkV3(_state - 1, _objectData, std::string(name)); + m_reloc_ptr = c_symlink2(m_object_data, goalObj, m_reloc_ptr); + // DECOMPILER->hookFinishSymlinkV3(); + if (*m_reloc_ptr == 0) { + break; // done + } + // u32 currentCycle = kernel.read_clock(); + // if(currentCycle - startCycle > 150000) { + // return 0; + // } + } + m_state = 3; + m_segment_process = 0; + } + } + m_entry = m_object_data + 4; + return 1; +} + +/*! + * Complete linking. This will execute the top-level code for v3 object files, if requested. + */ +void link_control::jak2_finish(bool jump_from_c_to_goal) { + CacheFlush(m_code_start.c(), 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 + jak2_symbols::FIX_SYM_TRUE; + } + if (m_flags & LINK_FLAG_FORCE_FAST_LINK) { + FastLink = 1; + } + *EnableMethodSet = *EnableMethodSet + m_keep_debug; + + ObjectFileHeader* ofh = m_link_block_ptr.cast().c(); + lg::info("link finish: {}", m_object_name); + if (ofh->object_file_version == 3) { + // todo check function type of entry + + // setup mips2c functions + /* + for (auto& x : Mips2C::gMips2CLinkCallbacks[m_object_name]) { + 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 { + if (m_flags & LINK_FLAG_EXECUTE) { + auto entry = m_entry; + auto name = basename_goal(m_object_name); + strcpy(Ptr(LINK_CONTROL_NAME_ADDR).c(), name); + jak2::call_method_of_type_arg2(entry.offset, Ptr(*((entry - 4).cast())), + GOAL_RELOC_METHOD, m_heap.offset, + Ptr(LINK_CONTROL_NAME_ADDR).offset); + } + } + + *EnableMethodSet = *EnableMethodSet - m_keep_debug; + FastLink = 0; // nested fast links won't work right. + m_heap->top = m_heap_top; + DebugSegment = old_debug_segment; + m_busy = false; +} + +namespace jak2 { +u32 link_busy() { + return saved_link_control.m_busy; +} + +void link_reset() { + // seems like a bad idea to do this - you'll probably leak memory. + saved_link_control.m_busy = 0; +} + +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.begin(data, name, size, heap, flags); + uint32_t done; + do { + done = lc.jak2_work(); + } while (!done); + lc.jak2_finish(jump_from_c_to_goal); + return lc.m_entry; +} + +/*! + * Wrapper so this can be called from GOAL. Not in original game. + */ +u64 link_and_exec_wrapper(u64* args) { + // data, name, size, heap, flags + return link_and_exec(Ptr(args[0]), Ptr(args[1]).c(), args[2], Ptr(args[3]), + args[4], false) + .offset; +} + +/*! + * GOAL exported function for beginning a link with the saved_link_control + * 47 -> output_load, output_true, execute, 8, force fast + * 39 -> no 8 (s7) + */ +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]); + auto work_result = saved_link_control.jak2_work(); + // if we managed to finish in one shot, take care of calling finish + if (work_result) { + // called from goal + saved_link_control.jak2_finish(false); + } + + return work_result != 0; +} + +/*! + * GOAL exported function for doing a small amount of linking work on the saved_link_control + */ +uint64_t link_resume() { + auto work_result = saved_link_control.jak2_work(); + if (work_result) { + // called from goal + saved_link_control.jak2_finish(false); + } + return work_result != 0; +} + +/*! + * 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 = jak2::find_symbol_from_c("ultimate-memcpy"); + if (sym->value() == 0) { + memmove(dst, src, size); + return; + } + gfunc_774.offset = sym->value(); + } + + 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 jak2 \ No newline at end of file diff --git a/game/kernel/jak2/klink.h b/game/kernel/jak2/klink.h new file mode 100644 index 0000000000..5417d9cead --- /dev/null +++ b/game/kernel/jak2/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 jak2 { +void ultimate_memcpy(void* dst, void* src, uint32_t size); +Ptr link_and_exec(Ptr data, + const char* name, + int32_t size, + Ptr heap, + uint32_t flags, + bool jump_from_c_to_goal); +u32 link_busy(); +void link_reset(); +uint64_t link_begin(u64* args); +uint64_t link_resume(); +} // namespace jak2 diff --git a/game/kernel/jak2/klisten.cpp b/game/kernel/jak2/klisten.cpp new file mode 100644 index 0000000000..ea0c534881 --- /dev/null +++ b/game/kernel/jak2/klisten.cpp @@ -0,0 +1,154 @@ +#include "klisten.h" + +#include +#include + +#include "common/symbols.h" + +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/jak2/kboot.h" +#include "game/kernel/jak2/klink.h" +#include "game/kernel/jak2/kprint.h" +#include "game/kernel/jak2/kscheme.h" + +namespace jak2 { +using namespace jak2_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("*listener-link-block*"); + ListenerFunction = intern_from_c("*listener-function*"); + KernelFunction = intern_from_c("*kernel-function*"); + kernel_dispatcher = intern_from_c("kernel-dispatcher"); + sync_dispatcher = intern_from_c("sync-dispatcher"); + kernel_packages = intern_from_c("*kernel-packages*"); + print_column = intern_from_c("*print-column*").cast(); + 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_INSEPCT: + 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) { + if (!MasterDebug) { + // not debugging, no sql results. + return s7.offset + FIX_SYM_EMPTY_PAIR - 1; + } else { + // clear old result + SqlResult->value() = s7.offset; + // output sql query to the compiler + output_sql_query(string_in->data()); + // clear pending listener stuff, so we don't run it again. + ListenerFunction->value() = s7.offset; + ListenerStatus = 1; + ClearPending(); + SendAck(); + + kdebugheap->top.offset -= 0x4000; // not sure what it's used for... + + // didn't we just set these to false? + if (ListenerFunction->value() == s7.offset && SqlResult->value() == s7.offset) { + do { + KernelDispatch(sync_dispatcher->value()); + SendAck(); + if (SqlResult->value() != s7.offset) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // ? + /* + iVar2 = 99999; + do { + bVar1 = iVar2 != 0; + iVar2 = iVar2 + -1; + } while (bVar1); + */ + } while (SqlResult->value() == s7.offset); + } + kdebugheap->top.offset += 0x4000; + return SqlResult->value(); + } +} + +} // namespace jak2 diff --git a/game/kernel/jak2/klisten.h b/game/kernel/jak2/klisten.h new file mode 100644 index 0000000000..55b737e832 --- /dev/null +++ b/game/kernel/jak2/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 jak2 { +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 klisten_init_globals(); +void ProcessListenerMessage(Ptr msg); +int sql_query_sync(Ptr string_in); +void InitListener(); +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kmachine.cpp b/game/kernel/jak2/kmachine.cpp new file mode 100644 index 0000000000..5e235c9614 --- /dev/null +++ b/game/kernel/jak2/kmachine.cpp @@ -0,0 +1,624 @@ +#include "kmachine.h" + +#include +#include + +#include "common/log/log.h" +#include "common/symbols.h" +#include "common/util/FileUtil.h" + +#include "game/kernel/common/Symbol4.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kboot.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/kscheme.h" +#include "game/kernel/common/ksocket.h" +#include "game/kernel/common/ksound.h" +#include "game/kernel/common/memory_layout.h" +#include "game/kernel/jak2/kboot.h" +#include "game/kernel/jak2/kdgo.h" +#include "game/kernel/jak2/klink.h" +#include "game/kernel/jak2/klisten.h" +#include "game/kernel/jak2/kmalloc.h" +#include "game/kernel/jak2/kscheme.h" +#include "game/kernel/jak2/ksound.h" +#include "game/kernel/svnrev.h" +#include "game/sce/libdma.h" +#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; + +namespace jak2 { +/*! + * 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 = 0; + DebugSegment = 0; + MasterDebug = 0; + } + + 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 = 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 = 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 = 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 = 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; + } + + // 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()); + } + + // new for jak 2 + if (arg == "-user") { + i++; + std::string levelName = argv[i]; + Msg(6, "dkernel: user %s\n", levelName.c_str()); + kstrcpy(DebugBootUser, levelName.c_str()); + } + + // new for jak 2 + if (arg == "-art") { + i++; + std::string levelName = argv[i]; + Msg(6, "dkernel: art-group %s\n", levelName.c_str()); + kstrcpy(DebugBootArtGroup, levelName.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; + } + } +} + +void InitIOP() { + Msg(6, "dkernel: boot:%d debug:%d mem:%d dev:%d mod:%d\n", DiskBoot, MasterDebug, DebugSegment, + isodrv, modsrc); + sceSifInitRpc(0); + + // init cd if we need it + if (((isodrv == iso_cd) || (modsrc == 1)) || (reboot == 1)) { + InitCD(); + } + + if (reboot == 0) { + // iop with dev kernel + printf("Rebooting IOP...\n"); + while (!sceSifRebootIop("host0:/usr/local/sce/iop/modules/ioprp271.img")) { + printf("Failed, retrying...\n"); + } + while (!sceSifSyncIop()) { + } + } else { + printf("Rebooting IOP...\n"); + while (!sceSifRebootIop("cdrom0:\\DRIVERS\\IOPRP271.IMG")) { + printf("Failed, retrying...\n"); + } + while (!sceSifSyncIop()) { + } + } + sceSifInitRpc(0); + if ((isodrv == iso_cd) || (modsrc == 1)) { + InitCD(); + sceFsReset(); + } + + 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) { + if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/sio2man.irx", 0, nullptr) < 0) { + MsgErr("loading sio2man.irx failed\n"); + exit(0); + } + + if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/padman.irx", 0, nullptr) < 0) { + MsgErr("loading padman.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/libsd.irx", 0, nullptr) < 0) { + MsgErr("loading libsd.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/mcman.irx", 0, nullptr) < 0) { + MsgErr("loading mcman.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/mcserv.irx", 0, nullptr) < 0) { + MsgErr("loading mcserv.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:/usr/home/src/989snd23/iop/sce27/989nostr.irx", 0, nullptr) < 0) { + MsgErr("loading %s failed\n", "host0:/usr/home/src/989snd23/iop/sce27/989nostr.irx"); + exit(0); + } + if (DebugSegment) { + sceSifLoadModule("host0:/usr/home/src/989snd23/iop/sce27/989err.irx", 0, nullptr); + } + if (sceSifLoadModule("host0:/usr/local/sce/iop/modules/scrtchpd.irx", 0, nullptr) < 0) { + MsgErr("loading scrtchpd.irx failed\n"); + exit(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 { + if (modsrc == 1) { + if (sceSifLoadModule("cdrom0:\\DRIVERS\\SIO2MAN.IRX;1", 0, nullptr) < 0) { + MsgErr("loading SIO2MAN.IRX failed\n"); + exit(0); + } + if (sceSifLoadModule("cdrom0:\\DRIVERS\\PADMAN.IRX;1", 0, nullptr) < 0) { + MsgErr("loading PADMAN.IRX failed\n"); + exit(0); + } + if (sceSifLoadModule("cdrom0:\\DRIVERS\\LIBSD.IRX;1", 0, nullptr) < 0) { + MsgErr("loading LIBSD.IRX failed\n"); + exit(0); + } + if (sceSifLoadModule("cdrom0:\\DRIVERS\\MCMAN.IRX;1", 0, nullptr) < 0) { + MsgErr("loading MCMAN.IRX failed\n"); + exit(0); + } + + if (sceSifLoadModule("cdrom0:\\DRIVERS\\MCSERV.IRX;1", 0, nullptr) < 0) { + MsgErr("loading MCSERV.IRX failed\n"); + exit(0); + } + if (sceSifLoadModule("cdrom0:\\DRIVERS\\989NOSTR.IRX;1", 0, nullptr) < 0) { + MsgErr("loading 989SND.IRX failed\n"); + exit(0); + } + if (sceSifLoadModule("cdrom0:\\DRIVERS\\SCRTCHPD.IRX;1", 0, nullptr) < 0) { + MsgErr("loading SCRTCHPD.IRX failed\n"); + exit(0); + } + printf("Initializing CD library in ISO_CD mode\n"); + auto rv = + sceSifLoadModule("cdrom0:\\DRIVERS\\OVERLORD.IRX;1", total_len, overlord_boot_command); + if (rv < 0) { + MsgErr("loading OVERLORD.IRX failed %d\n", rv); + exit(0); + } + } else { + if (modsrc == 2) { + if (sceSifLoadModule("host0:isoimage/DRIVERS/SIO2MAN.IRX", 0, nullptr) < 0) { + MsgErr("loading sio2man.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:isoimage/DRIVERS/PADMAN.IRX", 0, nullptr) < 0) { + MsgErr("loading padman.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:isoimage/DRIVERS/LIBSD.IRX", 0, nullptr) < 0) { + MsgErr("loading libsd.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:isoimage/DRIVERS/MCMAN.IRX", 0, nullptr) < 0) { + MsgErr("loading mcman.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host0:isoimage/DRIVERS/MCSERV.IRX", 0, nullptr) < 0) { + MsgErr("loading mcserv.irx failed\n"); + exit(0); + } + if (sceSifLoadModule("host:isoimage/DRIVERS/989NOSTR.IRX", 1, "do_rpc=0") < 0) { + MsgErr("loading 989snd.irx failed\n"); + exit(0); + } + sceSifLoadModule("host0:/usr/home/src/989snd23/iop/sce27/989err.irx", 0, nullptr); + + if (sceSifLoadModule("host0:isoimage/DRIVERS/SCRTCHPD.IRX;1", 0, nullptr) < 0) { + MsgErr("loading scrtchpd.irx failed\n"); + exit(0); + } + printf("Initializing CD library in DEVISO mode\n"); + + auto rv = sceSifLoadModule("host0:isoimage/DRIVERS/OVERLORD.IRX", total_len, + overlord_boot_command); + if (rv < 0) { + MsgErr("loading overlord.irx <2> failed\n"); + exit(0); + } + } + } + } + int rv = sceMcInit(); + if (rv < 0) { + MsgErr("MC driver init failed %d\n", rv); + exit(0); + } + printf("InitIOP OK\n"); +} + +int InitMachine() { + // heap_start = malloc(0x10); + // set up global heap (modified, the default size in the release game is 32 MB in all cases) + 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), jak2::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(); + // } + + 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; + } else { + return status; + } +} + +/*! + * Shutdown the runtime. + */ +int ShutdownMachine() { + Msg(6, "kernel: machine shutdown (reason %d)\n", MasterExit); + ASSERT(false); + /* + StopIOP(); + ShutdownSound(); + CloseListener(); + */ + ShutdownGoalProto(); + + // OpenGOAL only - kill ps2 VM + if (VM::use) { + VM::vm_kill(); + } + return 0; +} + +Ptr MouseGetData(Ptr mouse) { + // stubbed out in the actual game + return mouse; +} + +/*! + * 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(Ptr>(mode)->name_cstr(), "read")) { + // 0x1 + file_stream->file = sceOpen(buffer, SCE_RDONLY); + } else if (!strcmp(Ptr>(mode)->name_cstr(), "append")) { + // new in jak 2! + // 0x202 + file_stream->file = sceOpen(buffer, SCE_CREAT | SCE_WRONLY); + } else { + // 0x602 + file_stream->file = sceOpen(buffer, SCE_TRUNC | SCE_CREAT | SCE_WRONLY); + } + + return fs; +} + +void InitMachine_PCPort() { + // PC Port added functions + + make_function_symbol_from_c("__read-ee-timer", (void*)read_ee_timer); + make_function_symbol_from_c("__mem-move", (void*)c_memmove); + // make_function_symbol_from_c("__send-gfx-dma-chain", (void*)send_gfx_dma_chain); + // make_function_symbol_from_c("__pc-texture-upload-now", (void*)pc_texture_upload_now); + // make_function_symbol_from_c("__pc-texture-relocate", (void*)pc_texture_relocate); + // make_function_symbol_from_c("__pc-get-mips2c", (void*)pc_get_mips2c); + // make_function_symbol_from_c("__pc-set-levels", (void*)pc_set_levels); + + // pad stuff + make_function_symbol_from_c("pc-pad-get-mapped-button", (void*)Gfx::get_mapped_button); + make_function_symbol_from_c("pc-pad-input-map-save!", (void*)Gfx::input_mode_save); + make_function_symbol_from_c("pc-pad-input-mode-set", (void*)Gfx::input_mode_set); + make_function_symbol_from_c("pc-pad-input-pad-set", (void*)Pad::input_mode_pad_set); + make_function_symbol_from_c("pc-pad-input-mode-get", (void*)Pad::input_mode_get); + make_function_symbol_from_c("pc-pad-input-key-get", (void*)Pad::input_mode_get_key); + make_function_symbol_from_c("pc-pad-input-index-get", (void*)Pad::input_mode_get_index); + + // os stuff + // make_function_symbol_from_c("pc-get-os", (void*)get_os); + make_function_symbol_from_c("pc-get-window-size", (void*)get_window_size); + make_function_symbol_from_c("pc-get-window-scale", (void*)get_window_scale); + // make_function_symbol_from_c("pc-get-fullscreen", (void*)get_fullscreen); + make_function_symbol_from_c("pc-get-screen-size", (void*)get_screen_size); + make_function_symbol_from_c("pc-get-screen-rate", (void*)get_screen_rate); + make_function_symbol_from_c("pc-get-screen-vmode-count", (void*)get_screen_vmode_count); + make_function_symbol_from_c("pc-set-window-size", (void*)Gfx::set_window_size); + // make_function_symbol_from_c("pc-set-fullscreen", (void*)set_fullscreen); + make_function_symbol_from_c("pc-set-frame-rate", (void*)set_frame_rate); + make_function_symbol_from_c("pc-set-vsync", (void*)set_vsync); + make_function_symbol_from_c("pc-set-window-lock", (void*)set_window_lock); + + // graphics things + make_function_symbol_from_c("pc-set-letterbox", (void*)Gfx::set_letterbox); + make_function_symbol_from_c("pc-renderer-tree-set-lod", (void*)Gfx::SetLod); + make_function_symbol_from_c("pc-set-collision-mode", (void*)Gfx::CollisionRendererSetMode); + make_function_symbol_from_c("pc-set-collision-mask", (void*)set_collision_mask); + make_function_symbol_from_c("pc-get-collision-mask", (void*)get_collision_mask); + make_function_symbol_from_c("pc-set-collision-wireframe", (void*)set_collision_wireframe); + make_function_symbol_from_c("pc-set-collision", (void*)set_collision); + + // file related functions + make_function_symbol_from_c("pc-filepath-exists?", (void*)filepath_exists); + make_function_symbol_from_c("pc-mkdir-file-path", (void*)mkdir_path); + + // discord rich presence + // make_function_symbol_from_c("pc-discord-rpc-set", (void*)set_discord_rpc); + // make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc); + + // profiler + make_function_symbol_from_c("pc-prof", (void*)prof_event); + + // init ps2 VM + if (VM::use) { + make_function_symbol_from_c("vm-ptr", (void*)VM::get_vm_ptr); + VM::vm_init(); + } + + // setup string constants + auto user_dir_path = file_util::get_user_game_dir(); + intern_from_c("*pc-user-dir-base-path*")->value() = + make_string_from_c(user_dir_path.string().c_str()); + // TODO - we will eventually need a better way to know what game we are playing + auto settings_path = file_util::get_user_settings_dir(); + intern_from_c("*pc-settings-folder*")->value() = + make_string_from_c(settings_path.string().c_str()); + intern_from_c("*pc-settings-built-sha*")->value() = make_string_from_c(GIT_VERSION); +} + +void PutDisplayEnv(u32 /*ptr*/) { + ASSERT(false); +} + +u32 sceGsSyncV(u32 mode) { + // stub, jak2 probably works differently here + ASSERT(false); + return 0; + /* + ASSERT(mode == 0); + VBlank_Handler(); + return Gfx::vsync(); + */ +} + +u32 sceGsSyncPath(u32 mode, u32 timeout) { + // stub, jak2 probably works differently here + ASSERT(mode == 0 && timeout == 0); + ASSERT(false); + return 0; + // return Gfx::sync_path(); +} + +void aybabtu() {} + +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*)ee::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("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 function + + InitMachine_PCPort(); + + InitSoundScheme(); + intern_from_c("*stack-top*")->value() = 0x7f00000; + intern_from_c("*stack-base*")->value() = 0x7ffffff; + intern_from_c("*stack-size*")->value() = 0x100000; + intern_from_c("*kernel-boot-message*")->value() = intern_from_c(DebugBootMessage).offset; + intern_from_c("*user*")->value() = make_string_from_c(DebugBootUser); + if (DiskBoot) { + intern_from_c("*kernel-boot-mode*")->value() = intern_from_c("boot").offset; + } + intern_from_c("*kernel-boot-level*")->value() = intern_from_c(DebugBootLevel).offset; + intern_from_c("*kernel-boot-art-group*")->value() = make_string_from_c(DebugBootArtGroup); + if (DiskBoot) { + *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 jak2_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).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 jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kmachine.h b/game/kernel/jak2/kmachine.h new file mode 100644 index 0000000000..dd9c1b2d32 --- /dev/null +++ b/game/kernel/jak2/kmachine.h @@ -0,0 +1,11 @@ +#pragma once + +namespace jak2 { +void InitParms(int argc, const char* const* argv); +void InitIOP(); +int InitMachine(); +int ShutdownMachine(); +void InitMachineScheme(); + +struct MouseInfo {}; +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kmalloc.cpp b/game/kernel/jak2/kmalloc.cpp new file mode 100644 index 0000000000..6f00253b8b --- /dev/null +++ b/game/kernel/jak2/kmalloc.cpp @@ -0,0 +1,24 @@ +#include "kmalloc.h" + +/* + * Jak 2 adds a few "memopen" functions. I think these were used to categorize memory allocations. + * GOAL and C++ code will call "memopen" with an appropriate category name before making + * allocations. + * + * These functions do nothing in the release version (and all known versions), but they might be + * interested for us to track memory usage. + */ + +namespace jak2 { +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 jak2 diff --git a/game/kernel/jak2/kmalloc.h b/game/kernel/jak2/kmalloc.h new file mode 100644 index 0000000000..97fd850bb1 --- /dev/null +++ b/game/kernel/jak2/kmalloc.h @@ -0,0 +1,9 @@ +#pragma once + +#include "game/kernel/common/kmalloc.h" + +namespace jak2 { +void kmemopen_from_c(Ptr heap, const char* name); +void kmemopen(u32 heap, u32 name); +void kmemclose(); +} // namespace jak2 diff --git a/game/kernel/jak2/kprint.cpp b/game/kernel/jak2/kprint.cpp new file mode 100644 index 0000000000..1c442f8c19 --- /dev/null +++ b/game/kernel/jak2/kprint.cpp @@ -0,0 +1,565 @@ +#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/jak2/kscheme.h" +#include "game/sce/sif_ee.h" + +namespace jak2 { + +void output_sql_query(char* query_name) { + if (MasterDebug != 0) { + sprintf(strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)), "sql-query \""); + + char* buffer_ptr = strend(OutputBufArea.cast().c() + sizeof(ListenerMessageHeader)); + char query_char = *query_name; + char query_char_to_add = *query_name; + while (query_char != 0) { + if (query_char_to_add == '\"') { + *buffer_ptr = '\\'; + buffer_ptr[1] = *query_name; + buffer_ptr += 2; + } else { + *buffer_ptr = query_char_to_add; + buffer_ptr++; + } + query_name = query_name + 1; + query_char = *query_name; + query_char_to_add = *query_name; + } + *buffer_ptr = '\"'; + buffer_ptr[1] = '\n'; + buffer_ptr[2] = 0; + OutputPending = OutputBufArea + sizeof(ListenerMessageHeader); + } +} +} // namespace jak2 + +/*! + * 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, with the obvious fixes made, but + * it's probably worth another pass. + */ +s32 format_impl_jak2(uint64_t* args) { + using namespace jak2_symbols; + using namespace jak2; + + // 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) >> 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 10 + 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 '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++]; + jak2::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 { + jak2::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]; + u32 in = arg_regs[arg_reg_idx++]; + if (arg0 == -1) { + jak2::print_object(in); + } else { + auto sym = jak2::find_symbol_from_c(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]; + u32 in = arg_regs[arg_reg_idx++]; + if (arg0 == -1) { + inspect_object(in); + } else { + auto sym = find_symbol_from_c(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]); + ASSERT(false); + break; + } + format_ptr++; + } else { + // got normal char, just copy it + *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) { + // do nothing, we're done + 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/jak2/kprint.h b/game/kernel/jak2/kprint.h new file mode 100644 index 0000000000..d07a17cc68 --- /dev/null +++ b/game/kernel/jak2/kprint.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak2 { +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_jak2(uint64_t* args); +} \ No newline at end of file diff --git a/game/kernel/jak2/kscheme.cpp b/game/kernel/jak2/kscheme.cpp new file mode 100644 index 0000000000..83d5547e94 --- /dev/null +++ b/game/kernel/jak2/kscheme.cpp @@ -0,0 +1,1646 @@ + +#include "kscheme.h" + +#include +#include + +#include "common/common_types.h" +#include "common/goal_constants.h" +#include "common/log/log.h" +#include "common/symbols.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/jak2/kdgo.h" +#include "game/kernel/jak2/klink.h" +#include "game/kernel/jak2/klisten.h" +#include "game/kernel/jak2/kmachine.h" +#include "game/kernel/jak2/kmalloc.h" +#include "game/kernel/jak2/kprint.h" + +namespace jak2 { +using namespace jak2_symbols; +// where to put a new symbol for the most recently searched for symbol that wasn't found +u32 symbol_slot; + +Ptr> LevelTypeList; +Ptr> CollapseQuote; +Ptr> SqlResult; +Ptr KernelDebug; + +void kscheme_init_globals() { + symbol_slot = 0; + LevelTypeList.offset = 0; + CollapseQuote.offset = 0; + SqlResult.offset = 0; + KernelDebug.offset = 0; +} + +/*! + * New method for types which cannot have "new" used on them. + * Prints an error to stdout and returns false. + */ +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()); + return s7.offset; +} + +template +Ptr> sym_to_string_ptr(Ptr> in) { + return Ptr>(in.offset + SYM_TO_STRING_OFFSET); +} + +template +Ptr sym_to_string(Ptr> in) { + return *sym_to_string_ptr(in); +} + +template +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; +} + +u64 alloc_from_heap(u32 heap_symbol, u32 type, s32 size, u32 pp) { + using namespace jak2_symbols; + 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)); +} + +/*! + * Make an empty string of given size. + * Allocates from the global heap. + */ +u64 make_string(u32 size) { + using namespace jak2_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; +} + +extern "C" { +void _arg_call_linux(); +} + +/*! + * 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_linux(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; + auto trampoline_function_addr = _arg_call_linux; + 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]; + } + + 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(); +} + +extern "C" { +void _stack_call_linux(); +void _stack_call_win32(); +} + +Ptr make_stack_arg_function_from_c_linux(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; + auto trampoline_function_addr = _stack_call_linux; + 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(); +} + +/*! + * 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(); +} + +/*! + * 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_linux(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_linux(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(); +} + +/*! + * 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(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(name); + auto func = make_stack_arg_function_from_c(f); + sym->value() = func.offset; + return func; +} + +/*! + * Set the named symbol to the value. This isn't specific to functions. + */ +u32 make_raw_function_symbol_from_c(const char* name, u32 value) { + intern_from_c(name)->value() = value; + return value; +} + +/*! + * Configure a "fixed" symbol to have a given name and value. The "fixed" symbols are symbols + * which have their location in the symbol table determined ahead of time and not looked up by the + * hash function. + */ +Ptr> set_fixed_symbol(u32 offset, const char* name, u32 value) { + // grab the symbol directly + Ptr> sym(s7.offset + offset); + ASSERT((sym.offset & 3) == 1); // + sym->value() = value; + + // set name of the symbol + *sym_to_string_ptr(sym).c() = Ptr(make_string_from_c(name)); + + // set hash of the symbol + *sym_to_hash(sym).c() = crc32((const u8*)name, strlen(name)); + + NumSymbols++; + return sym; +} + +/*! + * Search the hash table's fixed area for a symbol. + * Returns null if we didn't find it. + */ +Ptr> find_symbol_in_fixed_area(u32 hash, const char* name) { + for (u32 i = s7.offset; i < s7.offset + FIX_FIXED_SYM_END_OFFSET; i += 4) { + auto sym = Ptr>(i); + if (*sym_to_hash(sym) == hash) { + if (!strcmp(sym_to_string(sym)->data(), name)) { + return sym; + } + } + } + return Ptr>(0); +} + +/*! + * Do a linear probe from start to end to find a symbol (or a slot for a new symbol). + * If we run into the end without finding anything, returns 1 to indicate the linear probe needs to + * wrap around. If we run into a blank space, mark that as the slot. Also search the fixed area for + * the symbol. If we fail to find it without wrapping, and it's not in the fixed area, return 0. + */ +Ptr> find_symbol_in_area(u32 hash, const char* name, u32 start, u32 end) { + for (u32 i = start; i < end; i += 4) { + auto sym = Ptr>(i); + + // note - this may break if any symbols hash to zero! + if (*sym_to_hash(sym) == hash) { + if (!strcmp(sym_to_string(sym)->data(), name)) { + return sym; + } + } + + if (!(*sym_to_hash(sym))) { + // open slot! + // means we don't need to wrap. + symbol_slot = i; + + // check the fixed area, in case it's not dynamically placed. + return find_symbol_in_fixed_area(hash, name); + } + } + + // we got to the end without finding the symbol or an empty slot. Return 1 to indicate this. + return Ptr>(1); +} + +/*! + * Searches the table for a symbol. If the symbol is found, returns it. + * If not, returns 0, but symbol_slot will contain the slot for the symbol. + * If both are 0, the symbol table is full and you are sad. + * Also allows you to find the empty pair by searching for _empty_ + */ +Ptr> find_symbol_from_c(const char* name) { + symbol_slot = 0; // nowhere to put the symbol yet, clear any old symbol_slot result. + u32 hash = crc32((const u8*)name, (int)strlen(name)); + + // check if we've got the empty pair. + if (hash == EMPTY_HASH) { + if (!strcmp(name, "_empty_")) { + return (s7 + FIX_SYM_EMPTY_PAIR).cast>(); + } + } + + s32 sh1 = hash << 0x13; + s32 sh2 = sh1 >> 0x10; + // will be signed, bottom 3 bits 0 (for alignment, symbols are every 8 bytes) + // upper 16 bits are the same, so we will reach +/- 8 kb around 0. + + if (sh2 > 0) { + // upper table first. + auto probe = find_symbol_in_area(hash, name, s7.offset + sh2, LastSymbol.offset); + if (probe.offset != 1) { + return probe; + } + + // overflow! + probe = find_symbol_in_area(hash, name, SymbolTable2.offset, s7.offset - 0x10); + if (probe.offset == 1) { + // uh oh, both overflowed! + printf("[BIG WARNING] symbol table probe double overflow!\n"); + return find_symbol_in_fixed_area(hash, name); + } else { + return probe; + } + + } else { + // lower table first + auto probe = find_symbol_in_area(hash, name, s7.offset + sh2, s7.offset - 0x10); + if (probe.offset != 1) { + return probe; + } + + // overflow! + probe = + find_symbol_in_area(hash, name, s7.offset + FIX_FIXED_SYM_END_OFFSET, LastSymbol.offset); + if (probe.offset == 1) { + printf("[BIG WARNING] symbol table probe double overflow!\n"); + return find_symbol_in_fixed_area(hash, name); + } else { + return probe; + } + } +} + +/*! + * Returns a symbol with the given name. If this is the first time, make a new symbol, otherwise it + * returns the old one. Basically a LISP symbol intern + */ +Ptr> intern_from_c(const char* name) { + auto symbol = find_symbol_from_c(name); + if (symbol.offset) { + // already exists, return it! + return symbol; + } + + // otherwise, a new symbol! + symbol = Ptr>(symbol_slot); + + u32 hash = crc32((const u8*)name, (int)strlen(name)); + auto str = make_string_from_c(name); + *sym_to_string_ptr(symbol) = Ptr(str); + *sym_to_hash(symbol) = hash; + + NumSymbols++; + return symbol; +} + +/*! + * GOAL intern function. + */ +u64 intern(u32 name) { + return intern_from_c(Ptr(name)->data()).offset; +} + +namespace { +u32 size_of_type(u32 method_count) { + return (4 * method_count + 0x23) & 0xfffffff0; +} +} // namespace + +/*! + * 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)) { + printf("using level types!\n"); // added + 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(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(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(Ptr(name)->data(), methods).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; +} + +/*! + * 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)); + *sym_to_hash(type_symbol) = crc32((const u8*)name, strlen(name)); + 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; +} + +/*! + * New method of type. A GOAL (deftype) will end up calling this method. + * Internally does an intern. + */ +u64 new_type(u32 symbol, u32 parent, u64 flags) { + ASSERT(false); // nyi + return 0; +} + +/*! + * 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) { + ASSERT(false); // nyi + return 0; +} + +/*! + * 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(name)->value())); +} + +/*! + * 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; +} + +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; +} + +/*! + * 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) == PAIR_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 + 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 + 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 + 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) == PAIR_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\thash: #x%x\n\tvalue: ", obj, sym_to_string(sym)->data(), + *sym_to_hash(sym)); + 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 + 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; +} + +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); +} +} // namespace + +int InitHeapAndSymbol() { + // allocate memory for all 3 tables + Ptr symbol_table = + kmalloc(kglobalheap, jak2::SYM_TABLE_MEM_SIZE, KMALLOC_MEMSET, "symbol-table").cast(); + // setup table pointers + LastSymbol = symbol_table + 0xff00; + SymbolTable2 = symbol_table + 5; + s7 = symbol_table + 0x8001; + NumSymbols = 0; + printf("st alloced\n"); + + // inform compiler of s7 + reset_output(); + printf("reset output\n"); + + // empty pair (this is extra confusing). + *Ptr(s7.offset + FIX_SYM_EMPTY_CAR - 1) = s7.offset + FIX_SYM_EMPTY_PAIR - 1; + *Ptr(s7.offset + FIX_SYM_EMPTY_CDR - 1) = s7.offset + FIX_SYM_EMPTY_PAIR - 1; + printf("setup pair\n"); + + // 'global + fixed_sym_set(FIX_SYM_GLOBAL_HEAP, kglobalheap.offset); + + // basic types required to actually set up symbols + 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 up symbols! + 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", + make_function_from_c((void*)copy_basic).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 up types + 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); + + // 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_NOTHING_FUNC)); + object_type->copy_method = make_function_from_c((void*)copy_fixed); + + // STRUCTURE + 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); + + // BASIC + 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); + 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)); + + // SYMBOL + 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; + + // TYPE + 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; + + // STRING + 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); + + // FUNCTION + 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; + + // VU FUNCTION + set_fixed_type(FIX_SYM_VU_FUNCTION, "vu-function", get_fixed_type_symbol(FIX_SYM_STRUCTURE), + pack_type_flag(9, 0, 0x16), 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; + + // LINK BLOCK + 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; + + // KHEAP + 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); + + // ARRAY + set_fixed_type(FIX_SYM_ARRAY, "array", get_fixed_type_symbol(FIX_SYM_BASIC), + pack_type_flag(9, 0, 0x10), 0, 0); + + // PAIR + 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); + + // KERNEL + 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); + + // POINTER + 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; + + // NUMERIC + 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); + + make_function_symbol_from_c("string->symbol", (void*)intern); + 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_function_symbol_from_c("_format", (void*)format_impl_jak2); + 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_function_symbol_from_c("link", (void*)link_and_exec); + 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("memcpy-and-rellink", 0); + make_raw_function_symbol_from_c("symlink2", 0); + make_raw_function_symbol_from_c("symlink3", 0); + make_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("*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("*enable-method-set*"); + EnableMethodSet = method_set_symbol.cast() - 1; + method_set_symbol->value() = 0; + + KernelDebug = intern_from_c("*kernel-debug*").cast() - 1; + *KernelDebug = 0; + + intern_from_c("*boot-video-mode*")->value() = 0; + intern_from_c("*video-mode*")->value() = 0; + + SqlResult = intern_from_c("*sql-result*"); + SqlResult->value() = s7.offset; + + CollapseQuote = intern_from_c("*collapse-quote*"); + CollapseQuote->value() = s7.offset + FIX_SYM_TRUE; + LevelTypeList = intern_from_c("*level-type-list*"); + + // load kernel! + + 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("*kernel-version*")->value(); + if (!kernel_version || ((kernel_version >> 0x13) != KERNEL_VERSION_MAJOR)) { + MsgErr("\n"); + MsgErr( + "dkernel: compiled C kernel version is %d.%d but the goal kernel is %d.%d\n\tfrom the " + "goal> prompt (:mch) then mkee your kernel in linux.\n", + 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); + } + } + + // in real jak2, the -1 occurs in the GoalProtoHandler. But let's just do it here to make + // things consistent. + protoBlock.deci2count = intern_from_c("*deci-count*").cast() - 1; + + // load stuff for the listener interface + InitListener(); + + // Do final initialization, including loading and initializing the engine. + 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) { + ASSERT(false); + return 0; +} + +/*! + * "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) { + ASSERT(false); + return 0; +} +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/kscheme.h b/game/kernel/jak2/kscheme.h new file mode 100644 index 0000000000..1b6798d487 --- /dev/null +++ b/game/kernel/jak2/kscheme.h @@ -0,0 +1,60 @@ +#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 jak2 { + +constexpr s32 SYMBOL_OFFSET = 1; + +extern Ptr> SqlResult; + +/*! + * 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]; + } +}; + +void kscheme_init_globals(); +Ptr> intern_from_c(const char* name); +Ptr intern_type_from_c(const char* name, u64 methods); +u64 call_method_of_type_arg2(u32 arg, Ptr type, u32 method_id, u32 a1, u32 a2); +Ptr> find_symbol_from_c(const char* name); +u64 make_string_from_c(const char* c_str); +u64 new_pair(u32 heap, u32 type, u32 car, u32 cdr); +u64 inspect_object(u32 obj); +u64 print_object(u32 obj); +Ptr make_function_symbol_from_c(const char* name, void* f); +Ptr make_stack_arg_function_symbol_from_c(const char* name, void* f); +int InitHeapAndSymbol(); +u64 load(u32 file_name_in, u32 heap_in); +u64 loadb(u32 file_name_in, u32 heap_in, u32 param3); +u64 loado(u32 file_name_in, u32 heap_in); +u64 unload(u32 name); +u64 call_method_of_type(u32 arg, Ptr type, u32 method_id); +u64 call_goal_function_by_name(const char* name); +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/ksound.cpp b/game/kernel/jak2/ksound.cpp new file mode 100644 index 0000000000..e5604224fb --- /dev/null +++ b/game/kernel/jak2/ksound.cpp @@ -0,0 +1,21 @@ +#include "ksound.h" + +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/ksound.h" +#include "game/kernel/jak2/kscheme.h" + +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); + + // PC port interns + make_function_symbol_from_c("pc-sound-set-flava-hack", (void*)set_flava_hack); + make_function_symbol_from_c("pc-sound-set-fade-hack", (void*)set_fade_hack); +} +} // namespace jak2 \ No newline at end of file diff --git a/game/kernel/jak2/ksound.h b/game/kernel/jak2/ksound.h new file mode 100644 index 0000000000..d7d9c613ef --- /dev/null +++ b/game/kernel/jak2/ksound.h @@ -0,0 +1,5 @@ +#pragma once + +namespace jak2 { +void InitSoundScheme(); +} \ No newline at end of file diff --git a/game/kernel/kdgo.h b/game/kernel/kdgo.h deleted file mode 100644 index 16ae0d3add..0000000000 --- a/game/kernel/kdgo.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -/*! - * @file kdgo.h - * Loading DGO Files. Also has some general SIF RPC stuff used for RPCs other than DGO loading. - * DONE! - */ - -#include "Ptr.h" -#include "kmalloc.h" - -#include "common/common_types.h" - -void kdgo_init_globals(); -u32 InitRPC(); -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 StopIOP(); - -u64 RpcCall_wrapper(void* args); -s32 RpcCall(s32 rpcChannel, - u32 fno, - bool async, - void* sendBuff, - s32 sendSize, - void* recvBuff, - s32 recvSize); -u32 RpcBusy(s32 channel); -void LoadDGOTest(); diff --git a/game/kernel/klisten.h b/game/kernel/klisten.h deleted file mode 100644 index f8781e33b6..0000000000 --- a/game/kernel/klisten.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*! - * @file klisten.h - * Implementation of the Listener protocol - * Done - */ - -#ifndef JAK_KLISTEN_H -#define JAK_KLISTEN_H - -#include "kmachine.h" -#include "kscheme.h" - -extern Ptr ListenerFunction; -extern Ptr kernel_dispatcher; -extern Ptr print_column; -extern Ptr kernel_packages; - -void klisten_init_globals(); -void InitListener(); -void ClearPending(); -void SendAck(); -void ProcessListenerMessage(Ptr msg); - -#endif // JAK_KLISTEN_H diff --git a/game/kernel/kmachine.h b/game/kernel/kmachine.h deleted file mode 100644 index dd7ff7938d..0000000000 --- a/game/kernel/kmachine.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once - -/*! - * @file kmachine.h - * GOAL Machine. Contains low-level hardware interfaces for GOAL. - */ - -#include "Ptr.h" - -#include "common/common_types.h" - -//! Toggle to use more memory. To simulate the original game's memory layout, set this to false. -// Make sure this matches the const in gcommon.gc. -constexpr bool BIG_MEMORY = true; - -//! How much space to leave for the stack when creating the debug heap -// In the game, it's 16 kB, but we increase it to 64 kB. -// ASAN builds + fmt stuff uses a _ton_ of stack when no optimizations are on and we -// need more. -constexpr u32 DEBUG_HEAP_SPACE_FOR_STACK = 0x10000; - -//! First free address for the GOAL heap -constexpr u32 HEAP_START = 0x13fd20; - -//! Where to end the global heap so it doesn't overlap with the stack. -constexpr u32 GLOBAL_HEAP_END = 0x1ffc000 + (BIG_MEMORY ? (0x1ffc000 - HEAP_START) : 0); // doubled - -//! Location of kglobalheap, kdebugheap kheapinfo structures. -constexpr u32 GLOBAL_HEAP_INFO_ADDR = 0x13AD00; -constexpr u32 DEBUG_HEAP_INFO_ADDR = 0x13AD10; -constexpr u32 LINK_CONTROL_NAME_ADDR = 0x13AD80; - -//! Where to place the debug heap -constexpr u32 DEBUG_HEAP_START = 0x5000000; - -/*! - * Where does OVERLORD load its data from? - */ -enum OverlordDataSource : u32 { - fakeiso = 0, //! some sort of development way of getting data - deviso = 1, //! some sort of development way of getting data - iso_cd = 2, //! use the actual DVD drive -}; - -extern OverlordDataSource isodrv; - -// Get IOP modules from DVD or from dsefilesv -extern u32 modsrc; - -// Reboot IOP on start? -extern u32 reboot; - -// Discord RPC -struct DiscordRichPresence; -extern int gDiscordRpcEnabled; -extern int64_t gStartTime; - -/*! - * Initialize globals for kmachine. - * This should be called before running main. - */ -void kmachine_init_globals(); - -/*! - * Initialize global variables based on command line parameters - */ -void InitParms(int argc, const char* const* argv); - -/*! - * Initialize the CD Drive - */ -void InitCD(); - -/*! - * Initialize the I/O Processor - */ -void InitIOP(); - -/*! - * Initialize the GS and display the splash screen. - */ -void InitVideo(); - -/*! - * Initialze GOAL Runtime - */ -int InitMachine(); - -/*! - * Shutdown GOAL runtime. - */ -int ShutdownMachine(); - -/*! - * Flush caches. Does all the memory, regardless of what you specify - */ -void CacheFlush(void* mem, int size); - -void InitMachineScheme(); - -//! Mirror of cpad-info -struct CPadInfo { - u8 valid; - u8 status; - u16 button0; - u8 rightx; - u8 righty; - u8 leftx; - u8 lefty; - u8 abutton[12]; - u8 dummy[12]; - s32 number; - s32 cpad_file; - u32 button0_abs[3]; - u32 button0_shadow_abs[1]; - u32 button0_rel[3]; - float stick0_dir; - float stick0_speed; - s32 new_pad; - s32 state; - u8 align[6]; - u8 direct[6]; - u8 buzz_val[2]; - u8 __pad[2]; - u64 buzz_time[2]; - u32 buzz; - s32 buzz_act; - s32 change_time; // actually u64 in goal! -}; - -struct FileStream { - u32 flags; - u32 mode; // basic - u32 name; // basic - s32 file; // int32 -}; - -struct DiscordInfo { - u32 fuel; - u32 money_total; - u32 buzzer_total; - u32 deaths; - u32 status; - u32 level; - u32 cutscene; // check if cutscene is playing - u32 ogreboss; // are we fighting ogreboss? - u32 plantboss; // are we fighting plant-boss? - u32 racer; // are we driving the zoomer? - u32 flutflut; // are we riding on flut flut? - u32 time_of_day; -}; - -// static_assert(offsetof(CpadInfo, new_pad) == 76, "cpad type offset"); - -void vif_interrupt_callback(); -u32 offset_of_s7(); diff --git a/game/kernel/ksound.h b/game/kernel/ksound.h deleted file mode 100644 index b48a88306a..0000000000 --- a/game/kernel/ksound.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -/*! - * @file ksound.h - * There's not much here. My guess is this was set up as framework to match the kmachine.cpp format, - * but whoever did the sound didn't use this. - */ - -void InitSound(); -void ShutdownSound(); -void InitSoundScheme(); diff --git a/game/mips2c/functions/bones.cpp b/game/mips2c/functions/bones.cpp index 5f29a66dd1..dcd4aa6f30 100644 --- a/game/mips2c/functions/bones.cpp +++ b/game/mips2c/functions/bones.cpp @@ -2,7 +2,8 @@ //--------------------------MIPS2C--------------------- // clang-format off #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace bones_mtx_calc { @@ -691,7 +692,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace draw_bones_merc { struct Cache { @@ -1044,7 +1045,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace draw_bones_check_longest_edge_asm { struct Cache { diff --git a/game/mips2c/functions/collide_cache.cpp b/game/mips2c/functions/collide_cache.cpp index 791c2d06b3..b369167279 100644 --- a/game/mips2c/functions/collide_cache.cpp +++ b/game/mips2c/functions/collide_cache.cpp @@ -1,9 +1,9 @@ //--------------------------MIPS2C--------------------- #include "common/dma/gs.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" - +using namespace jak1; namespace { u32 vu0_buffer[1024]; // todo, maybe can be 512. u32 vi1 = 0; @@ -790,7 +790,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_28_collide_cache { struct Cache { @@ -1073,7 +1073,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_27_collide_cache { struct Cache { @@ -1369,7 +1369,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_29_collide_cache { struct Cache { @@ -1558,7 +1558,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_12_collide_shape_prim_mesh { struct Cache { @@ -1804,7 +1804,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_14_collide_shape_prim_mesh { struct Cache { @@ -2049,7 +2049,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_13_collide_shape_prim_mesh { struct Cache { @@ -2300,7 +2300,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_30_collide_cache { u64 execute(void* ctxt) { @@ -2502,7 +2502,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_9_collide_cache_prim { struct Cache { @@ -2690,7 +2690,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_10_collide_cache_prim { struct Cache { @@ -2902,7 +2902,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_10_collide_puss_work { u64 execute(void* ctxt) { @@ -3071,7 +3071,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_9_collide_puss_work { struct Cache { diff --git a/game/mips2c/functions/collide_edge_grab.cpp b/game/mips2c/functions/collide_edge_grab.cpp index 0cfd98e23f..155e43450d 100644 --- a/game/mips2c/functions/collide_edge_grab.cpp +++ b/game/mips2c/functions/collide_edge_grab.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { namespace method_16_collide_edge_work { struct Cache { @@ -595,7 +596,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_10_collide_edge_hold_list { u64 execute(void* ctxt) { @@ -689,7 +690,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_18_collide_edge_work { struct Cache { diff --git a/game/mips2c/functions/collide_func.cpp b/game/mips2c/functions/collide_func.cpp index 90c8f9f8a9..c0c6a0ccdf 100644 --- a/game/mips2c/functions/collide_func.cpp +++ b/game/mips2c/functions/collide_func.cpp @@ -1,7 +1,7 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" - +using namespace jak1; // clang-format off namespace Mips2C { namespace collide_do_primitives { @@ -261,7 +261,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace moving_sphere_triangle_intersect { struct Cache { diff --git a/game/mips2c/functions/collide_mesh.cpp b/game/mips2c/functions/collide_mesh.cpp index 27c696856d..0128af6224 100644 --- a/game/mips2c/functions/collide_mesh.cpp +++ b/game/mips2c/functions/collide_mesh.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" -#include "game/mips2c/mips2c_private.h" +#include "game/kernel/jak1/kscheme.h" +#include "game/mips2c/mips2c_private.h" +using namespace jak1; // clang-format off namespace Mips2C { namespace method_12_collide_mesh { @@ -202,7 +203,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_11_collide_mesh { struct Cache { @@ -377,7 +378,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_14_collide_mesh { u64 execute(void* ctxt) { @@ -581,7 +582,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace method_15_collide_mesh { u64 execute(void* ctxt) { diff --git a/game/mips2c/functions/collide_probe.cpp b/game/mips2c/functions/collide_probe.cpp index 8d2cb26453..aaf7513906 100644 --- a/game/mips2c/functions/collide_probe.cpp +++ b/game/mips2c/functions/collide_probe.cpp @@ -2,7 +2,8 @@ //--------------------------MIPS2C--------------------- // clang-format off #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace collide_probe_node { struct Cache { @@ -353,7 +354,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace collide_probe_instance_tie { struct Cache { diff --git a/game/mips2c/functions/draw_string.cpp b/game/mips2c/functions/draw_string.cpp index ee082a87d9..31aceffe66 100644 --- a/game/mips2c/functions/draw_string.cpp +++ b/game/mips2c/functions/draw_string.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- // clang-format off #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace draw_string { struct Cache { diff --git a/game/mips2c/functions/generic_effect.cpp b/game/mips2c/functions/generic_effect.cpp index 224ad2a910..13032c7a6c 100644 --- a/game/mips2c/functions/generic_effect.cpp +++ b/game/mips2c/functions/generic_effect.cpp @@ -1,8 +1,9 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" -#include "game/mips2c/mips2c_private.h" +#include "game/kernel/jak1/kscheme.h" +#include "game/mips2c/mips2c_private.h" +using namespace jak1; // clang-format off namespace Mips2C { @@ -449,7 +450,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_light_proc { struct Cache { @@ -889,7 +890,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_envmap_proc { struct Cache { @@ -1658,7 +1659,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_prepare_dma_single { struct Cache { diff --git a/game/mips2c/functions/generic_effect2.cpp b/game/mips2c/functions/generic_effect2.cpp index 0427154777..bb44dd2f26 100644 --- a/game/mips2c/functions/generic_effect2.cpp +++ b/game/mips2c/functions/generic_effect2.cpp @@ -1,6 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" + +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { // clang-format off @@ -656,7 +658,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_interp_dproc { struct Cache { @@ -835,7 +837,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_no_light_dproc { struct Cache { diff --git a/game/mips2c/functions/generic_merc.cpp b/game/mips2c/functions/generic_merc.cpp index 6b00eec6eb..7b0f8afe7a 100644 --- a/game/mips2c/functions/generic_merc.cpp +++ b/game/mips2c/functions/generic_merc.cpp @@ -1,5 +1,6 @@ -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { namespace generic_prepare_dma_single { @@ -282,7 +283,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_merc_execute_asm { struct Cache { @@ -1350,7 +1351,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace mercneric_convert { struct Cache { @@ -3537,7 +3538,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace high_speed_reject { struct Cache { diff --git a/game/mips2c/functions/generic_tie.cpp b/game/mips2c/functions/generic_tie.cpp index 69dc9d0858..3d14810105 100644 --- a/game/mips2c/functions/generic_tie.cpp +++ b/game/mips2c/functions/generic_tie.cpp @@ -1,7 +1,8 @@ // clang-format off //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace generic_tie_dma_to_spad_sync { struct Cache { @@ -106,7 +107,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace generic_prepare_dma_double { diff --git a/game/mips2c/functions/joint.cpp b/game/mips2c/functions/joint.cpp index dbf0bb2f1c..d358e759dc 100644 --- a/game/mips2c/functions/joint.cpp +++ b/game/mips2c/functions/joint.cpp @@ -1,7 +1,7 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" - +using namespace jak1; // clang-format off namespace { @@ -819,7 +819,7 @@ u32 jump_table_vals[16] = { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace decompress_frame_data_to_accumulator { u64 execute(void* ctxt) { @@ -1419,7 +1419,7 @@ u32 jump_table_vals[16] = { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace decompress_fixed_data_to_accumulator { u64 execute(void* ctxt) { @@ -2035,7 +2035,7 @@ u32 jump_table_vals[16] = { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace normalize_frame_quaternions { u64 execute(void* ctxt) { @@ -2089,7 +2089,7 @@ u64 execute(void* ctxt) { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace clear_frame_accumulator { u64 execute(void* ctxt) { @@ -2149,7 +2149,7 @@ u64 execute(void* ctxt) { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace calc_animation_from_spr { @@ -2395,7 +2395,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace cspace_parented_transformq_joint { u64 execute(void* ctxt) { diff --git a/game/mips2c/functions/merc_blend_shape.cpp b/game/mips2c/functions/merc_blend_shape.cpp index 3ab10d2483..069447c0f0 100644 --- a/game/mips2c/functions/merc_blend_shape.cpp +++ b/game/mips2c/functions/merc_blend_shape.cpp @@ -2,7 +2,8 @@ // clang-format off //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace blerc_execute { struct Cache { @@ -406,7 +407,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace setup_blerc_chains_for_one_fragment { struct Cache { diff --git a/game/mips2c/functions/ocean.cpp b/game/mips2c/functions/ocean.cpp index 3df9501262..aaebe4f7c1 100644 --- a/game/mips2c/functions/ocean.cpp +++ b/game/mips2c/functions/ocean.cpp @@ -2,7 +2,8 @@ //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { ExecutionContext ocean_regs_vfs; @@ -73,7 +74,7 @@ u64 execute(void* ctxt); } //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace draw_large_polygon_ocean { struct Cache { @@ -267,7 +268,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace render_ocean_quad { struct Cache { diff --git a/game/mips2c/functions/ocean_vu0.cpp b/game/mips2c/functions/ocean_vu0.cpp index 1500153372..bcdcb63a66 100644 --- a/game/mips2c/functions/ocean_vu0.cpp +++ b/game/mips2c/functions/ocean_vu0.cpp @@ -1,7 +1,8 @@ // clang-format off //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace ocean_interp_wave { struct Cache { @@ -202,7 +203,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace ocean_generate_verts { struct Cache { diff --git a/game/mips2c/functions/ripple.cpp b/game/mips2c/functions/ripple.cpp index bb342e143f..e75a904200 100644 --- a/game/mips2c/functions/ripple.cpp +++ b/game/mips2c/functions/ripple.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { struct RippleVu0 { @@ -88,7 +89,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace ripple_create_wave_table { struct Cache { @@ -419,7 +420,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace ripple_apply_wave_table { struct Cache { @@ -523,7 +524,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace ripple_matrix_scale { u64 execute(void* ctxt) { diff --git a/game/mips2c/functions/shadow.cpp b/game/mips2c/functions/shadow.cpp index 6cd03cbce7..9413f991ca 100644 --- a/game/mips2c/functions/shadow.cpp +++ b/game/mips2c/functions/shadow.cpp @@ -1,6 +1,7 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { // clang-format off namespace { @@ -374,7 +375,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_calc_dual_verts { u64 execute(void* ctxt) { @@ -580,7 +581,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_scissor_edges { u64 execute(void* ctxt) { @@ -688,7 +689,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_scissor_top { u64 execute(void* ctxt) { @@ -764,7 +765,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_init_vars { struct Cache { @@ -830,7 +831,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_find_facing_single_tris { u64 execute(void* ctxt) { @@ -1134,7 +1135,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_find_single_edges { u64 execute(void* ctxt) { @@ -1234,7 +1235,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_find_facing_double_tris { u64 execute(void* ctxt) { @@ -1516,7 +1517,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_find_double_edges { u64 execute(void* ctxt) { @@ -1622,7 +1623,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_add_verts { struct Cache { @@ -1806,7 +1807,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_add_facing_single_tris { struct Cache { @@ -1885,7 +1886,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_add_single_edges { struct Cache { @@ -1968,7 +1969,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_add_double_tris { struct Cache { @@ -2045,7 +2046,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_add_double_edges { struct Cache { @@ -2128,7 +2129,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace shadow_execute { struct Cache { diff --git a/game/mips2c/functions/sky_tng.cpp b/game/mips2c/functions/sky_tng.cpp index 0ecaab6cf4..8aef0c43a5 100644 --- a/game/mips2c/functions/sky_tng.cpp +++ b/game/mips2c/functions/sky_tng.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" -#include "game/mips2c/mips2c_private.h" +#include "game/kernel/jak1/kscheme.h" +#include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { ExecutionContext sky_regs_vfs; @@ -11,7 +12,7 @@ ExecutionContext sky_regs_vfs; // clang-format off //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace init_sky_regs { struct Cache { @@ -106,7 +107,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace set_tex_offset { u64 execute(void* ctxt) { @@ -154,7 +155,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace clip_polygon_against_positive_hyperplane { u64 execute(void* ctxt) { @@ -439,7 +440,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace clip_polygon_against_negative_hyperplane { u64 execute(void* ctxt) { @@ -734,7 +735,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace draw_large_polygon { struct Cache { @@ -884,7 +885,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace render_sky_quad { struct Cache { @@ -988,7 +989,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace render_sky_tri { struct Cache { diff --git a/game/mips2c/functions/sparticle.cpp b/game/mips2c/functions/sparticle.cpp index 97ee17525d..1d0cdfb798 100644 --- a/game/mips2c/functions/sparticle.cpp +++ b/game/mips2c/functions/sparticle.cpp @@ -1,7 +1,8 @@ // clang-format off //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace sp_process_block_3d { struct Cache { @@ -369,7 +370,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace sp_process_block_2d { struct Cache { diff --git a/game/mips2c/functions/sparticle_launcher.cpp b/game/mips2c/functions/sparticle_launcher.cpp index 95bf01b67e..44721696e7 100644 --- a/game/mips2c/functions/sparticle_launcher.cpp +++ b/game/mips2c/functions/sparticle_launcher.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- // clang-format off #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace sp_init_fields { struct Cache { @@ -341,7 +342,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace particle_adgif { struct Cache { @@ -471,7 +472,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace sp_launch_particles_var { struct Cache { diff --git a/game/mips2c/functions/test_func.cpp b/game/mips2c/functions/test_func.cpp index 623e535a41..6f770af8e0 100644 --- a/game/mips2c/functions/test_func.cpp +++ b/game/mips2c/functions/test_func.cpp @@ -1,6 +1,7 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { namespace test_func { u64 execute(void* ctxt) { @@ -18,8 +19,7 @@ u64 execute(void* ctxt) { } // namespace Mips2C //--------------------------MIPS2C--------------------- -#include "game/kernel/kprint.h" -#include "game/kernel/kscheme.h" + #include "game/mips2c/mips2c_private.h" namespace Mips2C { namespace goal_call_test { diff --git a/game/mips2c/functions/texture.cpp b/game/mips2c/functions/texture.cpp index 914f8ecafa..04dae9f04a 100644 --- a/game/mips2c/functions/texture.cpp +++ b/game/mips2c/functions/texture.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- // clang-format off #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" +using namespace jak1; namespace Mips2C { namespace adgif_shader_texture_with_update { u64 execute(void* ctxt) { diff --git a/game/mips2c/functions/tfrag.cpp b/game/mips2c/functions/tfrag.cpp index 9c29d0e791..6bb421e9e1 100644 --- a/game/mips2c/functions/tfrag.cpp +++ b/game/mips2c/functions/tfrag.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { namespace draw_inline_array_tfrag { struct Cache { @@ -714,7 +715,7 @@ void link() { // clang-format off //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace stats_tfrag_asm { struct Cache { diff --git a/game/mips2c/functions/tie_methods.cpp b/game/mips2c/functions/tie_methods.cpp index b322297219..e240e25d71 100644 --- a/game/mips2c/functions/tie_methods.cpp +++ b/game/mips2c/functions/tie_methods.cpp @@ -1,7 +1,8 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; namespace Mips2C { namespace draw_inline_array_instance_tie { struct Cache { @@ -1347,7 +1348,7 @@ void link() { //--------------------------MIPS2C--------------------- #include "game/mips2c/mips2c_private.h" -#include "game/kernel/kscheme.h" + namespace Mips2C { namespace draw_inline_array_prototype_tie_generic_asm { struct Cache { diff --git a/game/mips2c/functions/time_of_day.cpp b/game/mips2c/functions/time_of_day.cpp index c987e2cc22..7ab849afd6 100644 --- a/game/mips2c/functions/time_of_day.cpp +++ b/game/mips2c/functions/time_of_day.cpp @@ -1,7 +1,9 @@ //--------------------------MIPS2C--------------------- -#include "game/kernel/kscheme.h" + +#include "game/kernel/jak1/kscheme.h" #include "game/mips2c/mips2c_private.h" +using namespace jak1; // clang-format off namespace Mips2C { diff --git a/game/mips2c/mips2c_table.cpp b/game/mips2c/mips2c_table.cpp index 535df70adb..4133c91e61 100644 --- a/game/mips2c/mips2c_table.cpp +++ b/game/mips2c/mips2c_table.cpp @@ -3,8 +3,9 @@ #include "common/log/log.h" #include "common/symbols.h" -#include "game/kernel/kmalloc.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/common/kmalloc.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/jak1/kscheme.h" using namespace jak1_symbols; @@ -413,8 +414,9 @@ void LinkedFunctionTable::reg(const std::string& name, u64 (*exec)(void*), u32 s } // this is short stub that will jump to the appropriate function. - auto jump_to_asm = Ptr(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP, - *(s7 + FIX_SYM_FUNCTION_TYPE), 0x40, UNKNOWN_PP)); + // todo jak1 + auto jump_to_asm = Ptr(jak1::alloc_heap_object( + s7.offset + FIX_SYM_GLOBAL_HEAP, *(s7 + FIX_SYM_FUNCTION_TYPE), 0x40, UNKNOWN_PP)); it.first->second.goal_trampoline = jump_to_asm; u8* ptr = jump_to_asm.c(); diff --git a/game/mips2c/mips2c_table.h b/game/mips2c/mips2c_table.h index 9b9f9ff0fd..a95e9fad77 100644 --- a/game/mips2c/mips2c_table.h +++ b/game/mips2c/mips2c_table.h @@ -9,7 +9,7 @@ #include "common/common_types.h" #include "common/util/Assert.h" -#include "game/kernel/Ptr.h" +#include "game/kernel/common/Ptr.h" namespace Mips2C { diff --git a/game/overlord/iso.cpp b/game/overlord/iso.cpp index e6b46d98a4..48ac46d4c9 100644 --- a/game/overlord/iso.cpp +++ b/game/overlord/iso.cpp @@ -21,6 +21,7 @@ #include "game/common/dgo_rpc_types.h" #include "game/overlord/srpc.h" +#include "game/runtime.h" #include "game/sce/iop.h" #include "game/sound/sdshim.h" #include "game/sound/sndshim.h" @@ -1271,7 +1272,7 @@ u32 DGOThread() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, DGO_RPC_ID, RPC_DGO, sRPCBuff, nullptr, nullptr, &dq); + sceSifRegisterRpc(&serve, DGO_RPC_ID[g_game_version], RPC_DGO, sRPCBuff, nullptr, nullptr, &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; diff --git a/game/overlord/overlord.cpp b/game/overlord/overlord.cpp index 7b5a350bdd..170eb29bc7 100644 --- a/game/overlord/overlord.cpp +++ b/game/overlord/overlord.cpp @@ -8,6 +8,8 @@ #include "srpc.h" #include "ssound.h" +#include "common/util/Assert.h" + #include "game/sce/iop.h" using namespace iop; diff --git a/game/overlord/ramdisk.cpp b/game/overlord/ramdisk.cpp index 33c87feaa5..3186e2c5b9 100644 --- a/game/overlord/ramdisk.cpp +++ b/game/overlord/ramdisk.cpp @@ -16,6 +16,7 @@ #include "common/util/Assert.h" #include "game/common/ramdisk_rpc_types.h" +#include "game/runtime.h" #include "game/sce/iop.h" // Note - the RAMDISK code supports having multiple files, but it appears only one file can ever be @@ -99,7 +100,8 @@ u32 Thread_Server() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, RAMDISK_RPC_ID, RPC_Ramdisk, gRPCBuf, nullptr, nullptr, &dq); + sceSifRegisterRpc(&serve, RAMDISK_RPC_ID[g_game_version], RPC_Ramdisk, gRPCBuf, nullptr, nullptr, + &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; diff --git a/game/overlord/srpc.cpp b/game/overlord/srpc.cpp index d532b59fa1..375243c288 100644 --- a/game/overlord/srpc.cpp +++ b/game/overlord/srpc.cpp @@ -15,6 +15,7 @@ #include "game/common/loader_rpc_types.h" #include "game/common/player_rpc_types.h" #include "game/graphics/gfx.h" +#include "game/runtime.h" #include "game/sce/iop.h" #include "game/sound/sndshim.h" @@ -65,7 +66,8 @@ u32 Thread_Player() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, PLAYER_RPC_ID, RPC_Player, gPlayerBuf, nullptr, nullptr, &dq); + sceSifRegisterRpc(&serve, PLAYER_RPC_ID[g_game_version], RPC_Player, gPlayerBuf, nullptr, nullptr, + &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; @@ -81,7 +83,8 @@ u32 Thread_Loader() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, LOADER_RPC_ID, RPC_Loader, gLoaderBuf, nullptr, nullptr, &dq); + sceSifRegisterRpc(&serve, LOADER_RPC_ID[g_game_version], RPC_Loader, gLoaderBuf, nullptr, nullptr, + &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; diff --git a/game/overlord/stream.cpp b/game/overlord/stream.cpp index 0068c0f35e..b7055d32bf 100644 --- a/game/overlord/stream.cpp +++ b/game/overlord/stream.cpp @@ -16,6 +16,7 @@ #include "game/overlord/iso_api.h" #include "game/overlord/isocommon.h" #include "game/overlord/srpc.h" +#include "game/runtime.h" #include "game/sce/iop.h" using namespace iop; @@ -61,7 +62,7 @@ u32 STRThread() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, STR_RPC_ID, RPC_STR, &sSTRBuf, nullptr, nullptr, &dq); + sceSifRegisterRpc(&serve, STR_RPC_ID[g_game_version], RPC_STR, &sSTRBuf, nullptr, nullptr, &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; @@ -74,7 +75,7 @@ u32 PLAYThread() { CpuDisableIntr(); sceSifInitRpc(0); sceSifSetRpcQueue(&dq, GetThreadId()); - sceSifRegisterRpc(&serve, PLAY_RPC_ID, RPC_PLAY, sPLAYBuf, nullptr, nullptr, &dq); + sceSifRegisterRpc(&serve, PLAY_RPC_ID[g_game_version], RPC_PLAY, sPLAYBuf, nullptr, nullptr, &dq); CpuEnableIntr(); sceSifRpcLoop(&dq); return 0; diff --git a/game/runtime.cpp b/game/runtime.cpp index 695f61d1c8..80895546a0 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -26,17 +26,25 @@ #include "common/goal_constants.h" #include "common/log/log.h" #include "common/util/FileUtil.h" +#include "common/versions.h" #include "game/graphics/gfx.h" -#include "game/kernel/fileio.h" -#include "game/kernel/kboot.h" -#include "game/kernel/kdgo.h" -#include "game/kernel/kdsnetm.h" -#include "game/kernel/klink.h" -#include "game/kernel/klisten.h" -#include "game/kernel/kmemcard.h" -#include "game/kernel/kprint.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kdgo.h" +#include "game/kernel/common/kdsnetm.h" +#include "game/kernel/common/klink.h" +#include "game/kernel/common/klisten.h" +#include "game/kernel/common/kmachine.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/jak1/kboot.h" +#include "game/kernel/jak1/klisten.h" +#include "game/kernel/jak1/kscheme.h" +#include "game/kernel/jak2/kboot.h" +#include "game/kernel/jak2/klisten.h" +#include "game/kernel/jak2/kscheme.h" #include "game/overlord/dma.h" #include "game/overlord/fake_iso.h" #include "game/overlord/iso.h" @@ -60,6 +68,7 @@ u8* g_ee_main_mem = nullptr; std::thread::id g_main_thread_id = std::thread::id(); +GameVersion g_game_version = GameVersion::Jak1; namespace { @@ -145,23 +154,40 @@ void ee_runner(SystemThreadInterface& iface) { // this may not work well on systems with a page size > 1 MB. mprotect((void*)g_ee_main_mem, EE_MAIN_MEM_LOW_PROTECT, PROT_NONE); fileio_init_globals(); - kboot_init_globals(); + jak1::kboot_init_globals(); + jak2::kboot_init_globals(); + + kboot_init_globals_common(); kdgo_init_globals(); - kdsnetm_init_globals(); + kdsnetm_init_globals_common(); klink_init_globals(); - kmachine_init_globals(); - kscheme_init_globals(); - kmalloc_init_globals(); + kmachine_init_globals_common(); + jak1::kscheme_init_globals(); + jak2::kscheme_init_globals(); + kscheme_init_globals_common(); + kmalloc_init_globals_common(); klisten_init_globals(); + jak1::klisten_init_globals(); + jak2::klisten_init_globals(); + kmemcard_init_globals(); - kprint_init_globals(); + kprint_init_globals_common(); // Added for OpenGOAL's debugger xdbg::allow_debugging(); - goal_main(g_argc, g_argv); + switch (g_game_version) { + case GameVersion::Jak1: + jak1::goal_main(g_argc, g_argv); + break; + case GameVersion::Jak2: + jak2::goal_main(g_argc, g_argv); + break; + default: + ASSERT_MSG(false, "Unsupported game version"); + } lg::debug("[EE] Done!"); // // kill the IOP todo @@ -287,6 +313,8 @@ RuntimeExitStatus exec_runtime(int argc, char** argv) { VM::use = true; } else if (std::string("-novm") == argv[i]) { // disable debug ps2 VM VM::use = false; + } else if (std::string("-jak2") == argv[i]) { + g_game_version = GameVersion::Jak2; } } diff --git a/game/runtime.h b/game/runtime.h index 21637eadca..5311f8d125 100644 --- a/game/runtime.h +++ b/game/runtime.h @@ -8,10 +8,12 @@ #include #include "common/common_types.h" +#include "common/versions.h" -#include "game/kernel/kboot.h" +#include "game/kernel/common/kboot.h" extern u8* g_ee_main_mem; +extern GameVersion g_game_version; RuntimeExitStatus exec_runtime(int argc, char** argv); diff --git a/game/sce/deci2.h b/game/sce/deci2.h index b4149cde7c..0bc71609df 100644 --- a/game/sce/deci2.h +++ b/game/sce/deci2.h @@ -5,9 +5,6 @@ * Implementation of SCE DECI2 library. */ -#ifndef JAK1_DECI2_H -#define JAK1_DECI2_H - #include "common/listener_common.h" class Deci2Server; @@ -25,5 +22,3 @@ s32 sceDeci2ExRecv(s32 s, void* buf, u16 len); s32 sceDeci2ExSend(s32 s, void* buf, u16 len); } // namespace ee - -#endif // JAK1_DECI2_H diff --git a/game/sce/libpad.cpp b/game/sce/libpad.cpp index f3f1c7bcdf..2b1736c06c 100644 --- a/game/sce/libpad.cpp +++ b/game/sce/libpad.cpp @@ -3,7 +3,7 @@ #include "common/util/Assert.h" #include "game/graphics/gfx.h" -#include "game/kernel/kmachine.h" +#include "game/kernel/common/kernel_types.h" /*! * @file libpad.cpp diff --git a/game/sce/sif_ee.cpp b/game/sce/sif_ee.cpp index be2358daf4..52cdc04406 100644 --- a/game/sce/sif_ee.cpp +++ b/game/sce/sif_ee.cpp @@ -45,7 +45,7 @@ void sceFsReset() {} int sceSifLoadModule(const char* name, int arg_size, const char* args) { if (!strcmp(name, "cdrom0:\\\\DRIVERS\\\\OVERLORD.IRX;1") || - !strcmp(name, "host0:binee/overlord.irx")) { + !strcmp(name, "host0:binee/overlord.irx") || !strcmp(name, "host0:bin/overlord.irx")) { const char* src = args; char* dst = iop->overlord_arg_data; int cnt; diff --git a/game/sound/989snd/ame_handler.cpp b/game/sound/989snd/ame_handler.cpp index 47295b51dc..9f8c16631f 100644 --- a/game/sound/989snd/ame_handler.cpp +++ b/game/sound/989snd/ame_handler.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: ISC #include "ame_handler.h" -#include "game/kernel/ksound.h" #include "game/sound/989snd/blocksound_handler.h" namespace snd { diff --git a/game/system/deci_common.h b/game/system/deci_common.h index 4adbcbace8..623a3022d5 100644 --- a/game/system/deci_common.h +++ b/game/system/deci_common.h @@ -1,7 +1,5 @@ #pragma once -#ifndef JAK_DECIM_COMMON_H -#define JAK_DECIM_COMMON_H #include "common/common_types.h" struct Deci2Driver { @@ -22,5 +20,3 @@ struct Deci2Driver { #define DECI2_WRITE 3 #define DECI2_WRITEDONE 4 #define DECI2_CHSTATUS 5 - -#endif // JAK_DECIM_COMMON_H diff --git a/game/system/vm/dmac.cpp b/game/system/vm/dmac.cpp index d971f61ef7..54d68d96ce 100644 --- a/game/system/vm/dmac.cpp +++ b/game/system/vm/dmac.cpp @@ -10,7 +10,7 @@ #include "common/log/log.h" -#include "game/kernel/kmalloc.h" +#include "game/kernel/common/kmalloc.h" #include "game/runtime.h" namespace VM { diff --git a/game/system/vm/dmac.h b/game/system/vm/dmac.h index 6849b21380..a8b7503d02 100644 --- a/game/system/vm/dmac.h +++ b/game/system/vm/dmac.h @@ -8,7 +8,7 @@ #include "common/common_types.h" -#include "game/kernel/Ptr.h" +#include "game/kernel/common/Ptr.h" namespace VM { diff --git a/game/system/vm/vm.cpp b/game/system/vm/vm.cpp index c9dc9b3244..ca97d03922 100644 --- a/game/system/vm/vm.cpp +++ b/game/system/vm/vm.cpp @@ -14,8 +14,6 @@ #include "common/log/log.h" -#include "game/kernel/kscheme.h" - namespace VM { bool use = true; // enable VM by default, since we're debugging right now diff --git a/goalc/build_level/Tfrag.cpp b/goalc/build_level/Tfrag.cpp index a5ac595584..1b0e277941 100644 --- a/goalc/build_level/Tfrag.cpp +++ b/goalc/build_level/Tfrag.cpp @@ -8,7 +8,7 @@ #include "goalc/data_compiler/DataObjectGenerator.h" void tfrag_from_gltf(const gltf_mesh_extract::TfragOutput& mesh_extract_out, - DrawableTreeTfrag& out, + DrawableTreeTfrag& /*out*/, tfrag3::TfragTree& out_pc) { out_pc.kind = tfrag3::TFragmentTreeKind::NORMAL; // todo more types? out_pc.draws = std::move(mesh_extract_out.strip_draws); diff --git a/goalc/build_level/gltf_mesh_extract.cpp b/goalc/build_level/gltf_mesh_extract.cpp index 12ef722520..7415300019 100644 --- a/goalc/build_level/gltf_mesh_extract.cpp +++ b/goalc/build_level/gltf_mesh_extract.cpp @@ -665,7 +665,7 @@ std::optional> subdivide_face_if_needed(CollideFace fac } } -void extract(const Input& in, +void extract(const Input& /*in*/, CollideOutput& out, const tinygltf::Model& model, const std::vector& all_nodes) { diff --git a/goalc/debugger/Debugger.cpp b/goalc/debugger/Debugger.cpp index f7f1701863..d7cac28b0b 100644 --- a/goalc/debugger/Debugger.cpp +++ b/goalc/debugger/Debugger.cpp @@ -518,6 +518,7 @@ bool Debugger::write_memory(const u8* src_buffer, int size, u32 goal_addr) { void Debugger::read_symbol_table() { // todo: this assumes many things specific to jak 1. using namespace jak1_symbols; + using namespace jak1; ASSERT(is_valid() && is_attached() && is_halted()); u32 bytes_read = 0; u32 reads = 0; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b98c46ce29..1929508bdc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,7 +8,7 @@ add_executable(goalc-test ${CMAKE_CURRENT_LIST_DIR}/test_reader.cpp ${CMAKE_CURRENT_LIST_DIR}/test_goos.cpp ${CMAKE_CURRENT_LIST_DIR}/test_listener_deci2.cpp - ${CMAKE_CURRENT_LIST_DIR}/test_kernel.cpp + ${CMAKE_CURRENT_LIST_DIR}/test_kernel_jak1.cpp ${CMAKE_CURRENT_LIST_DIR}/all_jak1_symbols.cpp ${CMAKE_CURRENT_LIST_DIR}/test_type_system.cpp ${CMAKE_CURRENT_LIST_DIR}/test_CodeTester.cpp diff --git a/test/test_kernel.cpp b/test/test_kernel_jak1.cpp similarity index 93% rename from test/test_kernel.cpp rename to test/test_kernel_jak1.cpp index 1308253cec..b26f4f152c 100644 --- a/test/test_kernel.cpp +++ b/test/test_kernel_jak1.cpp @@ -3,17 +3,21 @@ #include #include "common/goal_constants.h" +#include "common/listener_common.h" #include "common/symbols.h" #include "all_jak1_symbols.h" -#include "game/kernel/fileio.h" -#include "game/kernel/kboot.h" -#include "game/kernel/kdsnetm.h" -#include "game/kernel/kprint.h" -#include "game/kernel/kscheme.h" +#include "game/kernel/common/fileio.h" +#include "game/kernel/common/kboot.h" +#include "game/kernel/common/kprint.h" +#include "game/kernel/common/kscheme.h" +#include "game/kernel/common/memory_layout.h" +#include "game/kernel/jak1/fileio.h" +#include "game/kernel/jak1/kscheme.h" #include "gtest/gtest.h" using namespace jak1_symbols; +using namespace jak1; TEST(Kernel, strend) { char test[] = "test"; @@ -181,7 +185,7 @@ TEST(Kernel, ftoa) { TEST(Kernel, itoa_base_10) { char buffer[128]; - kprint_init_globals(); + kprint_init_globals_common(); // simple print 1 kitoa(buffer, 1, 10, -1, ' ', 0); @@ -228,7 +232,7 @@ TEST(Kernel, itoa_base_10) { TEST(Kernel, itoa_base_2) { char buffer[128]; - kprint_init_globals(); + kprint_init_globals_common(); kitoa(buffer, 1, 2, -1, ' ', 0); EXPECT_EQ("1", std::string(buffer)); @@ -263,7 +267,7 @@ TEST(Kernel, itoa_base_2) { TEST(Kernel, itoa_base_16) { char buffer[128]; - kprint_init_globals(); + kprint_init_globals_common(); kitoa(buffer, 1, 16, -1, ' ', 0); EXPECT_EQ("1", std::string(buffer)); @@ -295,8 +299,8 @@ void setup_hack_heaps(void* mem, int size) { int heap_size = (size - HEAP_START) / 2; MasterDebug = 1; EXPECT_TRUE(heap_size > 8 * 1024 * 1024); - kmalloc_init_globals(); - kprint_init_globals(); + kmalloc_init_globals_common(); + kprint_init_globals_common(); kinitheap(kglobalheap, Ptr(HEAP_START), heap_size); kinitheap(kdebugheap, Ptr(HEAP_START + heap_size), heap_size); @@ -305,8 +309,8 @@ void setup_hack_heaps(void* mem, int size) { // create a fake symbol table auto symbol_table = - kmalloc(kglobalheap, SYM_TABLE_MEM_SIZE, KMALLOC_MEMSET, "symbol-table").cast(); - s7 = symbol_table + (GOAL_MAX_SYMBOLS / 2) * 8 + BASIC_OFFSET; + kmalloc(kglobalheap, jak1::SYM_TABLE_MEM_SIZE, KMALLOC_MEMSET, "symbol-table").cast(); + s7 = symbol_table + (jak1::GOAL_MAX_SYMBOLS / 2) * 8 + BASIC_OFFSET; SymbolTable2 = symbol_table + BASIC_OFFSET; LastSymbol = symbol_table + 0xff00; NumSymbols = 0; diff --git a/tools/MemoryDumpTool/main.cpp b/tools/MemoryDumpTool/main.cpp index bca9b3e7cc..83223cae15 100644 --- a/tools/MemoryDumpTool/main.cpp +++ b/tools/MemoryDumpTool/main.cpp @@ -89,7 +89,8 @@ u32 scan_for_symbol_table(const Ram& ram, u32 start_addr, u32 end_addr) { fmt::print("got {} candidates for #f:\n", candidates.size()); for (auto addr : candidates) { - auto str = addr + BASIC_OFFSET + SYM_INFO_OFFSET; + // todo: this is wrong + auto str = addr + BASIC_OFFSET + jak1::SYM_INFO_OFFSET; fmt::print(" trying 0x{:x}:\n", addr); if (ram.word_in_memory(str)) { auto mem = ram.word(str + 4); // offset of str in SymInfo @@ -125,12 +126,13 @@ SymbolMap build_symbol_map(const Ram& ram, u32 s7) { LastSymbol = symbol_table + 0xff00; */ - auto symbol_table = s7 - ((GOAL_MAX_SYMBOLS / 2) * 8 + BASIC_OFFSET); + // todo wrong + auto symbol_table = s7 - ((jak1::GOAL_MAX_SYMBOLS / 2) * 8 + BASIC_OFFSET); auto SymbolTable2 = symbol_table + BASIC_OFFSET; auto LastSymbol = symbol_table + 0xff00; for (u32 sym = SymbolTable2; sym < LastSymbol; sym += 8) { - auto info = sym + SYM_INFO_OFFSET; // already has basic offset + auto info = sym + jak1::SYM_INFO_OFFSET; // already has basic offset auto str = ram.word(info + 4); if (str) { auto name = ram.string(str + 4);