[decompiler] bitfield support for sound-name (#582)

* fix 64-bit fields in 128-bit bitfields

* support sound-name

* fix merge

* support some more sound stuff in overlord
This commit is contained in:
water111 2021-06-12 12:55:38 -04:00 committed by GitHub
parent 877e3d161c
commit 8faded6400
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 718 additions and 139 deletions

View file

@ -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<EnvironmentObject>& 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<EnvironmentObject>& 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<EnvironmentObject>& 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

View file

@ -186,6 +186,15 @@ class Interpreter {
Object eval_error(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_string_ref(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_string_length(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
Object eval_ash(const Object& form,
Arguments& args,
const std::shared_ptr<EnvironmentObject>& env);
// specials
Object eval_define(const Object& form,

View file

@ -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;

View file

@ -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<BitfieldAccessElement>(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 {

View file

@ -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<bool> looking_for_unsigned) {
std::optional<BitField> try_find_field(const TypeSystem& ts,
const BitFieldType* type,
int start_bit,
int size,
std::optional<bool> 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<bool> 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<BitFieldDef> 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<BitFieldDef> 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<BitFieldDef> 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<BitFieldDef> 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<BitFieldDef> 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<BitFieldType*>(type);
assert(as_bitfield);
auto field = try_find_field(ts, as_bitfield, 64, 64, true);
if (field) {
auto result =
pool.alloc_element<DerefElement>(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<ConstantTokenElement>(
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<CastElement>(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()) {

View file

@ -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<BitfieldManip> m_steps;
};
@ -190,9 +194,9 @@ Form* cast_to_bitfield_enum(const EnumType* type_info, FormPool& pool, const Env
std::optional<u64> 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<bool> looking_for_unsigned);
BitField find_field(const TypeSystem& ts,
const BitFieldType* type,
int start_bit,
int size,
std::optional<bool> looking_for_unsigned);
} // namespace decompiler

View file

@ -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.
- 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`.

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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<Function> make_function_from_c_linux(void* func) {
// allocate a function object on the global heap
auto mem = Ptr<u8>(
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<Function>();

View file

@ -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);
}

View file

@ -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

View file

@ -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.
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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)();
};

View file

@ -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;
}

View file

@ -1,3 +1,63 @@
#include <cstring>
#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, "<unused>");
}
}
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--;
}
}

View file

@ -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);

View file

@ -1,8 +1,83 @@
#include <cstring>
#include <cstdio>
#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;
}

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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
)
)

View file

@ -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)

View file

@ -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))

View file

@ -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)

View file

@ -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)

View file

@ -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", "#<vector ~F ~F ~F ~F @ #x~X>"}});
}
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);
}

View file

@ -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)
)
)

View file

@ -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();