diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index 3bc52f8fe..1df0e5883 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -68,7 +68,10 @@ Interpreter::Interpreter() { {"type?", &Interpreter::eval_type}, {"current-method-type", &Interpreter::eval_current_method_type}, {"fmt", &Interpreter::eval_format}, - {"error", &Interpreter::eval_error}}; + {"error", &Interpreter::eval_error}, + {"string-ref", &Interpreter::eval_string_ref}, + {"string-length", &Interpreter::eval_string_length}, + {"ash", &Interpreter::eval_ash}}; string_to_type = {{"empty-list", ObjectType::EMPTY_LIST}, {"integer", ObjectType::INTEGER}, @@ -1042,6 +1045,8 @@ IntType Interpreter::number_to_integer(const Object& obj) { return obj.integer_obj.value; case ObjectType::FLOAT: return (int64_t)obj.float_obj.value; + case ObjectType::CHAR: + return (int8_t)obj.char_obj.value; default: throw_eval_error(obj, "object cannot be interpreted as a number!"); } @@ -1527,4 +1532,44 @@ Object Interpreter::eval_error(const Object& form, throw_eval_error(form, "Error: " + args.unnamed.at(0).as_string()->data); return EmptyListObject::make_new(); } + +Object Interpreter::eval_string_ref(const Object& form, + Arguments& args, + const std::shared_ptr& env) { + (void)env; + vararg_check(form, args, {ObjectType::STRING, ObjectType::INTEGER}, {}); + auto str = args.unnamed.at(0).as_string(); + auto idx = args.unnamed.at(1).as_int(); + if ((size_t)idx >= str->data.size()) { + throw_eval_error(form, fmt::format("String index {} out of range for string of size {}", idx, + str->data.size())); + } + return Object::make_char(str->data.at(idx)); +} + +Object Interpreter::eval_string_length(const Object& form, + Arguments& args, + const std::shared_ptr& env) { + (void)env; + vararg_check(form, args, {ObjectType::STRING}, {}); + auto str = args.unnamed.at(0).as_string(); + return Object::make_integer(str->data.length()); +} + +Object Interpreter::eval_ash(const Object& form, + Arguments& args, + const std::shared_ptr& env) { + (void)env; + vararg_check(form, args, {{}, {}}, {}); + auto val = number_to_integer(args.unnamed.at(0)); + auto sa = number_to_integer(args.unnamed.at(1)); + if (sa >= 0 && sa < 64) { + return Object::make_integer(val << sa); + } else if (sa > -64) { + return Object::make_integer(val >> sa); + } else { + throw_eval_error(form, fmt::format("Shift amount {} is out of range", sa)); + return EmptyListObject::make_new(); + } +} } // namespace goos diff --git a/common/goos/Interpreter.h b/common/goos/Interpreter.h index f869623a7..16a4f50e6 100644 --- a/common/goos/Interpreter.h +++ b/common/goos/Interpreter.h @@ -186,6 +186,15 @@ class Interpreter { Object eval_error(const Object& form, Arguments& args, const std::shared_ptr& env); + Object eval_string_ref(const Object& form, + Arguments& args, + const std::shared_ptr& env); + Object eval_string_length(const Object& form, + Arguments& args, + const std::shared_ptr& env); + Object eval_ash(const Object& form, + Arguments& args, + const std::shared_ptr& env); // specials Object eval_define(const Object& form, diff --git a/common/versions.h b/common/versions.h index d858d5738..5e99f048b 100644 --- a/common/versions.h +++ b/common/versions.h @@ -25,3 +25,7 @@ constexpr u32 TX_PAGE_VERSION = 7; // GOAL kernel version (OpenGOAL changes this version from the game's version) constexpr int KERNEL_VERSION_MAJOR = 2; constexpr int KERNEL_VERSION_MINOR = 0; + +// OVERLORD version returned by an RPC +constexpr int IRX_VERSION_MAJOR = 2; +constexpr int IRX_VERSION_MINOR = 0; \ No newline at end of file diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index fca204f99..7b3624e69 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -3091,7 +3091,7 @@ void push_asm_pcpyud_to_stack(const AsmOp* op, if (bitfield_info && possible_r0->reg() == Register(Reg::GPR, Reg::R0)) { auto base = pop_to_forms({*var}, env, pool, stack, true).at(0); auto read_elt = pool.alloc_element(base, arg0_type); - read_elt->push_pcpyud(); + read_elt->push_pcpyud(env.dts->ts, pool, env); stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, read_elt), true, env.get_variable_type(*dst, true)); } else { diff --git a/decompiler/IR2/bitfields.cpp b/decompiler/IR2/bitfields.cpp index 9587f93fe..24c07a1ee 100644 --- a/decompiler/IR2/bitfields.cpp +++ b/decompiler/IR2/bitfields.cpp @@ -144,6 +144,9 @@ BitfieldAccessElement::BitfieldAccessElement(Form* base_value, const TypeSpec& t } goos::Object BitfieldAccessElement::to_form_internal(const Env& env) const { + if (m_current_result) { + return m_current_result->to_form(env); + } return pretty_print::build_list("incomplete-bitfield-access", m_base->to_form(env)); } @@ -164,11 +167,11 @@ void BitfieldAccessElement::get_modified_regs(RegSet& regs) const { m_base->get_modified_regs(regs); } -const BitField& find_field(const TypeSystem& ts, - const BitFieldType* type, - int start_bit, - int size, - std::optional looking_for_unsigned) { +std::optional try_find_field(const TypeSystem& ts, + const BitFieldType* type, + int start_bit, + int size, + std::optional looking_for_unsigned) { for (auto& field : type->fields()) { if (field.size() == size && field.offset() == start_bit) { // the GOAL compiler sign extended floats. @@ -195,9 +198,22 @@ const BitField& find_field(const TypeSystem& ts, } } } - throw std::runtime_error( - fmt::format("Unmatched field: start bit {}, size {}, signed? {}, type {}", start_bit, size, - !looking_for_unsigned, type->get_name())); + return {}; +} + +BitField find_field(const TypeSystem& ts, + const BitFieldType* type, + int start_bit, + int size, + std::optional looking_for_unsigned) { + auto result = try_find_field(ts, type, start_bit, size, looking_for_unsigned); + if (!result) { + throw std::runtime_error( + fmt::format("Unmatched field: start bit {}, size {}, signed? {}, type {}", start_bit, size, + !looking_for_unsigned, type->get_name())); + } else { + return *result; + } } namespace { @@ -238,7 +254,7 @@ std::optional get_bitfield_initial_set(Form* form, int right = mr.maps.ints.at(2); int size = 64 - left; int offset = left - right; - auto& f = find_field(ts, type, offset, size, {}); + auto f = find_field(ts, type, offset, size, {}); BitFieldDef def; def.value = value; def.field_name = f.name(); @@ -261,7 +277,7 @@ std::optional get_bitfield_initial_set(Form* form, int right = *power_of_two; int size = 64 - left; int offset = left - right; - auto& f = find_field(ts, type, offset, size, {}); + auto f = find_field(ts, type, offset, size, {}); BitFieldDef def; def.value = value; def.field_name = f.name(); @@ -281,7 +297,7 @@ std::optional get_bitfield_initial_set(Form* form, int right = 0; int size = 64 - left; int offset = left - right; - auto& f = find_field(ts, type, offset, size, {}); + auto f = find_field(ts, type, offset, size, {}); BitFieldDef def; def.value = value; def.field_name = f.name(); @@ -296,7 +312,7 @@ std::optional get_bitfield_initial_set(Form* form, auto value = mr_sllv.maps.forms.at(0); int size = 32; int offset = 0; - auto& f = find_field(ts, type, offset, size, {}); + auto f = find_field(ts, type, offset, size, {}); BitFieldDef def; def.value = value; def.field_name = f.name(); @@ -309,11 +325,23 @@ std::optional get_bitfield_initial_set(Form* form, } // namespace -void BitfieldAccessElement::push_pcpyud() { +void BitfieldAccessElement::push_pcpyud(const TypeSystem& ts, FormPool& pool, const Env& /*env*/) { if (!m_steps.empty()) { throw std::runtime_error("Unexpected pcpyud!"); } m_got_pcpyud = true; + + // look for a 64-bit field + auto type = ts.lookup_type(m_type); + auto as_bitfield = dynamic_cast(type); + assert(as_bitfield); + auto field = try_find_field(ts, as_bitfield, 64, 64, true); + if (field) { + auto result = + pool.alloc_element(m_base, false, DerefToken::make_field_name(field->name())); + result->inline_nested(); + m_current_result = pool.alloc_single_form(this, result); + } } /*! @@ -571,6 +599,45 @@ BitFieldDef BitFieldDef::from_constant(const BitFieldConstantDef& constant, Form return bfd; } +namespace { +Form* cast_sound_name(FormPool& pool, const Env& env, Form* in) { + auto matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::PCPYLD), + {Matcher::any(1), Matcher::any(0)}); + auto mr = match(matcher, in); + if (!mr.matched) { + return nullptr; + } + + auto hi = mr.maps.forms.at(1); + auto lo = mr.maps.forms.at(0); + + auto hi_int = get_goal_integer_constant(hi, env); + auto lo_int = get_goal_integer_constant(lo, env); + if (!hi_int || !lo_int) { + return nullptr; + } + + char name[17]; + memcpy(name, &lo_int.value(), 8); + memcpy(name + 8, &hi_int.value(), 8); + name[16] = '\0'; + + bool got_zero = false; + for (int i = 0; i < 16; i++) { + if (name[i] == 0) { + got_zero = true; + } else { + if (got_zero) { + return nullptr; + } + } + } + + return pool.alloc_single_element_form( + nullptr, fmt::format("(static-sound-name \"{}\")", name)); +} +} // namespace + /*! * Cast the given form to a bitfield. * If the form could have been a (new 'static 'bitfieldtype ...) it will attempt to generate this. @@ -581,6 +648,15 @@ Form* cast_to_bitfield(const BitFieldType* type_info, const Env& env, Form* in) { in = strip_int_or_uint_cast(in); + + if (type_info->get_name() == "sound-name") { + auto as_sound_name = cast_sound_name(pool, env, in); + if (as_sound_name) { + return as_sound_name; + } + // just do a normal cast if that failed. + return pool.alloc_single_element_form(nullptr, typespec, in); + } // check if it's just a constant: auto in_as_atom = form_as_atom(in); if (in_as_atom && in_as_atom->is_int()) { diff --git a/decompiler/IR2/bitfields.h b/decompiler/IR2/bitfields.h index 7031db95a..1ae1b8f09 100644 --- a/decompiler/IR2/bitfields.h +++ b/decompiler/IR2/bitfields.h @@ -100,11 +100,15 @@ class BitfieldAccessElement : public FormElement { const TypeSystem& ts, FormPool& pool, const Env& env); - void push_pcpyud(); + void push_pcpyud(const TypeSystem& ts, FormPool& pool, const Env& env); private: bool m_got_pcpyud = false; Form* m_base = nullptr; + + // if we aren't sure if we are done or not, store the result here. + Form* m_current_result = nullptr; + TypeSpec m_type; std::vector m_steps; }; @@ -190,9 +194,9 @@ Form* cast_to_bitfield_enum(const EnumType* type_info, FormPool& pool, const Env std::optional get_goal_integer_constant(Form* in, const Env&); -const BitField& find_field(const TypeSystem& ts, - const BitFieldType* type, - int start_bit, - int size, - std::optional looking_for_unsigned); +BitField find_field(const TypeSystem& ts, + const BitFieldType* type, + int start_bit, + int size, + std::optional looking_for_unsigned); } // namespace decompiler diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index 90944cfd2..a8cc22429 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -160,4 +160,6 @@ - Fixed a bug where reader errors in `goal-lib.gc` or any error in `goos-lib.gs` would cause a crash - Fixed a bug where `''a` or similar repeated reader macros would generate a reader error. - Fixed a bug where reader macros without a following form would trigger an assert. -- It is now possible to take the address of a lexical variable. The variable will be spilled to the stack automatically. \ No newline at end of file +- It is now possible to take the address of a lexical variable. The variable will be spilled to the stack automatically. +- GOOS supports `string-ref`, `string-length`, `ash`, and characters can now be treated as a signed 8-bit number +- Fixed a bug where saved xmm registers might be clobbered when calling a C++ function that wasn't `format`. \ No newline at end of file diff --git a/game/common/dgo_rpc_types.h b/game/common/dgo_rpc_types.h index 6675a6632..18d15a2fc 100644 --- a/game/common/dgo_rpc_types.h +++ b/game/common/dgo_rpc_types.h @@ -5,9 +5,6 @@ * Types used for the DGO Remote Procedure Call between the EE and the IOP */ -#ifndef JAK1_DGO_RPC_TYPES_H -#define JAK1_DGO_RPC_TYPES_H - #include "common/common_types.h" constexpr int DGO_RPC_ID = 0xdeb4; @@ -29,5 +26,3 @@ struct RPC_Dgo_Cmd { uint32_t buffer_heap_top; char name[16]; }; - -#endif // JAK1_DGO_RPC_TYPES_H diff --git a/game/common/loader_rpc_types.h b/game/common/loader_rpc_types.h index f3a296859..1deee886f 100644 --- a/game/common/loader_rpc_types.h +++ b/game/common/loader_rpc_types.h @@ -5,10 +5,5 @@ * Types used for the Loader Remote Procedure Call between the EE and the IOP */ -#ifndef JAK1_LOADER_RPC_TYPES_H -#define JAK1_LOADER_RPC_TYPES_H - constexpr int LOADER_RPC_ID = 0xdeb2; constexpr int LOADER_RPC_CHANNEL = 1; - -#endif // JAK1_LOADER_RPC_TYPES_H diff --git a/game/kernel/asm_funcs.asm b/game/kernel/asm_funcs.asm index ffe12d385..21cd984c7 100644 --- a/game/kernel/asm_funcs.asm +++ b/game/kernel/asm_funcs.asm @@ -6,6 +6,44 @@ SECTION .text +;; Call C++ code on linux, from GOAL, using Linux calling convention. +global _arg_call_linux +_arg_call_linux: + pop rax + push r10 + push r11 + sub rsp, 8 + + ; xmm stuff + sub rsp, 128 + movaps [rsp], xmm8 + movaps [rsp + 16], xmm9 + movaps [rsp + 32], xmm10 + movaps [rsp + 48], xmm11 + movaps [rsp + 64], xmm12 + movaps [rsp + 80], xmm13 + movaps [rsp + 96], xmm14 + movaps [rsp + 112], xmm15 + + call rax + + movaps xmm8, [rsp] + movaps xmm9, [rsp + 16] + movaps xmm10, [rsp + 32] + movaps xmm11, [rsp + 48] + movaps xmm12, [rsp + 64] + movaps xmm13, [rsp + 80] + movaps xmm14, [rsp + 96] + movaps xmm15, [rsp + 112] + add rsp, 128 + + add rsp, 8 + pop r11 + pop r10 + ret + + +;; Call C++ code on linux, from GOAL. Pug arguments on the stack and put a pointer to this array in the first arg. ;; this function pushes all 8 OpenGOAL registers into a stack array. ;; then it calls the function pointed to by rax with a pointer to this array. ;; it returns the return value of the called function. @@ -64,7 +102,7 @@ _stack_call_linux: ; return! ret -;; windows implementation of stack_call +;; Call C++ code on windows, from GOAL. Pug arguments on the stack and put a pointer to this array in the first arg. global _stack_call_win32 _stack_call_win32: pop rax @@ -124,6 +162,7 @@ _stack_call_win32: ret ;; The _call_goal_asm function is used to call a GOAL function from C. +;; It calls on the parent stack, which is a bad idea if your stack is not already a GOAL stack. ;; It supports up to 3 arguments and a return value. ;; This should be called with the arguments: ;; - first goal arg @@ -163,6 +202,7 @@ _call_goal_asm_linux: pop r13 ret +;; Call goal, but switch stacks. global _call_goal_on_stack_asm_linux _call_goal_on_stack_asm_linux: diff --git a/game/kernel/kscheme.cpp b/game/kernel/kscheme.cpp index d93e4e30b..b1e80f2c1 100644 --- a/game/kernel/kscheme.cpp +++ b/game/kernel/kscheme.cpp @@ -318,39 +318,44 @@ u64 make_string_from_c(const char* 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 very fast and doesn't use the stack. + * But calling this function is fast. It used to be really fast but wrong. */ Ptr make_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, *(s7 + FIX_SYM_FUNCTION_TYPE), 0x40)); auto f = (uint64_t)func; - auto fp = (u8*)&f; + auto target_function = (u8*)&f; + auto trampoline_function_addr = _arg_call_linux; + 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]; + // 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 r10 - // push r11 - // sub rsp, 8 - // call rax - // add rsp, 8 - // pop r11 - // pop r10 - // ret - for (auto x : {0x41, 0x52, 0x41, 0x53, 0x48, 0x83, 0xEC, 0x08, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x08, - 0x41, 0x5B, 0x41, 0x5A, 0xC3}) { - mem.c()[i++] = x; + // 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]; } - // the C function's ret will return to the caller of this trampoline. + // 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); @@ -403,8 +408,6 @@ ret mem.c()[i++] = x; } - // the C function's ret will return to the caller of this trampoline. - // CacheFlush(mem, 0x34); return mem.cast(); diff --git a/game/overlord/iso.cpp b/game/overlord/iso.cpp index b8451fb1d..013ce2be3 100644 --- a/game/overlord/iso.cpp +++ b/game/overlord/iso.cpp @@ -270,6 +270,8 @@ u32 ISOThread() { InitDriver(temp_buffer->get_data()); // unblocks InitISOFS's WaitMbx FreeBuffer(temp_buffer); + VagCommand* in_progress_vag_command = nullptr; + // main CD/DVD read loop for (;;) { ///////////////////////////////////// @@ -358,7 +360,27 @@ u32 ISOThread() { load_single_cmd->callback_function = RunDGOStateMachine; } } - } else { + } else if (msg_from_mbx->cmd_id == LOAD_SOUND_BANK) { + // if there's an in progress vag command, try again. + if (!in_progress_vag_command || !in_progress_vag_command->field_0x3c) { + auto buff = TryAllocateBuffer(BUFFER_PAGE_SIZE); + if (!buff) { + // no buffers, try again. + SendMbx(iso_mbx, msg_from_mbx); + } else { + // todo - actual load here + auto* cmd = (SoundBankLoadCommand*)msg_from_mbx; + printf("Ignoring request to load sound bank %s\n", cmd->bank_name); + FreeBuffer(buff); + ReturnMessage(msg_from_mbx); + } + } else { + // just try again... + SendMbx(iso_mbx, msg_from_mbx); + } + } + + else { printf("[OVERLORD] Unknown ISOThread message id 0x%x\n", msg_from_mbx->cmd_id); } diff --git a/game/overlord/iso.h b/game/overlord/iso.h index 38d26fd37..a43bde5f0 100644 --- a/game/overlord/iso.h +++ b/game/overlord/iso.h @@ -6,9 +6,6 @@ * This is a huge mess */ -#ifndef JAK_V2_ISO_H -#define JAK_V2_ISO_H - #include "common/common_types.h" #include "isocommon.h" @@ -16,5 +13,3 @@ void iso_init_globals(); FileRecord* FindISOFile(const char* name); u32 GetISOFileLength(FileRecord* f); u32 InitISOFS(const char* fs_mode, const char* loading_screen); - -#endif // JAK_V2_ISO_H diff --git a/game/overlord/iso_api.cpp b/game/overlord/iso_api.cpp index de050d024..339febedd 100644 --- a/game/overlord/iso_api.cpp +++ b/game/overlord/iso_api.cpp @@ -1,6 +1,8 @@ #include "iso_api.h" #include "game/sce/iop.h" #include "common/log/log.h" +#include "common/util/assert.h" +#include "sbank.h" using namespace iop; @@ -65,3 +67,20 @@ s32 LoadISOFileChunkToEE(FileRecord* file, uint32_t dest_addr, uint32_t length, } return cmd.length_to_copy; } + +/*! + * Send a command to the ISO thread to load a sound bank. This will sleep the calling thread + * until the load has completed. + */ +void LoadSoundBank(const char* bank_name, SoundBank* bank) { + (void)bank; + assert(strlen(bank_name) < 16); + SoundBankLoadCommand cmd; + cmd.cmd_id = LOAD_SOUND_BANK; + cmd.messagebox_to_reply = 0; + cmd.thread_id = GetThreadId(); + strcpy(cmd.bank_name, bank_name); + cmd.bank = bank; + SendMbx(iso_mbx, &cmd); + SleepThread(); // wait for finish. +} \ No newline at end of file diff --git a/game/overlord/iso_api.h b/game/overlord/iso_api.h index 59fcb427f..54511d978 100644 --- a/game/overlord/iso_api.h +++ b/game/overlord/iso_api.h @@ -2,6 +2,9 @@ #include "isocommon.h" +struct SoundBank; + s32 LoadISOFileToIOP(FileRecord* file, void* addr, uint32_t length); s32 LoadISOFileToEE(FileRecord* file, uint32_t ee_addr, uint32_t length); s32 LoadISOFileChunkToEE(FileRecord* file, uint32_t dest_addr, uint32_t length, uint32_t offset); +void LoadSoundBank(const char* bank_name, SoundBank* bank); \ No newline at end of file diff --git a/game/overlord/iso_queue.cpp b/game/overlord/iso_queue.cpp index 8494ebd05..3eeb79e09 100644 --- a/game/overlord/iso_queue.cpp +++ b/game/overlord/iso_queue.cpp @@ -35,7 +35,6 @@ VagCommand vag_cmds[N_VAG_CMDS]; static s32 sSema; -IsoBufferHeader* TryAllocateBuffer(uint32_t size); void ReleaseMessage(IsoMessage* cmd); void FreeVAGCommand(VagCommand* cmd); diff --git a/game/overlord/iso_queue.h b/game/overlord/iso_queue.h index fcaa1a9ec..409f3b729 100644 --- a/game/overlord/iso_queue.h +++ b/game/overlord/iso_queue.h @@ -1,8 +1,5 @@ #pragma once -#ifndef JAK_V2_ISO_QUEUE_H -#define JAK_V2_ISO_QUEUE_H - #include "common/common_types.h" #include "isocommon.h" @@ -15,5 +12,4 @@ void UnqueueMessage(IsoMessage* cmd); IsoMessage* GetMessage(); void ProcessMessageData(); void ReturnMessage(IsoMessage* cmd); - -#endif // JAK_V2_ISO_QUEUE_H +IsoBufferHeader* TryAllocateBuffer(uint32_t size); diff --git a/game/overlord/isocommon.h b/game/overlord/isocommon.h index 8c0e43693..430348a03 100644 --- a/game/overlord/isocommon.h +++ b/game/overlord/isocommon.h @@ -30,6 +30,7 @@ constexpr int LOAD_TO_EE_CMD_ID = 0x100; // command to load file to ee constexpr int LOAD_TO_IOP_CMD_ID = 0x101; // command to load to iop constexpr int LOAD_TO_EE_OFFSET_CMD_ID = 0x102; // command to load file to ee with offset. constexpr int LOAD_DGO_CMD_ID = 0x200; // command to load DGO +constexpr int LOAD_SOUND_BANK = 0x300; // Command to load a sound bank constexpr int MAX_ISO_FILES = 350; // maximum files on FS constexpr int MAX_OPEN_FILES = 16; // maximum number of open files at a time. @@ -117,6 +118,13 @@ struct VagCommand : public IsoMessage { // 0x6c max }; +struct SoundBank; + +struct SoundBankLoadCommand : public IsoMessage { + char bank_name[16]; + SoundBank* bank; +}; + /*! * DGO Load State Machine states. */ @@ -167,16 +175,16 @@ struct PriStackEntry { * API to access files. There are debug modes + reading from an ISO filesystem. */ struct IsoFs { - int (*init)(u8*); - FileRecord* (*find)(const char*); - FileRecord* (*find_in)(const char*); - uint32_t (*get_length)(FileRecord*); - LoadStackEntry* (*open)(FileRecord*, int32_t); - LoadStackEntry* (*open_wad)(FileRecord*, int32_t); - void (*close)(LoadStackEntry*); - uint32_t (*begin_read)(LoadStackEntry*, void*, int32_t); - uint32_t (*sync_read)(); - uint32_t (*load_sound_bank)(char*, void*); + int (*init)(u8*); // 0 + FileRecord* (*find)(const char*); // 4 + FileRecord* (*find_in)(const char*); // 8 + uint32_t (*get_length)(FileRecord*); // c + LoadStackEntry* (*open)(FileRecord*, int32_t); // 10 + LoadStackEntry* (*open_wad)(FileRecord*, int32_t); // 14 + void (*close)(LoadStackEntry*); // 18 + uint32_t (*begin_read)(LoadStackEntry*, void*, int32_t); // 1c + uint32_t (*sync_read)(); // 20 + uint32_t (*load_sound_bank)(char*, void*); // 24 uint32_t (*load_music)(char*, void*); void (*poll_drive)(); }; diff --git a/game/overlord/overlord.cpp b/game/overlord/overlord.cpp index cf0aad18e..e5f17cdee 100644 --- a/game/overlord/overlord.cpp +++ b/game/overlord/overlord.cpp @@ -5,6 +5,7 @@ #include "iso.h" #include "ssound.h" #include "sbank.h" +#include "srpc.h" using namespace iop; @@ -44,21 +45,22 @@ int start_overlord(int argc, const char* const* argv) { // return 1; // } // - // thread_param.attr = TH_C; - // thread_param.initPriority = 99; - // thread_param.stackSize = 0x1000; - // thread_param.option = 0; - // thread_param.entry = Thread_Loader; - // auto thread_loader = CreateThread(&thread_param); - // if(thread_loader <= 0) { - // return 1; - // } + thread_param.attr = TH_C; + thread_param.initPriority = 99; + thread_param.stackSize = 0x1000; + thread_param.option = 0; + thread_param.entry = (void*)Thread_Loader; + strcpy(thread_param.name, "Loader"); // added for debug + auto thread_loader = CreateThread(&thread_param); + if (thread_loader <= 0) { + return 1; + } InitISOFS(argv[1], argv[2]); StartThread(thread_server, 0); // StartThread(thread_player, 0); - // StartThread(thread_loader, 0); + StartThread(thread_loader, 0); return 0; } diff --git a/game/overlord/sbank.cpp b/game/overlord/sbank.cpp index 5b88c51c1..726bc4dca 100644 --- a/game/overlord/sbank.cpp +++ b/game/overlord/sbank.cpp @@ -1,3 +1,63 @@ +#include #include "sbank.h" -void InitBanks() {} +constexpr int N_BANKS = 3; +SoundBank* gBanks[N_BANKS]; +SoundBank gCommonBank; +SoundBank gLevelBank[2]; + +void sbank_init_globals() { + gBanks[0] = &gCommonBank; + gBanks[1] = &gLevelBank[0]; + gBanks[2] = &gLevelBank[1]; + memset((void*)&gCommonBank, 0, sizeof(gCommonBank)); + memset((void*)&gLevelBank, 0, sizeof(gLevelBank)); +} + +void InitBanks() { + for (auto& gBank : gBanks) { + gBank->unk1 = 0; + gBank->unk2 = 0; + strcpy(gBank->name, ""); + } +} + +SoundBank* AllocateBank() { + int idx = 0; + // find a bank with unk1 = 0, or return nullptr if none exists + while (true) { + if (idx >= N_BANKS) { + return nullptr; + } + + if (gBanks[idx]->unk1 == 0) { + break; + } + idx++; + } + + if (idx == 0) { + gBanks[0]->unk2 = 0x3fe; // ?? + } else { + gBanks[idx]->unk2 = 0x65; // ?? + } + + return gBanks[idx]; +} + +// name should be a 16-byte "sound name" +SoundBank* LookupBank(const char* name) { + int idx = N_BANKS - 1; + while (true) { + if (idx < 0) { + return nullptr; // not found. + } + auto& bank = gBanks[idx]; + // they had some weird stuff here that took advantage of the fact that this region was + // 16-byte aligned, so it probably wasn't a memcmp, but this is easier. + if (memcmp(bank->name, name, 16) == 0) { + return bank; + } + idx--; + } +} \ No newline at end of file diff --git a/game/overlord/sbank.h b/game/overlord/sbank.h index 557a8ba76..10dca1ea7 100644 --- a/game/overlord/sbank.h +++ b/game/overlord/sbank.h @@ -1,8 +1,16 @@ #pragma once -#ifndef JAK_V2_SBANK_H -#define JAK_V2_SBANK_H +#include "common/common_types.h" + +struct SoundBank { + char name[16]; + u32 unk1; // maybe status? + u32 unk2; // maybe size +}; + +void sbank_init_globals(); void InitBanks(); -#endif // JAK_V2_SBANK_H +SoundBank* AllocateBank(); +SoundBank* LookupBank(const char* name); \ No newline at end of file diff --git a/game/overlord/srpc.cpp b/game/overlord/srpc.cpp index d7796cd38..17bcbfedc 100644 --- a/game/overlord/srpc.cpp +++ b/game/overlord/srpc.cpp @@ -1,8 +1,83 @@ #include +#include +#include "common/util/assert.h" #include "srpc.h" +#include "game/sce/iop.h" +#include "game/common/loader_rpc_types.h" +#include "common/versions.h" +#include "sbank.h" +#include "iso_api.h" + +using namespace iop; MusicTweaks gMusicTweakInfo; +constexpr int SRPC_MESSAGE_SIZE = 0x50; +uint8_t gLoaderBuf[SRPC_MESSAGE_SIZE]; +int32_t gSoundEnable = 1; +u32 gInfoEE = 0; // EE address where we should send info on each frame. void srpc_init_globals() { memset((void*)&gMusicTweakInfo, 0, sizeof(gMusicTweakInfo)); + memset((void*)gLoaderBuf, 0, sizeof(gLoaderBuf)); + gSoundEnable = 1; + gInfoEE = 0; } + +// todo Thread_Player + +void* RPC_Loader(unsigned int fno, void* data, int size); + +u32 Thread_Loader() { + sceSifQueueData dq; + sceSifServeData serve; + + // set up RPC + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, LOADER_RPC_ID, RPC_Loader, gLoaderBuf, nullptr, nullptr, &dq); + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} + +// todo RPC_Player + +void* RPC_Loader(unsigned int /*fno*/, void* data, int size) { + int n_messages = size / SRPC_MESSAGE_SIZE; + SoundRpcCommand* cmd = (SoundRpcCommand*)(data); + if (gSoundEnable) { + // I don't think it should be possible to have > 1 message here - the buffer isn't big enough. + if (n_messages > 1) { + assert(false); + } + while (n_messages > 0) { + switch (cmd->command) { + case SoundCommand::LOAD_BANK: { + // see if it's already loaded + auto bank = LookupBank(cmd->load_bank.bank_name); + if (!bank) { + // see if we have room to load another + auto new_bank = AllocateBank(); + if (new_bank) { + // we do! + LoadSoundBank(cmd->load_bank.bank_name, new_bank); + } + } + } break; + case SoundCommand::GET_IRX_VERSION: { + cmd->irx_version.major = IRX_VERSION_MAJOR; + cmd->irx_version.minor = IRX_VERSION_MINOR; + gInfoEE = cmd->irx_version.ee_addr; + return cmd; + } break; + default: + printf("Unhandled RPC Loader command %d\n", (int)cmd->command); + assert(false); + } + n_messages--; + cmd++; + } + } + return nullptr; +} \ No newline at end of file diff --git a/game/overlord/srpc.h b/game/overlord/srpc.h index fbff2a360..a6dd369bc 100644 --- a/game/overlord/srpc.h +++ b/game/overlord/srpc.h @@ -1,8 +1,5 @@ #pragma once -#ifndef JAK_V2_SRPC_H -#define JAK_V2_SRPC_H - #include "common/common_types.h" void srpc_init_globals(); @@ -18,6 +15,52 @@ struct MusicTweaks { } MusicTweak[MUSIC_TWEAK_COUNT]; }; +enum class SoundCommand : u16 { + LOAD_BANK = 0, + LOAD_MUSIC = 1, + UNLOAD_BANK = 2, + PLAY = 3, + PAUSE_SOUND = 4, + STOP_SOUND = 5, + CONTINUE_SOUND = 6, + SET_PARAM = 7, + SET_MASTER_VOLUME = 8, + PAUSE_GROUP = 9, + STOP_GROUP = 10, + CONTINUE_GROUP = 11, + GET_IRX_VERSION = 12, + SET_FALLOFF_CURVE = 13, + SET_SOUND_FALLOFF = 14, + RELOAD_INFO = 15, + SET_LANGUAGE = 16, + SET_FLAVA = 17, + SET_REVERB = 18, + SET_EAR_TRANS = 19, + SHUTDOWN = 20, + LIST_SOUNDS = 21, + UNLOAD_MUSIC = 22 +}; + +struct SoundRpcGetIrxVersion { + u32 major; + u32 minor; + u32 ee_addr; +}; + +struct SoundRpcBankCommand { + u8 pad[12]; + char bank_name[16]; +}; + +struct SoundRpcCommand { + u16 rsvd1; + SoundCommand command; + union { + SoundRpcGetIrxVersion irx_version; + SoundRpcBankCommand load_bank; + }; +}; + extern MusicTweaks gMusicTweakInfo; -#endif // JAK_V2_SRPC_H +u32 Thread_Loader(); \ No newline at end of file diff --git a/game/runtime.cpp b/game/runtime.cpp index 264711b43..a9d23c911 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -46,6 +46,7 @@ #include "game/overlord/overlord.h" #include "game/overlord/srpc.h" #include "game/overlord/stream.h" +#include "game/overlord/sbank.h" #include "game/graphics/opengl.h" #include "game/graphics/gfx.h" @@ -191,7 +192,7 @@ void iop_runner(SystemThreadInterface& iface) { // isocommon // overlord ramdisk_init_globals(); - // sbank + sbank_init_globals(); // soundcommon srpc_init_globals(); // ssound diff --git a/game/system/IOP_Kernel.cpp b/game/system/IOP_Kernel.cpp index 521b2fd30..b44759f33 100644 --- a/game/system/IOP_Kernel.cpp +++ b/game/system/IOP_Kernel.cpp @@ -210,6 +210,9 @@ void IOP_Kernel::sif_rpc(s32 rpcChannel, rec = &e; } } + if (!rec) { + printf("Failed to find handler for sif channel 0x%x\n", rpcChannel); + } assert(rec); // step 2 - check entry is safe to give command to diff --git a/goal_src/engine/load/load-dgo.gc b/goal_src/engine/load/load-dgo.gc index 634cb4e6b..8ea968e99 100644 --- a/goal_src/engine/load/load-dgo.gc +++ b/goal_src/engine/load/load-dgo.gc @@ -104,9 +104,9 @@ struct DgoHeader { (define-extern *load-dgo-rpc* rpc-buffer-pair) (when (= 0 (the int *load-dgo-rpc*)) ;; we need to allocate the rpc buffers - (set! *load-dgo-rpc* (new 'global 'rpc-buffer-pair (the uint 32) (the uint 1) 3)) ;; todo, constants - (define *load-str-rpc* (new 'global 'rpc-buffer-pair (the uint 64) (the uint 1) 4)) ;; todo, constants - (define *play-str-rpc* (new 'global 'rpc-buffer-pair (the uint 64) (the uint 2) 5)) + (set! *load-dgo-rpc* (new 'global 'rpc-buffer-pair (the uint 32) (the uint 1) RPC-DGO)) + (define *load-str-rpc* (new 'global 'rpc-buffer-pair (the uint 64) (the uint 1) RPC-LOAD-STR)) + (define *play-str-rpc* (new 'global 'rpc-buffer-pair (the uint 64) (the uint 2) RPC-PLAY-STR)) ;; we have separate locks for queuing and loading. (define *load-str-lock* '#f) diff --git a/goal_src/engine/load/ramdisk.gc b/goal_src/engine/load/ramdisk.gc index aa5d65284..4d4493550 100644 --- a/goal_src/engine/load/ramdisk.gc +++ b/goal_src/engine/load/ramdisk.gc @@ -49,7 +49,7 @@ ) ;; allocate the ramdisk RPC buffer -(define *ramdisk-rpc* (new 'global 'rpc-buffer-pair (the-as uint 32) (the-as uint 1) 2)) +(define *ramdisk-rpc* (new 'global 'rpc-buffer-pair (the-as uint 32) (the-as uint 1) RPC-RAMDISK)) (define *current-ramdisk-id* 0) diff --git a/goal_src/engine/ps2/rpc-h.gc b/goal_src/engine/ps2/rpc-h.gc index dd07b2ef6..32e90d5d4 100644 --- a/goal_src/engine/ps2/rpc-h.gc +++ b/goal_src/engine/ps2/rpc-h.gc @@ -5,6 +5,15 @@ ;; name in dgo: rpc-h ;; dgos: GAME, ENGINE +;; RPC channels. +;; these should match XXX_RPC_CHANNEL in the game/common/xxx_rpc_types.h +(defconstant RPC-SOUND-PLAYER 0) ;; called player in IOP code +(defconstant RPC-SOUND-LOADER 1) ;; called loader in IOP code +(defconstant RPC-RAMDISK 2) ;; called server in IOP code, sometimes +(defconstant RPC-DGO 3) +(defconstant RPC-LOAD-STR 4) ;; called STR in IOP code +(defconstant RPC-PLAY-STR 5) ;; called PLAY in IOP code + ;; an RPC buffer is a container of elements to send to the IOP. ;; each element is size elt-size, and there are maximum of elt-count elements ;; it is possible to use fewer elements than elt-count. @@ -136,7 +145,7 @@ "Call an RPC. This is an async RPC. Use check-busy or sync to see if it's done." (when (!= 0 (-> obj current elt-used)) ;; when we have used elements - (format 0 "call rpc-buffer-pair with ~D elts~%" (-> obj current elt-used)) + ;; (format 0 "call rpc-buffer-pair with ~D elts~%" (-> obj current elt-used)) ;; make sure the previous buffer is done (let ((active-buffer (if (= (-> obj buffer 0) (-> obj current)) @@ -145,25 +154,21 @@ (when (-> active-buffer busy) ;; we think the active buffer may be busy. ;; first lets just do a simple check - (cond - ((!= 0 (rpc-busy? (-> obj rpc-port))) - ;; busy! print an error and stall! - (format 0 "STALL: waiting for IOP on RPC port #~D~%" (-> obj rpc-port)) - (while (!= 0 (rpc-busy? (-> obj rpc-port))) - (+ 1 2 3) - ) - ) - (else - ;; not busy. - (set! (-> active-buffer busy) '#f) - (set! (-> active-buffer elt-used) 0) + (when (!= 0 (rpc-busy? (-> obj rpc-port))) + ;; busy! print an error and stall! + (format 0 "STALL: waiting for IOP on RPC port #~D~%" (-> obj rpc-port)) + (while (!= 0 (rpc-busy? (-> obj rpc-port))) + (+ 1 2 3) ) ) + ;; not busy. + (set! (-> active-buffer busy) '#f) + (set! (-> active-buffer elt-used) 0) ) ;; now we've cleared the last RPC call, we can do another (let ((current-buffer (-> obj current))) ;; rpc_channel, fno, async, send_buff, send_size, recv_buff, recv_size - (format 0 "recv-size is ~D~%" recv-size) + ;; (format 0 "recv-size is ~D~%" recv-size) (rpc-call (-> obj rpc-port) fno (the uint 1) @@ -225,4 +230,4 @@ (-! (-> obj current elt-used) 1) ) 0 - ) \ No newline at end of file + ) diff --git a/goal_src/engine/sound/gsound-h.gc b/goal_src/engine/sound/gsound-h.gc index 899914ed0..cddd37b33 100644 --- a/goal_src/engine/sound/gsound-h.gc +++ b/goal_src/engine/sound/gsound-h.gc @@ -31,6 +31,27 @@ :flag-assert #x900000010 ) +(defmacro static-sound-name (str) + "Convert a string constant to a static sound-name." + + ;; all this is done at compile-time so we can come up with 2 + ;; 64-bit constants to use + (when (> (string-length str) 16) + (error "static-sound-name got a string that is too long") + ) + (let ((lo-val 0) + (hi-val 0) + ) + (dotimes (i (string-length str)) + (if (>= i 8) + (+! hi-val (ash (string-ref str i) (* 8 (- i 8)))) + (+! lo-val (ash (string-ref str i) (* 8 i))) + ) + ) + `(new 'static 'sound-name :lo ,lo-val :hi ,hi-val) + ) + ) + (defenum sound-command :type uint16 (load-bank) diff --git a/goal_src/engine/sound/gsound.gc b/goal_src/engine/sound/gsound.gc index 1e61c1d1f..1146fbb8e 100644 --- a/goal_src/engine/sound/gsound.gc +++ b/goal_src/engine/sound/gsound.gc @@ -6,8 +6,8 @@ ;; dgos: GAME, ENGINE -(define *sound-player-rpc* (new 'global 'rpc-buffer-pair (the-as uint 80) (the-as uint 128) 0)) -(define *sound-loader-rpc* (new 'global 'rpc-buffer-pair (the-as uint 80) (the-as uint 1) 1)) +(define *sound-player-rpc* (new 'global 'rpc-buffer-pair (the-as uint 80) (the-as uint 128) RPC-SOUND-PLAYER)) +(define *sound-loader-rpc* (new 'global 'rpc-buffer-pair (the-as uint 80) (the-as uint 1) RPC-SOUND-LOADER)) (defun sound-name= ((arg0 sound-name) (arg1 sound-name)) "Return #t if both are the same name" @@ -101,8 +101,7 @@ 0 ) -;; TODO rpc 1 unimplemented -;(check-irx-version) +(check-irx-version) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -146,10 +145,10 @@ 0 ) -;; TODO rpc 1 unimplemented -;(sound-bank-load (new 'static 'sound-name :lo #x6e6f6d6d6f63 :hi 0)) ;; common -;(sound-bank-load (new 'static 'sound-name :lo #x317974706d65 :hi 0)) ;; empty1 -;(sound-bank-load (new 'static 'sound-name :lo #x327974706d65 :hi 0)) ;; empty2 +(sound-bank-load (static-sound-name "common")) +(sound-bank-load (static-sound-name "empty1")) +(sound-bank-load (static-sound-name "empty2")) + (define *sound-bank-1* 'empty1) (define *sound-bank-2* 'empty2) @@ -302,22 +301,20 @@ (defun string->sound-name ((str string)) (let ((snd-name (new 'stack-no-clear 'qword))) - (set! (-> snd-name quad) (the-as uint128 0)) - (let ((out-ptr (the-as (pointer uint8) snd-name)) - (in-ptr (-> str data)) - ) - (while - (and - (nonzero? (-> in-ptr 0)) - (< (&- in-ptr (the-as uint (-> str data))) 15) + (set! (-> snd-name quad) (the-as uint128 0)) + (let ((out-ptr (the-as (pointer uint8) snd-name)) + (in-ptr (-> str data)) + ) + (while (and (nonzero? (-> in-ptr 0)) + (< (&- in-ptr (the-as uint (-> str data))) 15) + ) + (set! (-> out-ptr 0) (-> in-ptr 0)) + (set! out-ptr (&-> out-ptr 1)) + (set! in-ptr (&-> in-ptr 1)) + ) ) - (set! (-> out-ptr 0) (-> in-ptr 0)) - (set! out-ptr (&-> out-ptr 1)) - (set! in-ptr (&-> in-ptr 1)) - ) + (the-as sound-name (-> snd-name quad)) ) - (the-as sound-name (-> snd-name quad)) - ) ) (defun sound-set-volume ((group uint) (volume float)) diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index bed6ccf9e..b4ff571e7 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -22,7 +22,10 @@ (defmacro md (file &rest path) "Make Debug: make + print disassembly for a file" - `(asm-file ,file :color :write :disassemble ,(first path)) + (if (null? path) + `(asm-file ,file :color :write :disassemble) + `(asm-file ,file :color :write :disassemble ,(first path)) + ) ) (defmacro ml (file) diff --git a/goal_src/goos-lib.gs b/goal_src/goos-lib.gs index 6fb5cc916..4379caf48 100644 --- a/goal_src/goos-lib.gs +++ b/goal_src/goos-lib.gs @@ -23,6 +23,10 @@ `(cond (,clause ,true) (#t ,false)) ) +(defsmacro when (clause &rest body) + `(if ,clause (begin ,@body) #f) + ) + (defsmacro not (x) `(if ,x #f #t) ) @@ -34,6 +38,14 @@ ) ) +(defsmacro max (a b) + `(if (> a b) a b) + ) + +(defsmacro min (a b) + `(if (< a b) a b) + ) + (defsmacro caar (x) `(car (car ,x))) @@ -96,6 +108,16 @@ ) ) +(defsmacro dotimes (var &rest body) + `(let (( ,(first var) 0)) + (while (< ,(first var) ,(second var)) + ,@body + (set! ,(first var) (+ ,(first var) 1)) + ) + ,@(cddr var) + ) + ) + (desfun repeated-list (obj count) (if (= 0 count) '() @@ -169,5 +191,13 @@ `(seval (desfun ,name ,args ,@body)) ) + + + +;; shortcut to quit GOOS +(defsmacro e () + `(exit) + ) + ;; this is checked in a test to see if this file is loaded. (define __goos-lib-loaded__ #t) diff --git a/test/decompiler/test_FormExpressionBuild2.cpp b/test/decompiler/test_FormExpressionBuild2.cpp index 753eabc5a..7fc8811db 100644 --- a/test/decompiler/test_FormExpressionBuild2.cpp +++ b/test/decompiler/test_FormExpressionBuild2.cpp @@ -1174,6 +1174,83 @@ TEST_F(FormRegressionTest, Method4ResTag) { test_with_expr(func, type, expected); } +TEST_F(FormRegressionTest, MakeSqrtTable) { + std::string func = + "sll r0, r0, 0\n" + "L149:\n" + " daddiu sp, sp, -32\n" + " sd ra, 0(sp)\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " sq gp, 16(sp)\n" + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L190\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + " addiu gp, r0, 0\n" + " beq r0, r0, L151\n" + " sll r0, r0, 0\n" + "L150:\n" + //" lwc1 f0, L232(fp)\n" + " mtc1 f0, r0\n" + " mtc1 f1, gp\n" + " cvt.s.w f1, f1\n" + " mul.s f0, f0, f1\n" + " sqrt.s f0, f0\n" + //" lwc1 f1, L233(fp)\n" + " mtc1 f1, r0\n" + " add.s f0, f1, f0\n" + " cvt.w.s f0, f0\n" + " mfc1 a2, f0\n" + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L189\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + " or v1, v0, r0\n" + " daddiu gp, gp, 1\n" + "L151:\n" + " slti v1, gp, 256\n" + " bne v1, r0, L150\n" + " sll r0, r0, 0\n" + + " or v1, s7, r0\n" + " or v1, s7, r0\n" + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L188\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + " or v0, r0, r0\n" + " ld ra, 0(sp)\n" + " ld fp, 8(sp)\n" + " lq gp, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 32\n" + " sll r0, r0, 0\n" + " sll r0, r0, 0"; + std::string type = "(function none)"; + std::string expected = + "(begin\n" + " (format #t \"static int sqrt_table[256] =~%{~%\")\n" + " (dotimes (gp-0 256)\n" + " (let* ((f0-2 (sqrtf (* 0.0 (the float gp-0))))\n" + " (a2-0 (the int (+ 0.0 f0-2)))\n" + " )\n" + " (format #t \"~D,~%\" a2-0)\n" + " )\n" + " )\n" + " (format #t \"};~%\")\n" + " (let ((v0-3 0))\n" + " )\n" + " (none)\n" + " )"; + test_with_expr( + func, type, expected, false, "", + {{"L190", "static int sqrt_table[256] =~%{~%"}, {"L189", "~D,~%"}, {"L188", "};~%"}}); +} + TEST_F(FormRegressionTest, Method2Vec4s) { std::string func = "sll r0, r0, 0\n" @@ -1218,4 +1295,25 @@ TEST_F(FormRegressionTest, Method2Vec4s) { " arg0\n" " )"; test_with_expr(func, type, expected, false, "", {{"L344", "#"}}); +} + +TEST_F(FormRegressionTest, SoundNameEqual) { + std::string func = + "sll r0, r0, 0\n" + " dsubu v1, a0, a1\n" + " daddiu a2, s7, 8\n" + " movn a2, s7, v1\n" + " beql s7, a2, L125\n" + " or v0, a2, r0\n" + " pcpyud v1, a0, r0\n" + " pcpyud a0, a1, r0\n" + " dsubu v1, v1, a0\n" + " daddiu v0, s7, 8\n" + " movn v0, s7, v1\n" + "L125:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function sound-name sound-name symbol)"; + std::string expected = "(and (= arg0 arg1) (= (-> arg0 hi) (-> arg1 hi)))"; + test_with_expr(func, type, expected); } \ No newline at end of file diff --git a/test/goalc/source_templates/with_game/test-sound-name.gc b/test/goalc/source_templates/with_game/test-sound-name.gc new file mode 100644 index 000000000..e7e6ced21 --- /dev/null +++ b/test/goalc/source_templates/with_game/test-sound-name.gc @@ -0,0 +1,12 @@ + +(let ((sound-name-1 (static-sound-name "asdfghjkzxcvn,.")) + (sound-name-2 (string->sound-name "asdfghjkzxcvn,.")) + (sound-name-3 (string->sound-name "asdfghjkzxcvnn,.")) + (sound-name-4 (string->sound-name "asddghjkzxcvnm,.")) + ) + (format #t "~A ~A ~A~%" + (sound-name= sound-name-1 sound-name-2) + (sound-name= sound-name-1 sound-name-3) + (sound-name= sound-name-1 sound-name-4) + ) + ) diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index 311aa430c..08d251367 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -753,6 +753,12 @@ TEST_F(WithGameTests, AddrOfVar) { "0\n"}); } +TEST_F(WithGameTests, SoundName) { + runner.run_static_test(env, testCategory, "test-sound-name.gc", + {"#t #f #f\n" + "0\n"}); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines();