From e81431bd21866cb0d98613e2d3dc1659b23d4e95 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:42:28 -0400 Subject: [PATCH] [wip] Jak 3 Overlord (#3567) --- common/repl/repl_wrapper.h | 7 +- common/util/json_util.h | 6 +- decompiler/ObjectFile/LinkedObjectFile.h | 2 +- .../ntsc_v1/anonymous_function_types.jsonc | 2 +- game/CMakeLists.txt | 23 + game/common/dgo_rpc_types.h | 16 - .../graphics/opengl_renderer/BucketRenderer.h | 4 +- .../opengl_renderer/background/Hfrag.cpp | 4 +- game/kernel/common/klink.h | 1 - game/kernel/jak2/kdgo.h | 1 - game/kernel/jak3/kdgo.cpp | 9 +- game/overlord/jak2/vag.cpp | 2 +- game/overlord/jak2/vag.h | 2 +- game/overlord/jak3/basefile.cpp | 333 +++ game/overlord/jak3/basefile.h | 76 + game/overlord/jak3/basefilesystem.cpp | 26 + game/overlord/jak3/basefilesystem.h | 33 + game/overlord/jak3/dma.cpp | 526 +++++ game/overlord/jak3/dma.h | 32 + game/overlord/jak3/dvd_driver.cpp | 582 ++++++ game/overlord/jak3/dvd_driver.h | 144 ++ game/overlord/jak3/init.cpp | 50 + game/overlord/jak3/init.h | 5 + game/overlord/jak3/iso.cpp | 1796 +++++++++++++++++ game/overlord/jak3/iso.h | 92 + game/overlord/jak3/iso_api.cpp | 229 +++ game/overlord/jak3/iso_api.h | 19 + game/overlord/jak3/iso_cd.cpp | 521 +++++ game/overlord/jak3/iso_cd.h | 58 + game/overlord/jak3/iso_queue.cpp | 732 +++++++ game/overlord/jak3/iso_queue.h | 30 + game/overlord/jak3/isocommon.cpp | 118 ++ game/overlord/jak3/isocommon.h | 152 ++ game/overlord/jak3/list.cpp | 54 + game/overlord/jak3/list.h | 27 + game/overlord/jak3/overlord.cpp | 151 ++ game/overlord/jak3/overlord.h | 61 + game/overlord/jak3/pagemanager.cpp | 852 ++++++++ game/overlord/jak3/pagemanager.h | 181 ++ game/overlord/jak3/ramdisk.cpp | 50 + game/overlord/jak3/ramdisk.h | 10 + game/overlord/jak3/rpc_interface.h | 280 +++ game/overlord/jak3/sbank.cpp | 167 ++ game/overlord/jak3/sbank.h | 29 + game/overlord/jak3/soundcommon.cpp | 19 + game/overlord/jak3/soundcommon.h | 7 + game/overlord/jak3/spustreams.cpp | 1066 ++++++++++ game/overlord/jak3/spustreams.h | 14 + game/overlord/jak3/srpc.cpp | 604 ++++++ game/overlord/jak3/srpc.h | 16 + game/overlord/jak3/ssound.cpp | 995 +++++++++ game/overlord/jak3/ssound.h | 50 + game/overlord/jak3/stream.cpp | 282 +++ game/overlord/jak3/stream.h | 13 + game/overlord/jak3/streamlist.cpp | 339 ++++ game/overlord/jak3/streamlist.h | 22 + game/overlord/jak3/todo.txt | 6 + game/overlord/jak3/vag.cpp | 1042 ++++++++++ game/overlord/jak3/vag.h | 173 ++ game/overlord/jak3/vblank_handler.cpp | 201 ++ game/overlord/jak3/vblank_handler.h | 12 + game/runtime.cpp | 54 +- game/sce/iop.cpp | 36 +- game/sce/iop.h | 24 +- game/sound/989snd/blocksound_handler.cpp | 5 +- game/sound/989snd/blocksound_handler.h | 6 +- game/sound/989snd/player.cpp | 8 + game/sound/989snd/player.h | 1 + game/sound/989snd/sfxblock.cpp | 2 +- game/sound/989snd/sound_handler.h | 1 + game/sound/sdshim.cpp | 9 +- game/sound/sdshim.h | 5 +- game/sound/sndshim.cpp | 28 + game/sound/sndshim.h | 6 + game/system/IOP_Kernel.cpp | 125 ++ game/system/IOP_Kernel.h | 86 +- game/system/iop_thread.cpp | 3 + goal_src/jak3/engine/scene/scene.gc | 8 - goal_src/jak3/game.gp | 6 +- .../reference/jak3/engine/scene/scene_REF.gc | 21 +- 80 files changed, 12658 insertions(+), 132 deletions(-) create mode 100644 game/overlord/jak3/basefile.cpp create mode 100644 game/overlord/jak3/basefile.h create mode 100644 game/overlord/jak3/basefilesystem.cpp create mode 100644 game/overlord/jak3/basefilesystem.h create mode 100644 game/overlord/jak3/dma.cpp create mode 100644 game/overlord/jak3/dma.h create mode 100644 game/overlord/jak3/dvd_driver.cpp create mode 100644 game/overlord/jak3/dvd_driver.h create mode 100644 game/overlord/jak3/init.cpp create mode 100644 game/overlord/jak3/init.h create mode 100644 game/overlord/jak3/iso.cpp create mode 100644 game/overlord/jak3/iso.h create mode 100644 game/overlord/jak3/iso_api.cpp create mode 100644 game/overlord/jak3/iso_api.h create mode 100644 game/overlord/jak3/iso_cd.cpp create mode 100644 game/overlord/jak3/iso_cd.h create mode 100644 game/overlord/jak3/iso_queue.cpp create mode 100644 game/overlord/jak3/iso_queue.h create mode 100644 game/overlord/jak3/isocommon.cpp create mode 100644 game/overlord/jak3/isocommon.h create mode 100644 game/overlord/jak3/list.cpp create mode 100644 game/overlord/jak3/list.h create mode 100644 game/overlord/jak3/overlord.cpp create mode 100644 game/overlord/jak3/overlord.h create mode 100644 game/overlord/jak3/pagemanager.cpp create mode 100644 game/overlord/jak3/pagemanager.h create mode 100644 game/overlord/jak3/ramdisk.cpp create mode 100644 game/overlord/jak3/ramdisk.h create mode 100644 game/overlord/jak3/rpc_interface.h create mode 100644 game/overlord/jak3/sbank.cpp create mode 100644 game/overlord/jak3/sbank.h create mode 100644 game/overlord/jak3/soundcommon.cpp create mode 100644 game/overlord/jak3/soundcommon.h create mode 100644 game/overlord/jak3/spustreams.cpp create mode 100644 game/overlord/jak3/spustreams.h create mode 100644 game/overlord/jak3/srpc.cpp create mode 100644 game/overlord/jak3/srpc.h create mode 100644 game/overlord/jak3/ssound.cpp create mode 100644 game/overlord/jak3/ssound.h create mode 100644 game/overlord/jak3/stream.cpp create mode 100644 game/overlord/jak3/stream.h create mode 100644 game/overlord/jak3/streamlist.cpp create mode 100644 game/overlord/jak3/streamlist.h create mode 100644 game/overlord/jak3/todo.txt create mode 100644 game/overlord/jak3/vag.cpp create mode 100644 game/overlord/jak3/vag.h create mode 100644 game/overlord/jak3/vblank_handler.cpp create mode 100644 game/overlord/jak3/vblank_handler.h diff --git a/common/repl/repl_wrapper.h b/common/repl/repl_wrapper.h index 6ba81ad87a..b5d3b3880f 100644 --- a/common/repl/repl_wrapper.h +++ b/common/repl/repl_wrapper.h @@ -29,8 +29,11 @@ class Wrapper { Wrapper(const std::string& _username, const Config& config, const StartupFile& startup, - bool nrepl_alive) - : username(_username), repl_config(config), startup_file(startup), nrepl_alive(nrepl_alive) {} + bool _nrepl_alive) + : username(_username), + repl_config(config), + startup_file(startup), + nrepl_alive(_nrepl_alive) {} replxx::Replxx& get_repl() { return repl; } void init_settings(); void reload_startup_file(); diff --git a/common/util/json_util.h b/common/util/json_util.h index 183cccdffe..ef8a9e7137 100644 --- a/common/util/json_util.h +++ b/common/util/json_util.h @@ -27,11 +27,11 @@ Range parse_json_optional_integer_range(const nlohmann::json& json); } template -void json_get_optional(const nlohmann::json& json, +void json_get_optional(const nlohmann::json& j, const std::string& key, std::optional& optionalValue) { - if (json.contains(key) && !json[key].is_null()) { - optionalValue = json[key].get(); + if (j.contains(key) && !j[key].is_null()) { + optionalValue = j[key].get(); } } diff --git a/decompiler/ObjectFile/LinkedObjectFile.h b/decompiler/ObjectFile/LinkedObjectFile.h index 94336888ad..dedefc077f 100644 --- a/decompiler/ObjectFile/LinkedObjectFile.h +++ b/decompiler/ObjectFile/LinkedObjectFile.h @@ -25,7 +25,7 @@ namespace decompiler { */ class LinkedObjectFile { public: - LinkedObjectFile(GameVersion version) : version(version){}; + LinkedObjectFile(GameVersion _version) : version(_version) {} void set_segment_count(int n_segs); void push_back_word_to_segment(uint32_t word, int segment); int get_label_id_for(int seg, int offset); diff --git a/decompiler/config/jak3/ntsc_v1/anonymous_function_types.jsonc b/decompiler/config/jak3/ntsc_v1/anonymous_function_types.jsonc index c6ad452490..a3d99d12e5 100644 --- a/decompiler/config/jak3/ntsc_v1/anonymous_function_types.jsonc +++ b/decompiler/config/jak3/ntsc_v1/anonymous_function_types.jsonc @@ -138,7 +138,7 @@ [7, "(function none)"], [3, "(function symbol :behavior process)"] ], - "scene": [[4, "(function none :behavior scene-player)"]], + "scene": [[4, "(function symbol :behavior scene-player)"]], "pov-camera": [ [ 7, diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 63e301592c..74434b1b7d 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -254,6 +254,29 @@ set(RUNTIME_SOURCE overlord/jak2/streamlfo.cpp overlord/jak2/streamlist.cpp overlord/jak2/vag.cpp + overlord/jak3/overlord.cpp + overlord/jak3/pagemanager.cpp + overlord/jak3/iso_cd.cpp + overlord/jak3/dma.cpp + overlord/jak3/iso.cpp + overlord/jak3/iso_queue.cpp + overlord/jak3/srpc.cpp + overlord/jak3/vag.cpp + overlord/jak3/ssound.cpp + overlord/jak3/iso_api.cpp + overlord/jak3/spustreams.cpp + overlord/jak3/list.cpp + overlord/jak3/vblank_handler.cpp + overlord/jak3/dvd_driver.cpp + overlord/jak3/basefile.cpp + overlord/jak3/basefilesystem.cpp + overlord/jak3/ramdisk.cpp + overlord/jak3/isocommon.cpp + overlord/jak3/init.cpp + overlord/jak3/stream.cpp + overlord/jak3/sbank.cpp + overlord/jak3/soundcommon.cpp + overlord/jak3/streamlist.cpp runtime.cpp sce/deci2.cpp sce/iop.cpp diff --git a/game/common/dgo_rpc_types.h b/game/common/dgo_rpc_types.h index 14e7523e33..3879bc7689 100644 --- a/game/common/dgo_rpc_types.h +++ b/game/common/dgo_rpc_types.h @@ -31,19 +31,3 @@ struct RPC_Dgo_Cmd { // a buffer. uint8_t pad[32]; }; - -namespace jak3 { -struct RPC_Dgo_Cmd { - uint16_t rsvd; - uint16_t result; - uint32_t buffer1; - uint32_t buffer2; - uint32_t buffer_heap_top; - char name[16]; - uint16_t cgo_id; - uint8_t pad[30]; -}; -static_assert(sizeof(RPC_Dgo_Cmd) == 0x40); -} // namespace jak3 - -static_assert(sizeof(RPC_Dgo_Cmd) == sizeof(jak3::RPC_Dgo_Cmd)); diff --git a/game/graphics/opengl_renderer/BucketRenderer.h b/game/graphics/opengl_renderer/BucketRenderer.h index 05ec7cf726..a6c654247a 100644 --- a/game/graphics/opengl_renderer/BucketRenderer.h +++ b/game/graphics/opengl_renderer/BucketRenderer.h @@ -26,8 +26,8 @@ class EyeRenderer; struct SharedRenderState { explicit SharedRenderState(std::shared_ptr _texture_pool, std::shared_ptr _loader, - GameVersion version) - : shaders(version), texture_pool(_texture_pool), loader(_loader) {} + GameVersion _version) + : shaders(_version), texture_pool(_texture_pool), loader(_loader), version(_version) {} ShaderLibrary shaders; std::shared_ptr texture_pool; std::shared_ptr loader; diff --git a/game/graphics/opengl_renderer/background/Hfrag.cpp b/game/graphics/opengl_renderer/background/Hfrag.cpp index cd3cc68625..3fb68a2931 100644 --- a/game/graphics/opengl_renderer/background/Hfrag.cpp +++ b/game/graphics/opengl_renderer/background/Hfrag.cpp @@ -129,8 +129,8 @@ Hfrag::HfragLevel* Hfrag::get_hfrag_level(const std::string& name, // first, see if this level is loaded. If not, there's nothing we can do. auto lev_data = render_state->loader->get_tfrag3_level(name); if (!lev_data) { - printf("[hfrag] can't display %s because it's not loaded.\n", name.c_str()); - render_state->loader->debug_print_loaded_levels(); + // printf("[hfrag] can't display %s because it's not loaded.\n", name.c_str()); + // render_state->loader->debug_print_loaded_levels(); return nullptr; } diff --git a/game/kernel/common/klink.h b/game/kernel/common/klink.h index b97ed1603a..a2fb9610a6 100644 --- a/game/kernel/common/klink.h +++ b/game/kernel/common/klink.h @@ -125,7 +125,6 @@ struct link_control { } }; -void klink_init_globals(); Ptr c_symlink2(Ptr objData, Ptr linkObj, Ptr relocTable); extern link_control saved_link_control; diff --git a/game/kernel/jak2/kdgo.h b/game/kernel/jak2/kdgo.h index 54904a1c46..44a77d6b8e 100644 --- a/game/kernel/jak2/kdgo.h +++ b/game/kernel/jak2/kdgo.h @@ -16,7 +16,6 @@ void load_and_link_dgo_from_c_fast(const char* name, Ptr heap, u32 linkFlag, s32 bufferSize); -void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size); void kdgo_init_globals(); extern RPC_Dgo_Cmd sMsg[2]; extern RPC_Dgo_Cmd* sLastMsg; diff --git a/game/kernel/jak3/kdgo.cpp b/game/kernel/jak3/kdgo.cpp index 23711ac74a..793f68da53 100644 --- a/game/kernel/jak3/kdgo.cpp +++ b/game/kernel/jak3/kdgo.cpp @@ -8,6 +8,7 @@ #include "game/kernel/common/kdgo.h" #include "game/kernel/common/kmalloc.h" #include "game/kernel/jak3/klink.h" +#include "game/overlord/jak3/rpc_interface.h" namespace jak3 { @@ -39,7 +40,7 @@ void BeginLoadingDGO(const char* name, Ptr buffer1, Ptr buffer2, Ptr RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished // put a dummy value here just to make sure the IOP overwrites it. - sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666 + sMsg[msgID].status = DGO_RPC_RESULT_INIT; // !! this is 666 // inform IOP of buffers sMsg[msgID].buffer1 = buffer1.offset; @@ -78,13 +79,13 @@ Ptr GetNextDGO(u32* lastObjectFlag) { Ptr buffer(0); if (sLastMsg) { // if we got a good result, get pointer to object - if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) { + if ((sLastMsg->status == DGO_RPC_RESULT_MORE) || (sLastMsg->status == DGO_RPC_RESULT_DONE)) { buffer.offset = sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object. } // not the last one, so don't set the flag. - if (sLastMsg->result == DGO_RPC_RESULT_MORE) { + if (sLastMsg->status == DGO_RPC_RESULT_MORE) { *lastObjectFlag = 0; } @@ -111,7 +112,7 @@ void ContinueLoadingDGO(Ptr b1, Ptr b2, Ptr heapPtr) { u32 msgID = sMsgNum; jak3::RPC_Dgo_Cmd* sendBuff = sMsg + sMsgNum; sMsgNum = sMsgNum ^ 1; - sendBuff->result = DGO_RPC_RESULT_INIT; + sendBuff->status = DGO_RPC_RESULT_INIT; sMsg[msgID].buffer1 = b1.offset; sMsg[msgID].buffer2 = b2.offset; sMsg[msgID].buffer_heap_top = heapPtr.offset; diff --git a/game/overlord/jak2/vag.cpp b/game/overlord/jak2/vag.cpp index 6bc1532f6f..3aed62539b 100644 --- a/game/overlord/jak2/vag.cpp +++ b/game/overlord/jak2/vag.cpp @@ -30,7 +30,7 @@ void StopVAG(VagCmd* cmd, int /*param_2*/); enum VolumeCategory { DIALOGUE = 2, // VAG streams. Copied "dialogue" name from jak 1. }; -int MasterVolume[17]; +int MasterVolume[32]; void vag_init_globals() { memset(VagCmds, 0, sizeof(VagCmds)); diff --git a/game/overlord/jak2/vag.h b/game/overlord/jak2/vag.h index 47b5ae61e1..4c41283457 100644 --- a/game/overlord/jak2/vag.h +++ b/game/overlord/jak2/vag.h @@ -113,7 +113,7 @@ extern int TrapSRAM[N_VAG_CMDS]; extern int StreamVoice[N_VAG_CMDS]; extern int ActiveVagStreams; -extern int MasterVolume[17]; +extern int MasterVolume[32]; void vag_init_globals(); diff --git a/game/overlord/jak3/basefile.cpp b/game/overlord/jak3/basefile.cpp new file mode 100644 index 0000000000..9caa2e7f79 --- /dev/null +++ b/game/overlord/jak3/basefile.cpp @@ -0,0 +1,333 @@ +#include "basefile.h" + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/pagemanager.h" +#include "game/overlord/jak3/vag.h" + +namespace jak3 { +void jak3_overlord_init_globals_basefile() {} + +/*! + * Construct a CBaseFile in an unused state. + */ +CBaseFile::CBaseFile() { + m_Buffer.m_pCurrentData = nullptr; + m_Buffer.m_pCurrentPageStart = nullptr; + m_Buffer.m_nMinNumPages = 1; + m_Buffer.m_nMaxNumPages = kDefaultBufferPageCount; + m_Buffer.m_nDataLength = 0; + m_Buffer.m_pPageList = nullptr; + m_Buffer.m_pIsoCmd = nullptr; + m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE; + + m_ProcessDataSemaphore = -1; + m_FileDef = nullptr; + m_FileKind = Kind::UNKNOWN; + m_Status = EIsoStatus::NONE_0; + m_ReadRate = 0; + m_LengthPages = 0; + m_PageOffset = 0; + m_nNumPages = kDefaultBufferPageCount; +} + +/*! + * Construct a CBaseFile for a given file, but keep it in the "idle" state, with no buffer + * allocated. + */ +CBaseFile::CBaseFile(const jak3::ISOFileDef* file, int semaphore) { + m_Buffer.m_pCurrentData = nullptr; + m_Buffer.m_nMaxNumPages = kDefaultBufferPageCount; + m_Buffer.m_nDataLength = 0; + m_Buffer.m_nMinNumPages = 1; + m_Buffer.m_pPageList = nullptr; + m_Buffer.m_pIsoCmd = nullptr; + m_Buffer.m_pCurrentPageStart = nullptr; + m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE; + + m_nNumPages = kDefaultBufferPageCount; + m_FileDef = file; + m_ProcessDataSemaphore = semaphore; + m_FileKind = Kind::UNKNOWN; + m_Status = EIsoStatus::IDLE_1; + m_ReadRate = 0; + m_LengthPages = 0; + m_PageOffset = 0; +} + +/*! + * Update our buffer to handle crossing page boundaries, return pointer to next data. + */ +uint8_t* CBaseFile::CheckPageBoundary() { + // Can't check page boundary if the buffer is not allocated. + ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE); + + // Can't check page boundary if there is no manager + ASSERT(m_Buffer.m_pPageList); + + CPageList* page_list = m_Buffer.m_pPageList; + CPage* page = page_list->m_pCurrentActivePage; + + // can only return data if there's an active page + if (!page || page_list->m_nNumActivePages <= 0) { + return nullptr; + } + + // buffer doesn't know the current page, just reset it to the start of the active page. + if (m_Buffer.m_pCurrentPageStart == nullptr) { + m_Buffer.m_pCurrentData = page->m_pPageMemStart; + m_Buffer.m_pCurrentPageStart = page->m_pPageMemStart; + } else { + uint8_t* end_ptr = page->m_pPageMemEnd; + // check if our data pointer crossed the page boundary + if (end_ptr <= m_Buffer.m_pCurrentData) { + // it did! + uint8_t* past_boundary_ptr = m_Buffer.m_pCurrentData; + + // get the next active page + CPage* next_page = page_list->StepActivePage(); + if (!next_page) { + // no more active pages, no data to process + m_Buffer.m_pCurrentPageStart = nullptr; + m_Buffer.m_pCurrentData = nullptr; + ovrld_log(LogCategory::PAGING, "File {} ran out of pages in CheckPageBoundary", + m_FileDef->name.data); + } else { + // this is a little weird, but if we went past the end of the previous page, we actually + // start at an offset into the next page - perhaps the user could know that pages are + // consecutive in memory? + uint8_t* new_page_mem = next_page->m_pPageMemStart; + m_Buffer.m_pCurrentData = new_page_mem + (end_ptr + 1 - past_boundary_ptr); + m_Buffer.m_pCurrentPageStart = new_page_mem; + ovrld_log(LogCategory::PAGING, "File {} advanced to next page (wrapped {} bytes)", + m_FileDef->name.data, (end_ptr + 1 - past_boundary_ptr)); + } + } + } + return m_Buffer.m_pCurrentData; +} + +/*! + * Allocate pages and set up the CBuffer for the given ISO Msg and type. + */ +int CBaseFile::InitBuffer(CBuffer::BufferType type, jak3::ISO_Hdr* msg) { + ASSERT(msg); + m_Buffer.m_pCurrentData = nullptr; + m_Buffer.m_pPageList = nullptr; + m_Buffer.m_pIsoCmd = msg; + m_Buffer.m_pCurrentPageStart = nullptr; + m_Buffer.m_nDataLength = 0; + m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE; + m_Buffer.m_nMinNumPages = 1; + m_Buffer.m_nMaxNumPages = kDefaultBufferPageCount; + m_nNumPages = 4; + + // adjust size based on the request buffer type + switch (type) { + case CBuffer::BufferType::REQUEST_NORMAL: { // 3 + m_Buffer.m_pCurrentData = nullptr; + m_Buffer.m_nMinNumPages = 1; + m_Buffer.m_nMaxNumPages = 4; + m_nNumPages = 4; + m_Buffer.m_nDataLength = 0; + m_Buffer.m_pCurrentPageStart = nullptr; + m_Buffer.m_eBufferType = CBuffer::BufferType::NORMAL; // 1 + + // and the iso msg request. + switch (msg->msg_type) { + case ISO_Hdr::MsgType::LOAD_EE: + case ISO_Hdr::MsgType::LOAD_IOP: + case ISO_Hdr::MsgType::LOAD_EE_CHUNK: + m_Buffer.m_nMinNumPages = 1; + m_nNumPages = 4; + m_Buffer.m_nMaxNumPages = 4; + break; + case ISO_Hdr::MsgType::LOAD_SOUNDBANK: + m_Buffer.m_nMinNumPages = 1; + m_Buffer.m_nMaxNumPages = 2; + break; + default: + break; + } + + } break; + case CBuffer::BufferType::REQUEST_VAG: { + m_Buffer.m_pCurrentData = nullptr; + m_Buffer.m_eBufferType = CBuffer::BufferType::VAG; + m_Buffer.m_nMinNumPages = 1; + m_nNumPages = 0x10; + m_Buffer.m_nDataLength = 0; + m_Buffer.m_pCurrentPageStart = nullptr; + m_Buffer.m_nMaxNumPages = 0x10; + } break; + default: + ASSERT_NOT_REACHED(); // bad buffer type + } + + ovrld_log(LogCategory::PAGING, "File {} initializing buffer ({} pages {} min {} max)", + m_FileDef->name.data, m_nNumPages, m_Buffer.m_nMinNumPages, m_Buffer.m_nMaxNumPages); + + // Actual allocation of page data + CPageList* page_list = AllocPages(); + if (page_list) { + // set up the current pointers of the buffer. + m_Buffer.m_pCurrentPageStart = page_list->m_pFirstPage->m_pPageMemStart; + m_Buffer.m_pCurrentData = m_Buffer.m_pCurrentPageStart; + return 1; + } else { + ovrld_log(LogCategory::WARN, "File {} failed to allocate a page list.", m_FileDef->name.data); + // if it failed, terminate the buffer + TerminateBuffer(); + return 0; + } +} + +/*! + * Free page memory, clear event flags for cpages. + */ +void CBaseFile::TerminateBuffer() { + ovrld_log(LogCategory::PAGING, "File {} terminating buffer.", m_FileDef->name.data); + + auto buffer_type = m_Buffer.m_eBufferType; + if (buffer_type != CBuffer::BufferType::EBT_FREE) { + // clean up our pages + auto* page_list = m_Buffer.m_pPageList; + if (page_list) { + // stop try to load + page_list->CancelActivePages(); + } + + switch (buffer_type) { + case CBuffer::BufferType::EBT_FREE: + case CBuffer::BufferType::NORMAL: + case CBuffer::BufferType::VAG: + case CBuffer::BufferType::REQUEST_NORMAL: + case CBuffer::BufferType::REQUEST_VAG: + // return memory + FreePages(); + + // reset our buffer + m_Buffer.m_nDataLength = 0; + m_Buffer.m_pCurrentPageStart = 0; + m_Buffer.m_eBufferType = CBuffer::BufferType::EBT_FREE; + + // reset our command + if (buffer_type != CBuffer::BufferType::EBT_FREE) { + ASSERT(m_Buffer.m_pIsoCmd); + m_Buffer.m_pIsoCmd = nullptr; + } + break; + default: + ASSERT_NOT_REACHED(); // Invalid buffer type + } + } else { + ASSERT_NOT_REACHED(); // Buffer already terminated, shouldn't happen again. + } +} + +/*! + * Allocate CPage and CPageList as needed to reach the target number of pages. + */ +CPageList* CBaseFile::AllocPages() { + // should have picked a buffer type + ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE); + + if (m_Buffer.m_eBufferType == CBuffer::BufferType::EBT_FREE) { + return nullptr; + } + + // We might have a PageList or not + auto* old_plist = m_Buffer.m_pPageList; + int old_unstepped_pages = 0; + bool have_plist = old_plist != nullptr; + CPageList* ret_plist = nullptr; + + // if we do have a pagelist, we'll reuse it and any unstepped pages. + if (have_plist) { + old_unstepped_pages = old_plist->m_nNumUnsteppedPages; + ret_plist = old_plist; + } + + int page_alloc_count = 0; + + // to increase unstepped pages to the target m_nNumPages, we need to allocate this many more. + page_alloc_count = m_nNumPages - old_unstepped_pages; + // lg::warn("page counts in AllocPages: {} {}", m_nNumPages, old_unstepped_pages); + if (old_plist) { + // lg::warn(" {} {}", old_plist->m_nNumPages, old_plist->m_nNumActivePages); + } + + if (old_unstepped_pages < (int)m_nNumPages) { + // alloc count will be positive. + + // reduce allocation count to at most the number of free pages + if (get_page_manager()->m_CCache.m_nNumFreePages < page_alloc_count) { + page_alloc_count = get_page_manager()->m_CCache.m_nNumFreePages; + } + + } else { + // alloc count would be negative, don't allocate + page_alloc_count = 0; + } + + switch (m_Buffer.m_eBufferType) { + case CBuffer::BufferType::NORMAL: + case CBuffer::BufferType::REQUEST_NORMAL: { + // don't do anything special + } break; + case CBuffer::BufferType::VAG: + case CBuffer::BufferType::REQUEST_VAG: { + // for VAG commands, we don't bother buffering more than the DMA transfer size + int dma_xfer_size = ((ISO_VAGCommand*)m_Buffer.m_pIsoCmd)->xfer_size; + if (dma_xfer_size) { + ASSERT(dma_xfer_size > 0); + int limit = (dma_xfer_size + 0x7fff) >> 0xf; + if (page_alloc_count > limit) { + ovrld_log(LogCategory::WARN, "File {} wants {} pages, but VAG DMA sizes limit us to ", + m_FileDef->name.data, page_alloc_count, limit); + lg::info("page count dma limit {} -> {}\n", page_alloc_count, limit); + page_alloc_count = limit; + } + } + } break; + default: + ASSERT_NOT_REACHED(); // bad buffer type. + } + + if (page_alloc_count == 0) { + return ret_plist; + } + + ovrld_log(LogCategory::PAGING, "File {} wants {} more pages in AllocPages.", m_FileDef->name.data, + page_alloc_count); + if (have_plist) { + ret_plist = get_page_manager()->GrowPageList(m_Buffer.m_pPageList, page_alloc_count); + } else { + ret_plist = get_page_manager()->AllocPageList(page_alloc_count, 0); + } + + if (ret_plist) { + m_Buffer.m_pPageList = ret_plist; + if (!have_plist) { + m_Buffer.m_pCurrentData = ret_plist->m_pFirstPage->m_pPageMemStart; + } + } else { + ASSERT_NOT_REACHED(); // might be ok. + } + return ret_plist; +} + +/*! + * Free all pages and the page list. + */ +void CBaseFile::FreePages() { + ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE); + if (m_Buffer.m_pPageList) { + get_page_manager()->FreePageList(m_Buffer.m_pPageList); + } + m_Buffer.m_pPageList = nullptr; +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/basefile.h b/game/overlord/jak3/basefile.h new file mode 100644 index 0000000000..268b90dd96 --- /dev/null +++ b/game/overlord/jak3/basefile.h @@ -0,0 +1,76 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/overlord/jak3/isocommon.h" +#include "game/overlord/jak3/pagemanager.h" + +namespace jak3 { +void jak3_overlord_init_globals_basefile(); + +struct CPageList; +struct ISOFileDef; +struct ISO_Hdr; + +constexpr int kDefaultBufferPageCount = 4; + +/*! + * Base class for a file that the ISO system is processing. + * This represents an "open" file, and contains references to the buffer holding this file's data + */ +struct CBaseFile { + CBaseFile(); + CBaseFile(const ISOFileDef* file, int semaphore); + virtual ~CBaseFile() = default; + + uint8_t* CheckPageBoundary(); + int InitBuffer(CBuffer::BufferType type, ISO_Hdr* msg); + CPageList* AllocPages(); + void TerminateBuffer(); + void FreePages(); + + // buffer that stores some contents of this file + CBuffer m_Buffer; + + // the number of pages that were allocated for reading this file. + u32 m_nNumPages; + + // Metadata about the file + const ISOFileDef* m_FileDef; + + // The compression format used on the file + enum class Kind { + UNKNOWN = 0, + NORMAL = 1, + LZO_COMPRESSED = 2, + } m_FileKind = Kind::UNKNOWN; + + EIsoStatus m_Status = EIsoStatus::NONE_0; + + // The expected read rate for streaming, used to prioritize CD reads. Can be 0 if unknown/not + // applicable. + int m_ReadRate = 0; + + // Number of sectors that we should read in total, decided based on the file size and request from + // user when they opened this file. + int m_LengthPages = 0; // really, in pages... + + // The current offset. (todo: is this for data we read, processed?) + int m_PageOffset = 0; + + // Semaphore that we should wait on before handing new data to the process callback. + // Set to -1 if there is no semaphore. + // (this is a bit of hack, only used for VAG streaming). + int m_ProcessDataSemaphore = 0; + + // virtual methods + virtual EIsoStatus BeginRead() = 0; + virtual EIsoStatus SyncRead() = 0; + virtual void Close() = 0; + virtual int RecoverPages(int num_pages) = 0; + virtual int GetSector() = 0; + // ?? + // ?? + // ?? +}; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/basefilesystem.cpp b/game/overlord/jak3/basefilesystem.cpp new file mode 100644 index 0000000000..baef60dcfe --- /dev/null +++ b/game/overlord/jak3/basefilesystem.cpp @@ -0,0 +1,26 @@ +#include "basefilesystem.h" + +#include "common/util/Assert.h" + +#include "game/sce/iop.h" + +using namespace iop; +namespace jak3 { +void jak3_overlord_init_globals_basefilesystem() {} + +CBaseFileSystem::CBaseFileSystem() { + for (auto& sema : m_Sema) { + sema = -1; + + SemaParam param; + param.max_count = 1; + param.attr = 0; + param.init_count = 1; + param.option = 0; + sema = CreateSema(¶m); + if (sema < 0) { + ASSERT_NOT_REACHED(); + } + } +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/basefilesystem.h b/game/overlord/jak3/basefilesystem.h new file mode 100644 index 0000000000..b0d76b19d2 --- /dev/null +++ b/game/overlord/jak3/basefilesystem.h @@ -0,0 +1,33 @@ +#pragma once + +namespace jak3 { +void jak3_overlord_init_globals_basefilesystem(); + +struct ISOFileDef; +struct ISOName; +struct CBaseFile; +struct VagDirEntry; + +constexpr int kMaxOpenFiles = 16; + +/*! + * Base class for "FileSystem", which supports finding and opening files. + * The only implementation we have is CISOCDFileSystem + */ +struct CBaseFileSystem { + CBaseFileSystem(); + virtual ~CBaseFileSystem() = default; + + // semaphores for processing open files + int m_Sema[kMaxOpenFiles]; + + virtual int Init() = 0; + // polldrive + virtual ISOFileDef* Find(const char* name) = 0; + virtual ISOFileDef* FindIN(const ISOName* name) = 0; + virtual int GetLength(const ISOFileDef* file) = 0; + virtual CBaseFile* Open(const ISOFileDef* file_def, int sector_offset, int file_kind) = 0; + virtual CBaseFile* OpenWAD(const ISOFileDef* file_def, int page_offset) = 0; + virtual VagDirEntry* FindVAGFile(const char* name) = 0; +}; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/dma.cpp b/game/overlord/jak3/dma.cpp new file mode 100644 index 0000000000..e194d01aa7 --- /dev/null +++ b/game/overlord/jak3/dma.cpp @@ -0,0 +1,526 @@ +#include "dma.h" + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/basefile.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" +#include "game/sound/sdshim.h" +#include "game/sound/sndshim.h" + +#define VOICE_BIT(voice) (1 << ((voice) >> 1)) + +namespace jak3 { +using namespace iop; +namespace { + +// most recent call to voice_trans_wrapper's arguments +u32 g_voiceTransMode = 0; +u32 g_voiceTransSize = 0; +s16 g_voiceTransChannel = 0; +const void* g_voiceTransAddr = nullptr; +u32 g_voiceTransSpuAddr = 0; + +// if we've started a transfer recently +bool g_voiceTransRunning = false; +// when that transfer was started +u32 g_voiceTransTime = 0; + +// despite the name, this is really an indicator that the SPU streaming system is waiting +// for a SPU interrupt on completion. +bool g_bSpuDmaBusy = false; +int g_nSpuDmaChannel = 0; + +ISO_VAGCommand* g_pDmaVagCmd = nullptr; +ISO_VAGCommand* g_pDmaStereoVagCmd = nullptr; + +int g_nSpuDmaChunks = 0; + +std::array g_aSpuDmaQueue; +int g_nSpuDmaQueueHead = 0; +int g_nSpuDmaQueueTail = 0; +int g_nSpuDmaQueueCount = 0; + +struct DmaInterruptHandlerHack { + s32 chan = 0; + sceSdTransIntrHandler cb = nullptr; + void* data; + int countdown = 0; +} g_DmaInterruptHack; + +} // namespace + +void jak3_overlord_init_globals_dma() { + g_voiceTransMode = 0; + g_voiceTransSize = 0; + g_voiceTransChannel = 0; + g_voiceTransAddr = nullptr; + g_voiceTransSpuAddr = 0; + g_voiceTransRunning = false; + g_voiceTransTime = 0; + g_bSpuDmaBusy = false; + g_nSpuDmaChannel = 0; + g_pDmaVagCmd = nullptr; + g_pDmaStereoVagCmd = nullptr; + g_nSpuDmaChunks = 0; + g_aSpuDmaQueue = {}; + g_nSpuDmaQueueHead = 0; + g_nSpuDmaQueueCount = 0; + g_nSpuDmaQueueTail = 0; + g_DmaInterruptHack = {}; +} + +// The DMA callback hack below is used to defer dma completion "interrupts" until the next run +// of the ISO Thread. This avoids re-entry type problems where the original design would set off +// a dma transfer in the completion handler of the previous transfer, and expect a few instructions +// to run after. + +void uninstall_dma_intr() { + g_DmaInterruptHack = {}; +} + +void set_dma_intr_handler_hack(s32 chan, sceSdTransIntrHandler cb, void* data) { + ASSERT(!g_DmaInterruptHack.cb); + g_DmaInterruptHack.chan = chan; + g_DmaInterruptHack.cb = cb; + g_DmaInterruptHack.data = data; + g_DmaInterruptHack.countdown = 10; +} + +int SPUDmaIntr(int channel, void* userdata); + +void dma_intr_hack() { + if (g_DmaInterruptHack.countdown) { + g_DmaInterruptHack.countdown--; + if (g_DmaInterruptHack.countdown == 0) { + int chan = g_DmaInterruptHack.chan; + void* data = g_DmaInterruptHack.data; + g_DmaInterruptHack = {}; + SPUDmaIntr(chan, data); + } + } +} + +/*! + * This function is used to set up a DMA transfer to SPU DMA. + * + * This wrapper was added very close to the end of Jak 3's development. + * + * I believe it basically checks for dma transfers that are somehow "dropped", and retries them. + * Since I don't think our IOP framework will ever do this, we have an assert if the dropped logic + * ever goes off. + */ +int voice_trans_wrapper(s16 chan, u32 mode, const void* iop_addr, u32 spu_addr, u32 size) { + // remember the transfer settings. If there's a transfer in progress, so we can't start here, + // we'll use these to start the transfer later. + g_voiceTransMode = mode; + g_voiceTransSize = size; + g_voiceTransChannel = chan; + g_voiceTransAddr = iop_addr; + g_voiceTransSpuAddr = spu_addr; + + if (g_voiceTransRunning) { + // I claim this should never happen, and this is their workaround for a bug. + ASSERT_NOT_REACHED(); + return -0xd2; // busy + } else { + g_voiceTransRunning = true; + g_voiceTransTime = GetSystemTimeLow(); + return sceSdVoiceTrans(chan, mode, iop_addr, spu_addr, size); + } +} + +u32 read_rate_calc(u32 pitch) { + u64 pitch1 = (pitch >> 3); + u64 mult_result = pitch1 * 0x2492'4925ull; + return mult_result >> 32; +} + +/*! + * The worst function of all time - the SPU DMA completion interrupt. + */ +int SPUDmaIntr(int channel, void* userdata) { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr enter! {} 0x{:x}", channel, (u64)userdata); + + if (!g_bSpuDmaBusy) { + // we got an interrupt, but weren't expecting it, or no longer have the need for the data. + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not busy"); + return 0; + } + + if (channel != g_nSpuDmaChannel) { + // interrupt was for the wrong channel, somehow. + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - not our channel ??"); + return 0; + } + + // since we're in the completion handler, we know that there is no voice trans (SPU DMA) running. + g_voiceTransRunning = false; + + // This next block will handle updating the playback command that triggered this dma: + if (g_pDmaVagCmd) { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDma for cmd {}", g_pDmaVagCmd->name); + if (!g_pDmaStereoVagCmd) { + // non-stereo audio + + // set a flag to indicate even/odd number of chunks have been dma'd + if ((g_nSpuDmaChunks & 1) == 0) { + g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1; + } else { + g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1; + } + } else { + // stereo audio. This requires two uploads, one for left/right audio. If we've finished the + // first, start the second one here: + if (g_pDmaStereoVagCmd->xfer_size) { + // parameters for second upload + int chan = g_pDmaVagCmd->dma_chan; + const u8* iop_addr = g_pDmaStereoVagCmd->dma_iop_mem_ptr; + int size = g_pDmaStereoVagCmd->xfer_size; + + // SPU addr - toggle the buffer based on stereo side: + // TODO: better explanation of why this picks the correct buffer. + int spu_addr; + if ((g_nSpuDmaChunks & 1) == 0) { + spu_addr = g_pDmaStereoVagCmd->stream_sram; + } else { + spu_addr = g_pDmaStereoVagCmd->stream_sram + 0x2000; + } + + // these lines reordered to possibly support immediate dma completion callback?? + + // clear flag so we know not to transfer the next part + g_pDmaStereoVagCmd->xfer_size = 0; + g_pDmaStereoVagCmd->dma_iop_mem_ptr = nullptr; + + // start next transfer + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr starting stereo sibling transfer"); + set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata); + voice_trans_wrapper(chan, 0, iop_addr, spu_addr, size); + return 0; + } + + // second stereo upload completed - update double-buffering flags + if ((g_nSpuDmaChunks & 1) == 0) { + g_pDmaVagCmd->flags.dma_complete_even_chunk_count = 1; + g_pDmaStereoVagCmd->flags.dma_complete_even_chunk_count = 1; + } else { + g_pDmaVagCmd->flags.dma_complete_odd_chunk_count = 1; + g_pDmaStereoVagCmd->flags.dma_complete_odd_chunk_count = 1; + } + } + + // if this is the first chunk, we'll start the actual audio here: + // lg::warn("----------> interrupt with chunks {}\n", g_nSpuDmaChunks); + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks count {}", g_nSpuDmaChunks); + + if (g_nSpuDmaChunks == 0) { + // compute pitch/playback rate + int pitch = CalculateVAGPitch(g_pDmaVagCmd->pitch1, g_pDmaVagCmd->pitch_cmd); + ASSERT(pitch == (pitch & 0xffff)); + + // inform the ISO system how fast we're reading + if (g_pDmaVagCmd->m_pBaseFile) { + // unlike actual playback, this is done with the pitch1 value from the file itself - so if + // we speed up/slow down stuff in debug, it won't change streaming modes + const int pitch_from_file = + CalculateVAGPitch(g_pDmaVagCmd->pitch1_file, g_pDmaVagCmd->pitch_cmd); + int rate = g_pDmaStereoVagCmd ? pitch_from_file * 0x2ee : pitch_from_file * 0x177; + g_pDmaVagCmd->m_pBaseFile->m_ReadRate = read_rate_calc(rate); + } + + // start! + u32 voice_mask = 0; + if (!g_pDmaStereoVagCmd) { + // forget any previous spu address + g_pDmaVagCmd->current_spu_address = 0; + + static_assert(SD_VA_SSA == 0x2040); + static_assert(SD_S_KOFF == 0x1600); + static_assert(SD_S_KON == 0x1500); + + static_assert(SD_VP_ADSR1 == 0x300); + static_assert(SD_VP_ADSR2 == 0x400); + static_assert(SD_VP_PITCH == 0x200); + + // before touching SPU2 hardware, wait for voice safety: + BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900); + + // set address and ADSR settings + sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30); + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff); + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0); + if (g_pDmaVagCmd->flags.paused) { + pitch = 0; + } + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch); + voice_mask = VOICE_BIT(g_pDmaVagCmd->voice); + } else { + // forget any previous spu address + g_pDmaVagCmd->current_spu_address = 0; + g_pDmaStereoVagCmd->current_spu_address = 0; + + // wait for voices to be safe to adjust + BlockUntilVoiceSafe(g_pDmaVagCmd->voice, 0x900); + BlockUntilVoiceSafe(g_pDmaStereoVagCmd->voice, 0x900); + + // set voice params + sceSdSetAddr(g_pDmaVagCmd->voice | SD_VA_SSA, g_pDmaVagCmd->stream_sram + 0x30); + sceSdSetAddr(g_pDmaStereoVagCmd->voice | SD_VA_SSA, g_pDmaStereoVagCmd->stream_sram + 0x30); + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR1, 0xff); + sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR1, 0xff); + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_ADSR2, 0x1fc0); + sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_ADSR2, 0x1fc0); + if (g_pDmaVagCmd->flags.paused) { + pitch = 0; + } + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, pitch); + sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, pitch); + voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice); + } + + // do key-on or key-off + if (g_pDmaVagCmd->flags.paused) { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key off"); + BlockUntilAllVoicesSafe(); + sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask); + } else { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 0, key on"); + BlockUntilAllVoicesSafe(); + sceSdSetSwitch(SD_S_KON | (g_pDmaVagCmd->voice & 1), voice_mask); + } + + // remember the time of the key-on/off. This is used to avoid sending voice commands + // quickly, which somehow confuses the sound hardware. + auto sys_time = GetSystemTimeLow(); + MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time); + if (g_pDmaStereoVagCmd) { + MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time); + } + } else if (g_nSpuDmaChunks == 1) { + g_pDmaVagCmd->flags.saw_chunks1 = 1; + if (g_pDmaStereoVagCmd) { + g_pDmaStereoVagCmd->flags.saw_chunks1 = 1; + } + + if (g_pDmaVagCmd->flags.paused) { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, pausing"); + u32 voice_mask = 0; + if (!g_pDmaStereoVagCmd) { + // pause by setting pitches to 0 + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0); + BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900); + voice_mask = VOICE_BIT(g_pDmaVagCmd->voice); + } else { + sceSdSetParam(g_pDmaStereoVagCmd->voice | SD_VP_PITCH, 0); + sceSdSetParam(g_pDmaVagCmd->voice | SD_VP_PITCH, 0); + BlockUntilVoiceSafe(VOICE_BIT(g_pDmaVagCmd->voice), 0x900); + BlockUntilVoiceSafe(VOICE_BIT(g_pDmaStereoVagCmd->voice), 0x900); + voice_mask = VOICE_BIT(g_pDmaVagCmd->voice) | VOICE_BIT(g_pDmaStereoVagCmd->voice); + } + + // switch off + BlockUntilAllVoicesSafe(); + sceSdSetSwitch(SD_S_KOFF | (g_pDmaVagCmd->voice & 1), voice_mask); + auto sys_time = GetSystemTimeLow(); + MarkVoiceKeyedOnOff(g_pDmaVagCmd->voice, sys_time); + if (g_pDmaStereoVagCmd) { + MarkVoiceKeyedOnOff(g_pDmaStereoVagCmd->voice, sys_time); + } + } else { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr chunks 1, unpausing by call to UnPauseVAG"); + g_pDmaVagCmd->flags.paused = 1; + UnPauseVAG(g_pDmaVagCmd); + } + } + + // now that we've processed the command from this interrupt, mark it as safe to modify + g_pDmaVagCmd->safe_to_modify_dma = 1; + if (g_pDmaStereoVagCmd) { + g_pDmaStereoVagCmd->safe_to_modify_dma = 1; + } + // and forget it! + g_pDmaVagCmd = nullptr; + g_pDmaStereoVagCmd = nullptr; + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma handling of VAG cmd is complete"); + } + + // release ref on this page. (interestingly, not a dma ref...) + if (userdata) { + CPage* page = (CPage*)userdata; + int ret = page->ReleaseRef(); + ASSERT(ret >= 0); + } + + // now - see if we have another queued dma transfer + ASSERT(g_nSpuDmaQueueCount >= 0); + if (g_nSpuDmaQueueCount == 0) { + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr dma queue is empty, disabling interrupt"); + // we're done! + // set_dma_intr_handler_hack(channel, nullptr, nullptr); + uninstall_dma_intr(); + // if (-1 < channel) { + // snd_FreeSPUDMA(channel); + // } + g_bSpuDmaBusy = false; + } else { + ovrld_log(LogCategory::SPU_DMA_STR, + "SPUDmaIntr dma queue is not empty, preparing to run {} ({} pending)", + g_nSpuDmaQueueHead, g_nSpuDmaQueueCount); + // nope, more dma to run + auto* next_xfer = &g_aSpuDmaQueue[g_nSpuDmaQueueHead]; + + // set up the next interrupt handler + set_dma_intr_handler_hack(channel, SPUDmaIntr, next_xfer->user_data); + + // args for the dma transfer + int next_chan = channel; + int next_mode = 0; + const void* next_iop = next_xfer->iop_mem; + u32 next_spu = next_xfer->spu_addr; + u32 next_length = next_xfer->length; + + // load up the commands to handle + g_pDmaVagCmd = next_xfer->command; + g_pDmaStereoVagCmd = nullptr; + if (g_pDmaVagCmd) { + g_pDmaStereoVagCmd = g_pDmaVagCmd->stereo_sibling; + } + g_nSpuDmaChunks = next_xfer->num_isobuffered_chunks; + + // advance the queue! + g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + -1; + g_nSpuDmaQueueHead = g_nSpuDmaQueueHead + 1; + if (0xf < g_nSpuDmaQueueHead) { + g_nSpuDmaQueueHead = 0; + } + + // start the next one! + // set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, userdata); + voice_trans_wrapper(next_chan, next_mode, next_iop, next_spu, next_length); + } + + ovrld_log(LogCategory::SPU_DMA_STR, "SPUDmaIntr exit - end of function"); + return 0; +} + +/*! + * Start DMA to EE. + */ +void DMA_SendToEE(void* ee_dest, + const void* iop_src, + u32 length, + void callback(void*), + void* callback_arg) { + ASSERT(iop_src); + ASSERT(ee_dest); + ASSERT(((uintptr_t)iop_src & 3) == 0); + ASSERT(((uintptr_t)ee_dest & 0xf) == 0); + ASSERT(length < 0xffff0); + sceSifDmaData cmd; // DMA settings + + // setup command + cmd.mode = 0; + cmd.data = iop_src; + cmd.addr = ee_dest; + cmd.size = length; + + // instant DMA + // ovrld_log(LogCategory::EE_DMA, "DMA_SendToEE: 0x{:x}, size {}", (u64)ee_dest, length); + sceSifSetDma(&cmd, 1); + + // for now, we'll do the callback here, but I bet it will cause problems + if (callback) { + callback(callback_arg); + } +} + +/*! + * Start DMA transfer to SPU. Despite the name, this does not actually "sync" - the transfer will + * be ongoing. If there is an ongoing transfer when this is called, the transfer will be queued. + */ +int DMA_SendToSPUAndSync(const u8* iop_mem, + int length, + int spu_addr, + ISO_VAGCommand* cmd, + void* user_data) { + // CpuSuspendIntr(local_28); + int ret = 1; + bool defer = false; + + ovrld_log(LogCategory::SPU_DMA_STR, + "DMA to SPU requested for {}, {} bytes to 0x{:x}, currently busy? {}", + cmd ? cmd->name : "NO-CMD", length, spu_addr, g_bSpuDmaBusy); + if (g_bSpuDmaBusy == 0) { + // not busy, we can actually start dma now. + g_nSpuDmaChannel = snd_GetFreeSPUDMA(); + if (g_nSpuDmaChannel == -1) { + return 0; + } + // set globals for DMA processing + if (cmd) { + g_nSpuDmaChunks = cmd->num_isobuffered_chunks; + g_pDmaStereoVagCmd = cmd->stereo_sibling; + g_pDmaVagCmd = cmd; + } + + } else { + // busy, need to queue the dma + ASSERT(g_nSpuDmaQueueCount <= (int)g_aSpuDmaQueue.size()); + + // set values: + g_aSpuDmaQueue[g_nSpuDmaQueueTail].length = length; + g_aSpuDmaQueue[g_nSpuDmaQueueTail].spu_addr = spu_addr; + g_aSpuDmaQueue[g_nSpuDmaQueueTail].user_data = user_data; + g_aSpuDmaQueue[g_nSpuDmaQueueTail].num_isobuffered_chunks = + cmd ? cmd->num_isobuffered_chunks : 0; + g_aSpuDmaQueue[g_nSpuDmaQueueTail].command = cmd; + g_aSpuDmaQueue[g_nSpuDmaQueueTail].iop_mem = iop_mem; + g_nSpuDmaQueueCount = g_nSpuDmaQueueCount + 1; + g_nSpuDmaQueueTail = g_nSpuDmaQueueTail + 1; + if (0xf < g_nSpuDmaQueueTail) { + g_nSpuDmaQueueTail = 0; + } + defer = true; + } + + // set up the stereo command + if (cmd) { + cmd->safe_to_modify_dma = 0; + auto* stereo = cmd->stereo_sibling; + if (stereo) { + stereo->num_isobuffered_chunks = cmd->num_isobuffered_chunks; + stereo->dma_iop_mem_ptr = iop_mem + length; + cmd->dma_chan = g_nSpuDmaChannel; + stereo->xfer_size = length; + } + } + + // kick off dma, if we decided not to queue. + if (!defer) { + g_bSpuDmaBusy = true; + set_dma_intr_handler_hack(g_nSpuDmaChannel, SPUDmaIntr, user_data); + voice_trans_wrapper(g_nSpuDmaChannel, 0, iop_mem, spu_addr, length); + } + return ret; +} + +/*! + * Run a dma transfer that was delayed or dropped. + */ +void RunDeferredVoiceTrans() { + // only if there's a currently happening transfer. + if (g_voiceTransRunning) { + if (GetSystemTimeLow() - g_voiceTransTime > 0x384000) { + ovrld_log(LogCategory::WARN, "DeferredVoiceTrans has detected hung dma... expect problems."); + // original game also check sceSdVoiceTransStatus here, we'll possibly need to mess with this + // if we delay dma completion interrupts... + g_voiceTransRunning = false; + voice_trans_wrapper(g_voiceTransChannel, g_voiceTransMode, g_voiceTransAddr, + g_voiceTransSpuAddr, g_voiceTransSize); + } + } +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/dma.h b/game/overlord/jak3/dma.h new file mode 100644 index 0000000000..f8e689d6ab --- /dev/null +++ b/game/overlord/jak3/dma.h @@ -0,0 +1,32 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_dma(); +struct ISO_VAGCommand; + +int voice_trans_wrapper(s16 chan, u32 mode, const void* iop_addr, u32 spu_addr, u32 size); +void DMA_SendToEE(void* ee_dest, + const void* iop_src, + u32 length, + void callback(void*), + void* callback_arg); +int DMA_SendToSPUAndSync(const u8* iop_mem, + int length, + int spu_addr, + ISO_VAGCommand* cmd, + void* user_data); +void RunDeferredVoiceTrans(); +struct ISO_VAGCommand; + +struct DmaQueueEntry { + ISO_VAGCommand* command = nullptr; + const void* iop_mem = nullptr; + u32 spu_addr = 0; + u32 length = 0; + void* user_data = nullptr; + u32 num_isobuffered_chunks = 0; +}; +void dma_intr_hack(); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/dvd_driver.cpp b/game/overlord/jak3/dvd_driver.cpp new file mode 100644 index 0000000000..c642849bd8 --- /dev/null +++ b/game/overlord/jak3/dvd_driver.cpp @@ -0,0 +1,582 @@ +#include "dvd_driver.h" + +#include +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" +#include "common/util/FileUtil.h" + +#include "game/overlord/jak3/isocommon.h" +#include "game/overlord/jak3/overlord.h" +#include "game/sce/iop.h" + +namespace jak3 { +using namespace iop; +std::unique_ptr g_DvdDriver; +s32 g_nDvdDriverThread = -1; + +void jak3_overlord_init_globals_dvd_driver() { + g_DvdDriver = std::make_unique(); + g_nDvdDriverThread = -1; +} + +CDvdDriver* get_driver() { + return g_DvdDriver.get(); +} + +CMsg::CMsg(jak3::CMsg::MsgKind msg) : m_msg(msg) { + m_ret = 1; + m_thread = GetThreadId(); +} + +int CMsg::send() { + // note: changed from passing data + // and removed corresponding -4 to skip back past the vtable in DVD thread + // (what were they thinking?) + s32 ret = SendMbx(get_driver()->msgbox, this); + if (ret == 0) { + get_driver()->KickDvdThread(); + SleepThread(); + return m_ret; + } + return ret; +} + +// CMsgLock::CMsgLock() : CMsg(CMsg::MsgKind::LOCK) {} +// +// void CMsgLock::handler() { +// get_driver()->Lock(); +// m_ret = 0; +// } + +// CMsgReadRaw::CMsgReadRaw(jak3::BlockParams* params) : CMsg(CMsg::MsgKind::READ_RAW) { +// m_block_params = *params; +// } +// +// void CMsgReadRaw::handler() { +// m_ret = get_driver()->ReadDirect(&m_block_params); +// } + +CMsgCancelRead::CMsgCancelRead(jak3::CDescriptor* desc) : CMsg(CMsg::MsgKind::CANCEL_READ) { + m_desc = desc; +} + +void CMsgCancelRead::handler() { + get_driver()->CancelRead(m_desc); + m_ret = 0; +} + +u32 DvdThread(); + +CDvdDriver::CDvdDriver() { + fifo_entry_sema = -1; + current_thread_priority = 0x13; + disk_type = 5; + tray_flag = 1; + // m_nLockCount = 0; + event_flag = -1; + fifo_access_sema = -1; + tray_flag2 = 1; + initialized = 0; + m_nNumFifoEntries = 0; + ring_head = 0; + ring_tail = 0; + read_in_progress = 0; + callback = nullptr; + // locked = false; + trayflag3 = 0; + m_nDvdThreadAccessSemaCount = 0; + memset(ring, 0, sizeof(Block) * 16); +} + +void CDvdDriver::Initialize() { + if (!initialized) { + *this = {}; + ThreadParam thread_param; + thread_param.attr = 0x2000000; + // mbox_param.option = gDvdDriverThreadOptions; // ??? + thread_param.entry = DvdThread; + thread_param.stackSize = 0x800; + thread_param.initPriority = 0x13; + thread_param.option = 0; + strcpy(thread_param.name, "dvd"); + // mbox_param.attr = (int)PTR_DvdThread_00015c98; // ??? + g_nDvdDriverThread = CreateThread(&thread_param); + ASSERT(g_nDvdDriverThread >= 0); + + SemaParam sema_param; + sema_param.attr = 0; + sema_param.init_count = 1; + sema_param.max_count = 1; + sema_param.option = 0; + fifo_access_sema = CreateSema(&sema_param); + ASSERT(fifo_access_sema >= 0); + sema_param.max_count = 0x10; + sema_param.attr = 0; + sema_param.init_count = 0x10; + sema_param.option = 0; + fifo_entry_sema = CreateSema(&sema_param); + ASSERT(fifo_entry_sema >= 0); + MbxParam mbox_param; + mbox_param.attr = 0; + mbox_param.option = 0; + msgbox = CreateMbx(&mbox_param); + ASSERT(msgbox >= 0); + + EventFlagParam param; + param.attr = 0; + param.option = 0; + param.init_pattern = 0; + event_flag = CreateEventFlag(¶m); + ASSERT(event_flag >= 0); + StartThread(g_nDvdDriverThread, 0); // this... + } + initialized = 1; +} + +void CDvdDriver::SetDriverCallback(std::function f) { + callback = f; +} + +// GetDriveCallback + +// Poll - would kick the thread... + +// void CDvdDriver::Lock() { +// ASSERT_NOT_REACHED(); +// if (GetThreadId() == g_nDvdDriverThread) { +// m_nLockCount++; +// locked = true; +// // needs break HACK +// needs_break = false; +// } else { +// CMsgLock lock; +// lock.send(); +// } +// } + +// Read + +int CDvdDriver::ReadMultiple(CDescriptor* descriptor, + int* pages_read_out, + BlockParams* params, + int num_blocks, + bool block_if_queue_full) { + *pages_read_out = 0; + s32 ret = 1; + + // check block parameters are reasonable + if (ValidateBlockParams(params, num_blocks) != 0) { + bool from_dvd_thread = GetThreadId() == g_nDvdDriverThread; + if (from_dvd_thread) { + // there is a setting to control if this function should block if there are too many + // queued reads. If the is called from the DVD thread, then this would deadlock. + // the original game ignored the block argument, but I'm asserting + block_if_queue_full = 0; + ASSERT_NOT_REACHED(); + } + + ovrld_log(LogCategory::DRIVER, "[driver] ReadMultiple (from our thread? {}) num_blocks {}", + from_dvd_thread, num_blocks); + + ret = 0; + if (0 < num_blocks) { + // loop, until we've done all the requested reads. + do { + s32 acquired_slots = 0; + if (0 < num_blocks) { + // loop to try to get up to num_blocks slots in the fifo + // but, if we get less, we'll take that too + do { + if (PollSema(this->fifo_entry_sema) == -0x1a3) + break; + acquired_slots = acquired_slots + 1; + } while (acquired_slots < num_blocks); + } + ovrld_log(LogCategory::DRIVER, "[driver] ReadMultiple acquired {} slots in ring", + acquired_slots); + + // if we are blocking, and we acquired no slots, then we'll wait here until we get one slot. + if ((block_if_queue_full != 0) && (acquired_slots < 1)) { + ovrld_log(LogCategory::DRIVER, "[driver] ring is full, blocking!"); + acquired_slots = 1; // the one we'll get from the WaitSema below + do { + } while (WaitSema(fifo_entry_sema) != 0); + } + + // lock, now that we've gotten the slots + AcquireFIFOSema(from_dvd_thread); + num_blocks = num_blocks - acquired_slots; + + // if we didn't get any slots, bail. + if (acquired_slots < 1) { + ovrld_log(LogCategory::DRIVER, "[driver] ring is full, bailing!"); + ReleaseFIFOSema(from_dvd_thread); + if (0 < *pages_read_out) { + KickDvdThread(); + } + return 2; + } + + // loop, updating the ring for each slot we aquired + do { + auto* slot = ring + ring_tail; + ovrld_log(LogCategory::DRIVER, "[driver] inserting in ring slot {}", ring_tail); + + ring_tail++; + if (0xf < ring_tail) { + ring_tail = 0; + } + m_nNumFifoEntries++; + + auto* tail = descriptor->m_pTail; + slot->descriptor = descriptor; + slot->params = params[*pages_read_out]; + *pages_read_out = (*pages_read_out) + 1; + if (!tail) { + descriptor->m_pHead = slot; + } else { + tail->next = slot; + } + acquired_slots = acquired_slots + -1; + descriptor->m_pTail = slot; + slot->next = nullptr; + } while (acquired_slots != 0); + ReleaseFIFOSema(from_dvd_thread); + KickDvdThread(); + ret = 0; + } while (0 < num_blocks); + } + } else { + ASSERT_NOT_REACHED(); + } + return ret; +} + +void CDvdDriver::CancelRead(jak3::CDescriptor* desc) { + if (GetThreadId() == g_nDvdDriverThread) { + AcquireFIFOSema(true); + if ((read_in_progress != 0) && (ring[ring_head].descriptor == desc)) { + // while (iVar1 = sceCdBreak(), iVar1 == 0) { + // DelayThread(8000); + // sceCdSync(0); + // } + // sceCdSync(0); + read_in_progress = 0; + } + + Block* iter = desc->m_pHead; + Block* tail = desc->m_pTail; + while (iter) { + if (iter->descriptor) { + CompletionHandler(iter, 6); + } + iter->descriptor = nullptr; + iter->next = nullptr; + if (iter == tail) + break; + iter = desc->m_pHead; + } + + if (desc->m_pTail == tail) { + desc->m_pTail = nullptr; + } + ReleaseFIFOSema(true); + } else { + CMsgCancelRead msg(desc); + msg.send(); + } +} + +s32 CDvdDriver::ValidateBlockParams(jak3::BlockParams* params, int num_params) { + if (!params) { + lg::die("Invalid BlockParams: nullptr"); + return 0; + } + if (num_params < 1) { + lg::die("Invalid BlockParams: size == 0"); + return 0; + } + + for (int i = 0; i < num_params; i++) { + auto& p = params[i]; + if (p.destination == nullptr) { + lg::die("Invalid BlockParams: {} had nullptr dest", i); + return 0; + } + int kMaxFileSize = 1024 * 1024 * 1024; + if (p.sector_num > kMaxFileSize / 0x800) { + lg::die("Invalid BlockParams: {} had sector num {}", i, p.sector_num); + return 0; + } + if (p.num_sectors > 0x1d0) { + lg::die("Invalid BlockParams: {} had sector count {}", i, p.num_sectors); + return 0; + } + if (!p.file_def) { + lg::die("Invalid BlockParams: {} had no file", i); + return 0; + } + } + return 1; +} + +void CDvdDriver::KickDvdThread() { + while (SetEventFlag(event_flag, 1)) { + ; + } +} + +int CDvdDriver::AcquireFIFOSema(bool from_dvd_thread) { + int iVar1; + int iVar2; + + if ((from_dvd_thread != 0) && + (iVar2 = m_nDvdThreadAccessSemaCount, m_nDvdThreadAccessSemaCount = iVar2 + 1, 0 < iVar2)) { + return 0; + } + iVar1 = WaitSema(this->fifo_access_sema); + iVar2 = 0; + if ((iVar1 != 0) && (iVar2 = iVar1, from_dvd_thread != 0)) { + m_nDvdThreadAccessSemaCount = m_nDvdThreadAccessSemaCount + -1; + iVar2 = iVar1; + } + return iVar2; +} + +int CDvdDriver::ReleaseFIFOSema(bool from_dvd_thread) { + int iVar1; + int iVar2; + + if (from_dvd_thread != 0) { + iVar2 = m_nDvdThreadAccessSemaCount + -1; + if (m_nDvdThreadAccessSemaCount < 1) { + return -0x1a4; + } + m_nDvdThreadAccessSemaCount = iVar2; + if (0 < iVar2) { + return 0; + } + } + iVar1 = SignalSema(fifo_access_sema); + iVar2 = 0; + if ((iVar1 != 0) && (iVar2 = iVar1, from_dvd_thread != 0)) { + m_nDvdThreadAccessSemaCount = m_nDvdThreadAccessSemaCount + 1; + iVar2 = iVar1; + } + return iVar2; +} + +/*! + * PC port added function to actually do a read of a block. + */ +void CDvdDriver::read_from_file(const jak3::Block* block) { + const auto* fd = block->params.file_def; + ASSERT(fd); + FileCacheEntry* selected_entry = nullptr; + + // get a cache entry + for (auto& entry : m_file_cache) { + // if we already opened this file, use that + if (entry.def == fd) { + selected_entry = &entry; + break; + } + + // otherwise pick the least recently used + if (!selected_entry) { + selected_entry = &entry; + } else { + if (selected_entry->last_use_count > entry.last_use_count) { + selected_entry = &entry; + } + } + } + + // open a new file if needed + if (selected_entry->def != fd) { + lg::debug("CDvdDriver swapping files {} - > {}", + selected_entry->def ? selected_entry->def->name.data : "NONE", fd->name.data); + if (selected_entry->def) { + fclose(selected_entry->fp); + } + + selected_entry->def = fd; + selected_entry->fp = file_util::open_file(fd->full_path, "rb"); + if (!selected_entry->fp) { + lg::die("Failed to open {} {}", fd->full_path, strerror(errno)); + } + selected_entry->offset_in_file = 0; + } + + // increment use counter + selected_entry->last_use_count = m_file_cache_counter++; + + const u64 desired_offset = block->params.sector_num * 0x800; + + // see if we're reading entirely past the end of the file + if (desired_offset >= fd->length) { + return; + } + + if (selected_entry->offset_in_file != desired_offset) { + lg::debug("CDvdDriver jumping in file {}: {} -> {}", fd->name.data, + selected_entry->offset_in_file, desired_offset); + if (fseek(selected_entry->fp, desired_offset, SEEK_SET)) { + ASSERT_NOT_REACHED_MSG("Failed to fseek"); + } + selected_entry->offset_in_file = desired_offset; + } + + // read + s64 read_length = block->params.num_sectors * 0x800; + s64 extra_length = read_length + desired_offset - fd->length; + if (extra_length > 0) { + read_length -= extra_length; + } + auto ret = fread(block->params.destination, read_length, 1, selected_entry->fp); + if (ret != 1) { + lg::die("Failed to read {} {}, size {} of {} (ret {})", fd->full_path, strerror(errno), + read_length, fd->length, ret); + } + selected_entry->offset_in_file += read_length; +} + +u32 DvdThread() { + auto* driver = get_driver(); + + while (true) { + // Poll for messages + CMsg* msg = nullptr; + while (true) { + int poll_status = PollMbx((MsgPacket**)&msg, driver->msgbox); + if (poll_status || !msg) { + break; + } + // run message code + msg->handler(); + // wake the waiting thread. + WakeupThread(msg->m_thread); + } + + bool completed = false; + + // if a read is in progress, wait for it to finish. + if (driver->read_in_progress) { + // TODO: if we switch to async reads, this is where we'd want to sync. + // sceCdSync(0); + completed = true; + // error checking + } + + driver->AcquireFIFOSema(true); + // error handling + + // handle ring book-keeping. + // note that these are somewhat double-buffered - we'll sync on read i-1, start read i, then + // run the completion handler for read i - 1. + // the completion is what actually removes stuff from the ring. + + s32 fifo_slots_freed = completed ? 1 : 0; + s32 ring_entry = driver->ring_head + (completed ? 1 : 0); + if (ring_entry > 15) { + ring_entry = 0; + } + Block* block = driver->ring + ring_entry; + s32 fifo_entries = driver->m_nNumFifoEntries - fifo_slots_freed; + + if (fifo_entries) { + // start a new read. + driver->read_in_progress = 1; + ovrld_log(LogCategory::DRIVER, "[driver thread] Reading for slot {}", block - driver->ring); + driver->read_from_file(block); + + } else { + driver->read_in_progress = 0; + } + + // run completion handler for the previous read - not the one we just started! + Block sblock; + if (completed) { + auto* last_block = &driver->ring[driver->ring_head]; + ovrld_log(LogCategory::DRIVER, "[driver thread] Completion handler for {}", + driver->ring_head); + sblock = *last_block; + + if (sblock.descriptor && sblock.descriptor->m_pHead) { + ASSERT(sblock.descriptor->m_pHead == last_block); + } + if (((sblock.descriptor)->m_pHead == last_block) && + ((sblock.descriptor)->m_pHead = &sblock, (sblock.descriptor)->m_pTail == last_block)) { + (sblock.descriptor)->m_pTail = &sblock; + } + } + driver->ring_head = ring_entry; + driver->m_nNumFifoEntries = fifo_entries; + while (0 < fifo_slots_freed) { + fifo_slots_freed = fifo_slots_freed + -1; + SignalSema(driver->fifo_entry_sema); + } + if (completed != 0) { + driver->CompletionHandler(&sblock, 0); + } + driver->ReleaseFIFOSema(true); + + // this logic here was modified - we go to waiting if we have no more reads. + if (driver->m_nNumFifoEntries == 0) { + ovrld_log(LogCategory::DRIVER, "[driver thread] No work, waiting."); + WaitEventFlag(driver->event_flag, 1, 0x11); + ovrld_log(LogCategory::DRIVER, "[driver thread] Woken up!"); + } + + // s32 status; + // if ((((driver->locked != false)) || + // ((driver->needs_break == 0 && (driver->m_nNumFifoEntries == 0)))) && + // ((status = WaitEventFlag(driver->event_flag, 1, 0x11), + // status != 0 && (status != -0x1a2)))) { + // if (status == -0x1a9) { + // do { + // SleepThread(); + // } while (true); + // } + // DelayThread(8000); + // } + } +} + +void CDvdDriver::CompletionHandler(jak3::Block* block, int code) { + // there is some janky thread priority changes here, + // but they do not seem to be needed with the changes to the ISO thread. + + // PushPri(this,local_20,0x35); + auto* desc = block->descriptor; + if (desc && desc->m_pHead) { + desc->m_status = code; + int thread = desc->m_ThreadID; + if (desc->m_Callback) { + (desc->m_Callback)(desc->m_File, block, code); + } + if (0 < thread) { + WakeupThread(thread); + } + + Block* next; + if ((desc->m_pHead == block) && (next = block->next, desc->m_pHead = next, next == nullptr)) { + desc->m_pTail = nullptr; + } + } + block->next = nullptr; + block->descriptor = nullptr; + // PopPri(this, local_20[0]); +} + +CDvdDriver::~CDvdDriver() { + for (auto& entry : m_file_cache) { + if (entry.fp) { + fclose(entry.fp); + } + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/dvd_driver.h b/game/overlord/jak3/dvd_driver.h new file mode 100644 index 0000000000..fc3f91c60f --- /dev/null +++ b/game/overlord/jak3/dvd_driver.h @@ -0,0 +1,144 @@ +#pragma once + +#include + +#include "common/common_types.h" + +#include "game/overlord/jak3/isocommon.h" + +namespace jak3 { +void jak3_overlord_init_globals_dvd_driver(); + +/*! + * The DVD Driver is what actually called the Sony CD library functions. + * It also had many special cases for handling errors, open trays, retries, etc that are not + * recreated in this port. + * + * The original DVD driver thread didn't run the actual reads - the Sony CD library had its own + * threads for async reads. However, they would call sceCdSync() to wait until a read finished. + * It's not clear if sceCdSync would allow other threads to run while waiting on the read to finish. + * but if it did, this is a bit of a difference in the blocking behavior. + * + * This is something that could be revisited, as freads will now essentially block the entire + * overlord, including refilling sound RAM. + */ + +struct CDescriptor; +struct BlockParams; +struct CPage; +struct ISOFileDef; + +// The CMSG system is used to pass messages from external threads to the driver. It's mostly used +// internally by the driver, when functions are called on it from outside threads. In these cases +// the Cmsg will get send to the driver thread and handler will run on it here. + +struct CMsg { + enum class MsgKind { + // LOCK = 0, + READ_RAW = 1, + CANCEL_READ = 2, + }; + CMsg(MsgKind msg); + virtual int send(); + virtual void handler() = 0; + + u8 data[8]; + MsgKind m_msg; + int m_thread; + int m_ret; +}; + +// struct CMsgLock : public CMsg { +// CMsgLock(); +// void handler() override; +// }; + +// struct CMsgReadRaw : public CMsg { +// explicit CMsgReadRaw(BlockParams* params); +// void handler() override; +// BlockParams m_block_params; +// }; + +struct CMsgCancelRead : public CMsg { + explicit CMsgCancelRead(CDescriptor* desc); + void handler() override; + CDescriptor* m_desc; +}; + +struct CISOCDFile; + +/*! + * Reference to an ongoing or requested read at the driver level, possibly made up of multiple + * blocks. In this case, the head/tail pointers point to Blocks stored inside the CDvdDriver's + * internal FIFO. + */ +struct CDescriptor { + CDescriptor() = default; + int m_unk0 = 0; + void (*m_Callback)(CISOCDFile*, Block*, s32) = nullptr; + int m_ThreadID = 0; + int m_status = 0; + CISOCDFile* m_File = nullptr; + Block* m_pHead = nullptr; + Block* m_pTail = nullptr; +}; + +class CDvdDriver { + public: + CDvdDriver(); + ~CDvdDriver(); + void Initialize(); + void CancelRead(CDescriptor* descriptor); + int ReadMultiple(CDescriptor* descriptor, + int* pages_read_out, + BlockParams* params, + int num_pages, + bool block_if_queue_full); + void SetDriverCallback(std::function f); + // int ReadDirect(BlockParams* params); + void KickDvdThread(); + // void Lock(); + int GetDiskType() const { return disk_type; } + s32 ValidateBlockParams(BlockParams* params, int num_params); + int ReleaseFIFOSema(bool from_dvd_thread); + int AcquireFIFOSema(bool from_dvd_thread); + void read_from_file(const Block* block); + void CompletionHandler(Block* block, int code); + + u8 initialized = 0; + s32 event_flag = -1; + s32 fifo_access_sema = -1; + s32 fifo_entry_sema = -1; + s32 msgbox = -1; + s32 m_nNumFifoEntries = 0; + s32 ring_head = 0; + s32 ring_tail = 0; + Block ring[16]; + u8 read_in_progress; // more likely: read in progress + std::function callback; + s32 current_thread_priority = 0; + // s32 m_nLockCount = 0; + // bool locked = false; + // + s32 disk_type; + u8 tray_flag2; + u8 trayflag3; + u8 tray_flag; + s32 m_nDvdThreadAccessSemaCount = 0; + + private: + struct FileCacheEntry { + const ISOFileDef* def = nullptr; + FILE* fp = nullptr; + u32 last_use_count = 0; + u64 offset_in_file = 0; + }; + u32 m_file_cache_counter = 0; + static constexpr int kNumFileCacheEntries = 6; + FileCacheEntry m_file_cache[kNumFileCacheEntries]; +}; + +// replacement for g_DvdDriver +CDvdDriver* get_driver(); + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/init.cpp b/game/overlord/jak3/init.cpp new file mode 100644 index 0000000000..7bbc073990 --- /dev/null +++ b/game/overlord/jak3/init.cpp @@ -0,0 +1,50 @@ + +#include "game/overlord/jak3/basefile.h" +#include "game/overlord/jak3/basefilesystem.h" +#include "game/overlord/jak3/dma.h" +#include "game/overlord/jak3/dvd_driver.h" +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/iso_api.h" +#include "game/overlord/jak3/iso_cd.h" +#include "game/overlord/jak3/iso_queue.h" +#include "game/overlord/jak3/isocommon.h" +#include "game/overlord/jak3/list.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/pagemanager.h" +#include "game/overlord/jak3/ramdisk.h" +#include "game/overlord/jak3/sbank.h" +#include "game/overlord/jak3/soundcommon.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/overlord/jak3/srpc.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/stream.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/overlord/jak3/vag.h" +#include "game/overlord/jak3/vblank_handler.h" + +namespace jak3 { +void jak3_overlord_init_globals_all() { + jak3_overlord_init_globals_overlord(); + jak3_overlord_init_globals_pagemanager(); + jak3_overlord_init_globals_iso_cd(); + jak3_overlord_init_globals_dma(); + jak3_overlord_init_globals_iso(); + jak3_overlord_init_globals_iso_queue(); + jak3_overlord_init_globals_srpc(); + jak3_overlord_init_globals_vag(); + jak3_overlord_init_globals_ssound(); + jak3_overlord_init_globals_iso_api(); + jak3_overlord_init_globals_spustreams(); + jak3_overlord_init_globals_list(); + jak3_overlord_init_globals_vblank_handler(); + jak3_overlord_init_globals_dvd_driver(); + jak3_overlord_init_globals_basefile(); + jak3_overlord_init_globals_basefilesystem(); + jak3_overlord_init_globals_ramdisk(); + jak3_overlord_init_globals_isocommon(); + jak3_overlord_init_globals_stream(); + jak3_overlord_init_globals_sbank(); + jak3_overlord_init_globals_soundcommon(); + jak3_overlord_init_globals_streamlist(); +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/init.h b/game/overlord/jak3/init.h new file mode 100644 index 0000000000..ceecc1b396 --- /dev/null +++ b/game/overlord/jak3/init.h @@ -0,0 +1,5 @@ +#pragma once + +namespace jak3 { +void jak3_overlord_init_globals_all(); +} \ No newline at end of file diff --git a/game/overlord/jak3/iso.cpp b/game/overlord/jak3/iso.cpp new file mode 100644 index 0000000000..6ba18eb2f4 --- /dev/null +++ b/game/overlord/jak3/iso.cpp @@ -0,0 +1,1796 @@ +#include "iso.h" + +#include + +#include "common/util/Assert.h" + +#include "game/overlord/jak3/dma.h" +#include "game/overlord/jak3/iso_api.h" +#include "game/overlord/jak3/iso_cd.h" +#include "game/overlord/jak3/iso_queue.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/rpc_interface.h" +#include "game/overlord/jak3/sbank.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/overlord/jak3/srpc.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/stream.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" +#include "game/sound/sndshim.h" + +using namespace iop; + +namespace jak3 { +int g_nISOInitFlag = 0; +u32 s_MsgPacket_NotOnStackSync = 0; +int g_nSyncMbx = -1; +int g_nISOMbx = -1; +int g_nDGOMbx = -1; +int g_nDGOThread = -1; +int g_nSTRThreadID = -1; +int g_nISOThreadID = -1; +int g_nPlayThreadID = -1; +int g_nMusicFadeDir = 0; +int g_nMusicFade = 0; +int g_nMusicTweak = 0; +int g_nMusicSemaphore = 0; +bool g_bMusicIsPaused = false; +bool g_bMusicPause = false; +bool g_bAnotherMusicPauseFlag = false; +char g_szCurrentMusicName[0x30]; +char g_szTargetMusicName[0x30]; +int g_nActiveMusicStreams = 0; +bool g_bVagCmdsInitialized = false; +u32 time_of_last_unknown_rate_drive_op = 0; +ISO_DGOCommand sLoadDGO; +RPC_Dgo_Cmd sRPCBuff[1]; +constexpr int kRpcBuffSize = sizeof(RPC_Dgo_Cmd); + +void jak3_overlord_init_globals_iso() { + g_nISOInitFlag = 0; + s_MsgPacket_NotOnStackSync = 0; + g_nSyncMbx = -1; + g_nISOMbx = -1; + g_nDGOMbx = -1; + g_nDGOThread = -1; + g_nSTRThreadID = -1; + g_nISOThreadID = -1; + g_nPlayThreadID = -1; + g_nMusicFadeDir = 0; + g_nMusicFade = 0; + g_nMusicTweak = 0; + g_nMusicSemaphore = 0; + g_bMusicIsPaused = false; + g_bMusicPause = false; + g_szCurrentMusicName[0] = 0; + g_szTargetMusicName[0] = 0; + sLoadDGO = {}; + g_nActiveMusicStreams = 0; + g_bVagCmdsInitialized = false; + time_of_last_unknown_rate_drive_op = 0; + sRPCBuff[0] = {}; +} + +/*! + * Initialize the filesystem driver, then send a message to the sync messagebox + */ +void InitDriver() { + // init the filesystem + if (get_file_system()->Init() == 0) { + g_nISOInitFlag = 0; + } + lg::info("sending mbox for init driver"); + SendMbx(g_nSyncMbx, &s_MsgPacket_NotOnStackSync); +} + +/*! + * Check if there is anything in the sync mbox of the iso thread. + */ +u32 LookSyncMbx() { + MsgPacket* msg_packet; + auto poll_result = PollMbx(&msg_packet, g_nSyncMbx); + if (poll_result != KE_MBOX_NOMSG) { + sLoadDGO.sync_mbox_wait_count++; + } + return poll_result != KE_MBOX_NOMSG; +} + +/*! + * Wait on the message box to have a message. Unlike older versions, they finally figured out + * how to call ReceiveMbx instead of polling in a loop! + */ +u32 WaitMbx(s32 box) { + MsgPacket* msg_packet; + return ReceiveMbx(&msg_packet, box); +} + +u32 ISOThread(); +u32 DGOThread(); + +/*! + * Initialize the Filesystem and ISO Thread. + */ +void InitISOFS() { + // memset(&sLoadDGO,0,0x110); + sLoadDGO = {}; + sLoadDGO.last_id = -1; + g_nISOInitFlag = 1; + sLoadDGO.acked_cancel_id = -1; + sLoadDGO.selected_id = -1; + sLoadDGO.nosync_cancel_pending_flag = 0; + // g_pFileSystem = &g_ISOCDFileSystem; + sLoadDGO.request_cancel_id = -1; + sLoadDGO.nosync_cancel_ack = 0; + sLoadDGO.sync_sent_count = 0; + sLoadDGO.sync_mbox_wait_count = 0; + sLoadDGO.sync_ret_count = 0; + + MbxParam mbx_param; + mbx_param.option = 0; + mbx_param.attr = 0; + + g_nISOMbx = CreateMbx(&mbx_param); + ASSERT(g_nISOMbx >= 0); + g_nDGOMbx = CreateMbx(&mbx_param); + ASSERT(g_nDGOMbx >= 0); + g_nSyncMbx = CreateMbx(&mbx_param); + ASSERT(g_nSyncMbx >= 0); + + ThreadParam thread_param; + + thread_param.stackSize = 0x1100; + thread_param.entry = ISOThread; + thread_param.initPriority = 0x80; // changed to be lower priority + thread_param.attr = TH_C; + thread_param.option = 0; + strcpy(thread_param.name, "ISO"); + g_nISOThreadID = CreateThread(&thread_param); + ASSERT(g_nISOThreadID >= 0); + + thread_param.entry = DGOThread; + thread_param.initPriority = 0x38; + thread_param.attr = TH_C; + thread_param.stackSize = 0x900; + thread_param.option = 0; + strcpy(thread_param.name, "DGO"); + g_nDGOThread = CreateThread(&thread_param); + ASSERT(g_nDGOThread >= 0); + + thread_param.entry = STRThread; + thread_param.initPriority = 0x39; + thread_param.attr = TH_C; + thread_param.stackSize = 0x900; + thread_param.option = 0; + strcpy(thread_param.name, "STR"); + g_nSTRThreadID = CreateThread(&thread_param); + ASSERT(g_nSTRThreadID >= 0); + + thread_param.attr = TH_C; + thread_param.entry = PLAYThread; + thread_param.initPriority = 0x35; + thread_param.stackSize = 0x900; + thread_param.option = 0; + strcpy(thread_param.name, "Play"); + g_nPlayThreadID = CreateThread(&thread_param); + ASSERT(g_nPlayThreadID >= 0); + + StartThread(g_nISOThreadID, 0); + StartThread(g_nDGOThread, 0); + StartThread(g_nSTRThreadID, 0); + StartThread(g_nPlayThreadID, 0); + WaitMbx(g_nSyncMbx); + + const ISOFileDef* vagdir_file = FindISOFile("VAGDIR.AYB"); + if (vagdir_file) { + int load_status = LoadISOFileToIOP(vagdir_file, &g_VagDir, sizeof(g_VagDir)); + if (load_status) { + ASSERT(g_VagDir.vag_magic_1 == 0x41574756); + ASSERT(g_VagDir.vag_magic_2 == 0x52494444); + } else { + lg::warn("Failed to load vagdir file"); + g_VagDir.num_entries = 0; + } + } else { + lg::warn("Failed to find vagdir file"); + g_VagDir.num_entries = 0; + } + + // splash screen load was here... + ASSERT(g_nISOInitFlag == 0); +} + +const ISOFileDef* FindISOFile(const char* name) { + return get_file_system()->Find(name); +} + +s32 GetISOFileLength(const ISOFileDef* def) { + return get_file_system()->GetLength(def); +} + +void SetVagClock(ISO_VAGCommand* cmd) { + cmd->clockc = 0; + cmd->clocka = 0; + cmd->clockb = 0; + cmd->clockd = 0; + if (!cmd->m_pBaseFile) { + cmd->flags.file_disappeared = 1; + } else { + cmd->flags.clocks_set = 1; + if (cmd->stereo_sibling) { + cmd->stereo_sibling->flags.clocks_set = 1; + } + } +} + +/*! + * Start playing music, from an "external" command (from the ISO system). + */ +void IsoPlayMusicStream(ISO_VAGCommand* user_cmd) { + // must be flagged as music in the request. + ASSERT(user_cmd->music_flag); + + const char* name = user_cmd->name; + + // first, let's try to find an existing internal command that's streaming this music + ISO_VAGCommand* internal_cmd = FindMusicStreamName(name); + ISO_VAGCommand* stereo_internal_cmd = nullptr; + + if (!internal_cmd) { + // no existing command, so allocate one. + internal_cmd = SmartAllocMusicVagCommand(user_cmd, 0); + + if (!internal_cmd) { + ASSERT_NOT_REACHED(); // was only a warning + return; + } + + // set the active flags to false: + set_active_a(internal_cmd, 0); + set_active_b(internal_cmd, 0); + set_active_c(internal_cmd, 0); + set_active_a(user_cmd, 0); + set_active_b(user_cmd, 0); + set_active_c(user_cmd, 0); + + // clear flags related to actual streaming, since we're starting (or restarting) the stream + // from nothing. + user_cmd->flags.saw_chunks1 = 0; + user_cmd->flags.running = 0; + + // if we're reusing an already started stream, need to stop that one first. + // if ((*(uint*)&internal_cmd->bit_running & 0xffff00) != 0) { + if (internal_cmd->flags.clocks_set || internal_cmd->flags.file_disappeared) { + IsoStopVagStream(internal_cmd); + } + + // copy entire header + internal_cmd->unka = user_cmd->unka; + internal_cmd->unkb = user_cmd->unkb; + internal_cmd->status = user_cmd->status; + internal_cmd->active_a = user_cmd->active_a; + internal_cmd->active_b = user_cmd->active_b; + internal_cmd->active_c = user_cmd->active_c; + internal_cmd->pad = user_cmd->pad; + + internal_cmd->msg_type = user_cmd->msg_type; + internal_cmd->mbox_reply = user_cmd->mbox_reply; + internal_cmd->thread_to_wake = user_cmd->thread_to_wake; + internal_cmd->callback = user_cmd->callback; + + internal_cmd->m_pBaseFile = user_cmd->m_pBaseFile; + internal_cmd->priority = user_cmd->priority; + internal_cmd->file_def = user_cmd->file_def; + + // copy part of the command set by the user + internal_cmd->vag_file_def = user_cmd->vag_file_def; + internal_cmd->vag_dir_entry = user_cmd->vag_dir_entry; + ASSERT(strlen(name) < 0x30); + strncpy(internal_cmd->name, name, 0x30); + internal_cmd->play_volume = user_cmd->play_volume; + internal_cmd->id = user_cmd->id; + internal_cmd->plugin_id = user_cmd->plugin_id; + internal_cmd->maybe_sound_handler = user_cmd->maybe_sound_handler; + internal_cmd->oog = user_cmd->oog; + internal_cmd->dolby_pan_angle = user_cmd->dolby_pan_angle; + internal_cmd->art_flag = user_cmd->art_flag; + internal_cmd->movie_flag = user_cmd->movie_flag; + + // + InitVAGCmd(internal_cmd, 1); + + // check for stereo bit: + if ((internal_cmd->vag_dir_entry->words[1] & 0x400U) != 0) { + // allocate stereo command + stereo_internal_cmd = SmartAllocMusicVagCommand(user_cmd, 1); + if (!stereo_internal_cmd) { + // it failed - give up on this message! + ReleaseMessage(internal_cmd); + FreeVagCmd(internal_cmd); + internal_cmd = nullptr; + ASSERT_NOT_REACHED(); + } else { + // stop existing stereo command + // if ((*(uint*)&stereo_internal_cmd->bit_running & 0xffff00) != 0) { + if (stereo_internal_cmd->flags.clocks_set || stereo_internal_cmd->flags.file_disappeared) { + IsoStopVagStream(stereo_internal_cmd); + } + + // set up as a stereo secondary + stereo_internal_cmd->flags.stereo_secondary = 1; + internal_cmd->stereo_sibling = stereo_internal_cmd; + stereo_internal_cmd->stereo_sibling = internal_cmd; + + auto name_len = strlen(internal_cmd->name); + strncpyz(stereo_internal_cmd->name, internal_cmd->name, 0x31); + if (name_len < 0x30) { + strncpyz(stereo_internal_cmd->name + name_len, " (stereo)", 0x31 - name_len); + } + stereo_internal_cmd->id = ~internal_cmd->id; + stereo_internal_cmd->vag_dir_entry = internal_cmd->vag_dir_entry; + } + } + + // abort if no command + if (!internal_cmd) { + return; + } + + // start us out as paused + internal_cmd->flags.paused = 1; + if (stereo_internal_cmd) { + stereo_internal_cmd->flags.paused = 1; + } + + if (QueueMessage(internal_cmd, 5) == 0) { + // failed to queue, clear our commands and release the message. + FreeVagCmd(internal_cmd); + if ((internal_cmd->vag_dir_entry->words[1] & 0x400U) != 0) { + FreeVagCmd(stereo_internal_cmd); + } + ReleaseMessage(internal_cmd); + } else { + auto* vag_dir_entry = internal_cmd->vag_dir_entry; + if (!vag_dir_entry) { + // not really sure how this can happen... + internal_cmd->m_pBaseFile = nullptr; + ASSERT_NOT_REACHED(); // just so I can learn when this happens. + } else { + // need to understand this better, but it seems like we can pick between two different files + // to actually load from... + const ISOFileDef* filedef = nullptr; + if (((u32)vag_dir_entry->words[1] >> 0xb & 1) == 0) { + filedef = internal_cmd->file_def; + } else { + filedef = internal_cmd->vag_file_def; + } + + // open the file!! + ovrld_log(LogCategory::VAG_SETUP, "vag dir entry offset is {}", + vag_dir_entry->words[1] >> 16); + auto* base_file = get_file_system()->OpenWAD(filedef, vag_dir_entry->words[1] >> 16); + internal_cmd->m_pBaseFile = base_file; + + // determine the reading rate of the music + if (base_file) { + u32 rate_idx = 0x3c & (internal_cmd->vag_dir_entry->words[1] >> 10); + ASSERT((rate_idx % 4) == 0); + constexpr int rates[16] = {0xFA00, 0x1F40, 0x3E80, 0x5DC0, 0x7D00, 0x9C40, + 0xBB80, 0xDAC0, 0xAC44, 0x1589, 0x2B11, 0x409A, + 0x5622, 0x6BAB, 0x8133, 0x96BC}; + + int rate = rates[rate_idx]; + if (((internal_cmd->vag_dir_entry->words[1] >> 10) & 1) == 0) { + rate = rate << 2; + } else { + rate = rate << 3; + } + base_file->m_ReadRate = rate / 7; + } + } + + SetVagStreamName(internal_cmd, 0x30); + if (stereo_internal_cmd) { + SetVagStreamName(stereo_internal_cmd, 0x30); + } + + internal_cmd->callback = ProcessVAGData; + internal_cmd->status = EIsoStatus::OK_2; + internal_cmd->flags.paused = 0; + + if (stereo_internal_cmd) { + stereo_internal_cmd->flags.paused = 0; + } + internal_cmd->flags.running = 1; + if (stereo_internal_cmd) { + stereo_internal_cmd->flags.running = 1; + } + internal_cmd->status = EIsoStatus::OK_2; + set_active_a(internal_cmd, 1); + set_active_b(internal_cmd, 1); + + // load tweak value + g_nMusicFadeDir = 0; + g_nMusicFade = 0x10000; + g_nMusicTweak = 0x80; + for (u32 i = 0; i < gMusicTweakInfo.TweakCount; i++) { + if (strcmp(gMusicTweakInfo.MusicTweak[i].MusicName, name) == 0) { + g_nMusicTweak = gMusicTweakInfo.MusicTweak[i].VolumeAdjust; + } + } + } + + if (!internal_cmd) { + return; + } + } + SetVagClock(internal_cmd); +} + +/*! + * Get a VAG command ready for playback, but don't actually start the audio yet. + */ +void IsoQueueVagStream(ISO_VAGCommand* user_cmd) { + ASSERT(user_cmd); + ASSERT(!user_cmd->music_flag); // can't use music commands with this function + ASSERT(user_cmd->id); + ISO_VAGCommand* internal_stereo_cmd = nullptr; + + // mysterious case to reject a command + if (user_cmd->vag_dir_entry && (user_cmd->vag_dir_entry->words[1] & 0x400U) != 0 && + HowManyBelowThisPriority(user_cmd->priority_pq) < 2) { + ovrld_log(LogCategory::WARN, "mysterious rejection of a queued vag stream"); + return; + } + + // see if we already have a command for this + ISO_VAGCommand* internal_cmd = FindThisVagStream(user_cmd->name, user_cmd->id); + if (!internal_cmd) { + // try allocating one + internal_cmd = SmartAllocVagCmd(user_cmd); + if (!internal_cmd) { + // no more commands! + return; + } + + ovrld_log(LogCategory::VAG_SETUP, "IsoQueueVagStream allocating for {} {}", user_cmd->name, + user_cmd->id); + // clear active flags + set_active_a(internal_cmd, 0); + set_active_b(internal_cmd, 0); + set_active_c(internal_cmd, 0); + set_active_a(user_cmd, 0); + set_active_b(user_cmd, 0); + set_active_c(user_cmd, 0); + user_cmd->flags.saw_chunks1 = 0; + user_cmd->flags.running = 0; + // if ((*(uint*)&internal_cmd->bit_running & 0xffff00) != 0) { + // if we're playing it, stop it. + if (internal_cmd->flags.clocks_set || internal_cmd->flags.file_disappeared) { + IsoStopVagStream(internal_cmd); + } + + // copy entire header + internal_cmd->unka = user_cmd->unka; + internal_cmd->unkb = user_cmd->unkb; + internal_cmd->status = user_cmd->status; + internal_cmd->active_a = user_cmd->active_a; + internal_cmd->active_b = user_cmd->active_b; + internal_cmd->active_c = user_cmd->active_c; + internal_cmd->pad = user_cmd->pad; + + internal_cmd->msg_type = user_cmd->msg_type; + internal_cmd->mbox_reply = user_cmd->mbox_reply; + internal_cmd->thread_to_wake = user_cmd->thread_to_wake; + internal_cmd->callback = user_cmd->callback; + + internal_cmd->m_pBaseFile = user_cmd->m_pBaseFile; + internal_cmd->priority = user_cmd->priority; + internal_cmd->file_def = user_cmd->file_def; + + internal_cmd->vag_file_def = user_cmd->vag_file_def; + internal_cmd->vag_dir_entry = user_cmd->vag_dir_entry; + strncpy(internal_cmd->name, user_cmd->name, 0x30); + internal_cmd->id = user_cmd->id; + internal_cmd->play_volume = user_cmd->play_volume; + internal_cmd->plugin_id = user_cmd->plugin_id; + internal_cmd->maybe_sound_handler = user_cmd->maybe_sound_handler; + internal_cmd->oog = user_cmd->oog; + internal_cmd->dolby_pan_angle = user_cmd->dolby_pan_angle; + internal_cmd->art_flag = user_cmd->art_flag; + internal_cmd->movie_flag = user_cmd->movie_flag; + + InitVAGCmd(internal_cmd, 1); + + internal_cmd->flags.scanned = 1; + + // check if we're a stereo command + if ((internal_cmd->vag_dir_entry) && ((internal_cmd->vag_dir_entry->words[1] & 0x400U) != 0)) { + internal_stereo_cmd = SmartAllocVagCmd(user_cmd); + if (!internal_stereo_cmd) { + // allocating stereo failed, give up. + internal_cmd->flags.scanned = 0; + ASSERT_NOT_REACHED(); + ReleaseMessage(internal_cmd); + RemoveVagCmd(internal_cmd); + FreeVagCmd(internal_cmd); + internal_cmd = nullptr; + } else { + // if ((*(uint*)&internal_stereo_cmd->bit_running & 0xffff00) != 0) { + if (internal_stereo_cmd->flags.clocks_set || internal_stereo_cmd->flags.file_disappeared) { + IsoStopVagStream(internal_stereo_cmd); + } + internal_cmd->stereo_sibling = internal_stereo_cmd; + internal_stereo_cmd->flags.stereo_secondary = 1; + internal_stereo_cmd->stereo_sibling = internal_cmd; + auto name_len = strlen(internal_cmd->name); + strncpyz(internal_stereo_cmd->name, internal_cmd->name, 0x31); + if (name_len < 0x30) { + strncpyz(internal_stereo_cmd->name + name_len, " (stereo)", 0x31 - name_len); + } + internal_stereo_cmd->id = ~internal_cmd->id; + internal_stereo_cmd->vag_dir_entry = internal_cmd->vag_dir_entry; + internal_stereo_cmd->flags.scanned = 1; + } + } + + // return if alloc failed + if (!internal_cmd) { + return; + } + + if (QueueMessage(internal_cmd, 5) == 0) { + // queueing failed. + internal_cmd->flags.scanned = 0; + ASSERT_NOT_REACHED(); + RemoveVagCmd(internal_cmd); + FreeVagCmd(internal_cmd); + if ((internal_cmd->vag_dir_entry->words[1] & 0x400U) != 0) { + internal_stereo_cmd->flags.scanned = 0; + RemoveVagCmd(internal_stereo_cmd); + FreeVagCmd(internal_stereo_cmd); + } + ReleaseMessage(internal_cmd); + } else { + auto* vag_dir_entry = internal_cmd->vag_dir_entry; + if (!vag_dir_entry) { + // not really sure how this can happen... + internal_cmd->m_pBaseFile = nullptr; + // ASSERT_NOT_REACHED(); // just so I can learn when this happens. + } else { + // need to understand this better, but it seems like we can pick between two different files + // to actually load from... + const ISOFileDef* filedef = nullptr; + if (((u32)vag_dir_entry->words[1] >> 0xb & 1) == 0) { + filedef = internal_cmd->file_def; + } else { + filedef = internal_cmd->vag_file_def; + } + auto* base_file = get_file_system()->OpenWAD(filedef, vag_dir_entry->words[1] >> 16); + internal_cmd->m_pBaseFile = base_file; + if (!base_file) { + u32 rate_idx = 0x3c & (internal_cmd->vag_dir_entry->words[1] >> 10); + ASSERT((rate_idx % 4) == 0); + constexpr int rates[16] = {0xFA00, 0x1F40, 0x3E80, 0x5DC0, 0x7D00, 0x9C40, + 0xBB80, 0xDAC0, 0xAC44, 0x1589, 0x2B11, 0x409A, + 0x5622, 0x6BAB, 0x8133, 0x96BC}; + + int rate = rates[rate_idx]; + if (((internal_cmd->vag_dir_entry->words[1] >> 10) & 1) == 0) { + rate = rate << 2; + } else { + rate = rate << 3; + } + base_file->m_ReadRate = rate / 7; + } + } + if (user_cmd->art_flag != 0) { + internal_cmd->flags.art = 1; + } + if (user_cmd->movie_flag != 0) { + internal_cmd->flags.movie = 1; + } + + internal_cmd->flags.paused = 1; + SetNewVagCmdPri(internal_cmd, user_cmd->priority_pq); + if (internal_stereo_cmd) { + internal_stereo_cmd->flags.paused = 1; + internal_stereo_cmd->flags.scanned = 1; + SetNewVagCmdPri(internal_stereo_cmd, 10); + } + SetVagStreamName(internal_cmd, 0x30); + if (internal_stereo_cmd) { + SetVagStreamName(internal_stereo_cmd, 0x30); + } + internal_cmd->status = EIsoStatus::OK_2; + internal_cmd->callback = ProcessVAGData; + } + if (!internal_cmd) { + return; + } + } + SetVagClock(internal_cmd); +} + +/*! + * Actually start playback of audio, requested from ISO thread functions. + */ +void IsoPlayVagStream(ISO_VAGCommand* user_cmd) { + ASSERT(user_cmd); + ASSERT(!user_cmd->music_flag); + ISO_VAGCommand* stereo_cmd = user_cmd->stereo_sibling; + ISO_VAGCommand* internal_cmd = FindThisVagStream(user_cmd->name, user_cmd->id); + + // update to running only if we aren't already. + if (internal_cmd && (internal_cmd->flags.running == 0)) { + internal_cmd->play_volume = user_cmd->play_volume; + if (internal_cmd->flags.paused != 0) { + if (g_bExtPause) { + g_bExtResume = true; + } + if (internal_cmd->flags.saw_chunks1 == 0) { + internal_cmd->flags.paused = 0; + if (stereo_cmd) { + stereo_cmd->flags.paused = 0; + } + } else { + ovrld_log(LogCategory::VAG_SETUP, "IsoPlayVagStream is unpausing {}", internal_cmd->name); + UnPauseVAG(internal_cmd); + } + if (user_cmd->priority_pq < 3) { + // this printed a "ruins fix" message, seems like this is a total hack!! + SetNewVagCmdPri(user_cmd, 7); + } + } + internal_cmd->flags.running = 1; + if (stereo_cmd) { + stereo_cmd->flags.running = 1; + } + if (internal_cmd) { + SetVagClock(internal_cmd); + } + } +} + +void IsoStopVagStream(ISO_VAGCommand* cmd) { + auto id = cmd->id; + ISO_VAGCommand* internal_cmd; + + // handle music/audio separately here + if (cmd->music_flag != 0) { + if (id == 0) { // no id, must use name + if (cmd->name[0] == 0) { + ASSERT_NOT_REACHED(); // shouldn't happen + return; + } + + // terminate all with this name. + while (internal_cmd = FindMusicStreamName(cmd->name), internal_cmd) { + ovrld_log(LogCategory::VAG_SETUP, "IsoStopVagStream is terminating {} (1)", + internal_cmd->name); + TerminateVAG(internal_cmd); + } + return; + } + + // we have an id, just terminate that. + internal_cmd = FindThisMusicStream(cmd->name, id); + if (!internal_cmd) { + return; + } + ovrld_log(LogCategory::VAG_SETUP, "IsoStopVagStream is terminating {} (2)", internal_cmd->name); + TerminateVAG(internal_cmd); + return; + } + + // flag - this controls if we do the AnyVagRunning check or not. + // not really sure why... + bool flag = false; + + if (id == 0) { + if (cmd->name[0] == 0) { + return; + } + while (internal_cmd = FindVagStreamName(cmd->name), internal_cmd) { + flag = true; + ovrld_log(LogCategory::VAG_SETUP, "IsoStopVagStream is terminating {} (3)", + internal_cmd->name); + TerminateVAG(internal_cmd); + } + + if (!flag) { + return; + } + } else { + internal_cmd = FindThisVagStream(cmd->name, id); + if (!internal_cmd) { + return; + } + ovrld_log(LogCategory::VAG_SETUP, "IsoStopVagStream is terminating {} (4)", internal_cmd->name); + TerminateVAG(internal_cmd); + } + + if (AnyVagRunning() == 0) { + g_bExtPause = false; + g_bExtResume = false; + } +} + +void ProcessMusic() { + ISO_VAGCommand* cmd = nullptr; + WaitSema(g_nMusicSemaphore); + + // handle unpausing request + if (g_bMusicIsPaused && !g_bMusicPause && !g_bAnotherMusicPauseFlag) { + cmd = FindMusicStreamName(g_szCurrentMusicName); + if (cmd && cmd->id && !cmd->flags.stop) { // can't unpause if stopped. + UnPauseVAG(cmd); + } + g_bMusicIsPaused = false; + } + + // handle pausing request. + if (!g_bMusicIsPaused && g_bMusicPause) { + cmd = FindMusicStreamName(g_szCurrentMusicName); + if (cmd && cmd->id & !cmd->flags.stop) { + PauseVAG(cmd); + } + g_bMusicIsPaused = true; + } + + // handle playing music updates. + if (g_bMusicIsPaused == 0) { + // first, see if we're even playing the right music: + if (strncmp(g_szCurrentMusicName, g_szTargetMusicName, 0xf) == 0) { + // we are! fade in the music, if it's active: + if (0 < g_nActiveMusicStreams && g_nMusicFadeDir < 0) { + g_nMusicFadeDir = 1; + } + + if ((g_szCurrentMusicName[0] == 0) || (g_nActiveMusicStreams != 0)) { + SignalSema(g_nMusicSemaphore); + return; + } + + } else { + // we aren't. fade out music if the target is null, or we're currently playing music. + if ((g_szTargetMusicName[0] == 0) || (g_szCurrentMusicName[0] != 0)) { + if (g_nMusicFade < 1) { + cmd = g_aVagCmds + 4; + int i = 1; + do { + i--; + // stop any non-stereo, but real music. + if (cmd->music_flag && !cmd->flags.stereo_secondary && cmd->id) { + IsoStopVagStream(cmd); + } + cmd = cmd + 1; + } while (-1 < i); + } else { + g_nMusicFadeDir = -1; + } + } + if (g_nMusicFade != 0) { + SignalSema(g_nMusicSemaphore); + return; + } + + // if we made it through that, we're done playing old stuff. + strncpyz(g_szCurrentMusicName, g_szTargetMusicName, 0x10); + } + + if (g_szCurrentMusicName[0] != 0) { + VagStreamData vsd; + strncpy(vsd.name, g_szCurrentMusicName, 0x30); + vsd.id = 0x29a; + vsd.priority = 9; + vsd.art_load = 0; + vsd.movie_art_load = 0; + vsd.sound_handler = 0; + ovrld_log(LogCategory::VAG_SETUP, "ProcessMusic is changing the music to {}", vsd.name); + PlayMusicStream(&vsd); + } + } + + SignalSema(g_nMusicSemaphore); +} + +u32 ISOThread() { + int priority = -1; + g_szCurrentMusicName[0] = 0; + g_szTargetMusicName[0] = 0; + g_bMusicIsPaused = false; + + lg::info("top of ISO Thread"); + + // file = (CISOCDFile*)0x0; + InitBuffers(); + // bVar1 = false; + InitVagCmds(); + g_bVagCmdsInitialized = true; + InitDriver(); + + ISO_Hdr* mbx_cmd = nullptr; + ISO_LoadSoundbank* load_sbk_cmd = nullptr; + ISO_LoadCommon* load_cmd = nullptr; + char local_name[32]; + ISO_VAGCommand* vag_cmd = nullptr; + ISO_VAGCommand* internal_vag_cmd = nullptr; + + // ISOFileDef* file_def = nullptr; + + while (true) { + dma_intr_hack(); + // Part 1: Handle incoming messages from the user: + + int poll_result = PollMbx((MsgPacket**)&mbx_cmd, g_nISOMbx); + if (poll_result == KE_OK) { + if (mbx_cmd->msg_type == ISO_Hdr::MsgType::ABADBABE) { + // what is this garbage + ASSERT_NOT_REACHED(); + ReleaseMessage(mbx_cmd); + } else { + set_active_a(mbx_cmd, 0); + set_active_b(mbx_cmd, 0); + set_active_c(mbx_cmd, 0); + + // iVar3 = (cmd->header).kind; + mbx_cmd->callback = NullCallback; + mbx_cmd->m_pBaseFile = nullptr; + auto msg_kind = mbx_cmd->msg_type; + + ovrld_log(LogCategory::ISO_QUEUE, "Incoming message to the ISO Queue with type 0x{:x}", + (int)msg_kind); + + // if we're a simple file loading command: + if (msg_kind == ISO_Hdr::MsgType::LOAD_EE || msg_kind == ISO_Hdr::MsgType::LOAD_EE_CHUNK || + msg_kind == ISO_Hdr::MsgType::LOAD_IOP || + msg_kind == ISO_Hdr::MsgType::LOAD_SOUNDBANK) { + priority = 3; // default priority for file loads is 3... unless we're loading a soundbank + // in which case there's a bizarre special case here: + load_cmd = (ISO_LoadCommon*)mbx_cmd; + + if (msg_kind == ISO_Hdr::MsgType::LOAD_SOUNDBANK) { + load_sbk_cmd = (ISO_LoadSoundbank*)mbx_cmd; + priority = 0; + if (load_sbk_cmd->priority == 2) { + priority = 2; + } else { + if (load_sbk_cmd->priority == 10) { + priority = 4; + } + } + } + + if (QueueMessage(mbx_cmd, priority) == 0) { + ovrld_log(LogCategory::WARN, "Failed to queue incoming iso message"); + goto LAB_00006b18; + } + + // iVar3 = (cmd->header).kind; + // handle opening the file: + switch (msg_kind) { + case ISO_Hdr::MsgType::LOAD_EE_CHUNK: { + ovrld_log(LogCategory::ISO_QUEUE, "Opening File {} for EE Chunk Load offset {}", + mbx_cmd->file_def->name.data, ((ISO_LoadSingle*)mbx_cmd)->sector_offset); + mbx_cmd->m_pBaseFile = get_file_system()->Open( + mbx_cmd->file_def, ((ISO_LoadSingle*)mbx_cmd)->sector_offset, 1); + } break; + case ISO_Hdr::MsgType::LOAD_IOP: + case ISO_Hdr::MsgType::LOAD_EE: + ovrld_log(LogCategory::ISO_QUEUE, "Opening File {} for Load {}", + msg_kind == ISO_Hdr::MsgType::LOAD_EE ? "EE" : "IOP", + mbx_cmd->file_def->name.data); + mbx_cmd->m_pBaseFile = get_file_system()->Open(mbx_cmd->file_def, -1, 1); + break; + case ISO_Hdr::MsgType::LOAD_SOUNDBANK: { + ovrld_log(LogCategory::ISO_QUEUE, "Opening for LOAD_SOUNDBANK {} ", + load_sbk_cmd->name); + // build name + ASSERT(load_sbk_cmd->name); + strncpy(local_name, load_sbk_cmd->name, 0xc); + local_name[8] = 0; + strcat(local_name, ".sbk"); + mbx_cmd->file_def = get_file_system()->Find(local_name); + ASSERT(mbx_cmd->file_def); + mbx_cmd->m_pBaseFile = get_file_system()->Open(mbx_cmd->file_def, -1, 1); + ASSERT(mbx_cmd->m_pBaseFile); + } break; + default: + ASSERT_NOT_REACHED(); + } + + // if we failed to open, bail + if (!mbx_cmd->m_pBaseFile) { + ASSERT_NOT_REACHED(); + mbx_cmd->status = EIsoStatus::ERROR_OPENING_FILE_8; + UnqueueMessage(mbx_cmd); + ReturnMessage(mbx_cmd); + // this goes somewhere else... + } + + // set up lengths based on the actual file length on disc. + load_cmd->dest_ptr = load_cmd->addr; + load_cmd->progress_bytes = 0; + load_cmd->length_to_copy = get_file_system()->GetLength(mbx_cmd->file_def); + // pIVar5 = length + if (msg_kind == ISO_Hdr::MsgType::LOAD_SOUNDBANK) { + load_cmd->maxlen = load_cmd->length_to_copy; + } else { + ASSERT(load_cmd->length_to_copy); + // trim copy size to the max buffer length given. + if (load_cmd->length_to_copy > load_cmd->maxlen) { + load_cmd->length_to_copy = load_cmd->maxlen; + } + } + + // set up callback + switch (msg_kind) { + case ISO_Hdr::MsgType::LOAD_IOP: + mbx_cmd->callback = CopyDataToIOP; + break; + case ISO_Hdr::MsgType::LOAD_SOUNDBANK: + mbx_cmd->callback = CopyDataSbkLoad; + break; + case ISO_Hdr::MsgType::LOAD_EE: + case ISO_Hdr::MsgType::LOAD_EE_CHUNK: + mbx_cmd->callback = CopyDataToEE; + break; + default: + ASSERT_NOT_REACHED(); + } + + mbx_cmd->status = EIsoStatus::OK_2; + set_active_a(mbx_cmd, 1); + } else { + switch (msg_kind) { + case ISO_Hdr::MsgType::DGO_LOAD: + if (QueueMessage(mbx_cmd, 1) != 0) { + // modified for non compressed dgos + ovrld_log(LogCategory::ISO_QUEUE, "Opening {} for DGO Load", + mbx_cmd->file_def->name.data); + mbx_cmd->m_pBaseFile = get_file_system()->Open(mbx_cmd->file_def, -1, 1); + if (mbx_cmd->m_pBaseFile) { + mbx_cmd->callback = RunDGOStateMachine; + mbx_cmd->status = EIsoStatus::OK_2; + ((ISO_DGOCommand*)mbx_cmd)->state = ISO_DGOCommand::State::INIT; + set_active_a(mbx_cmd, 1); + } else { + ASSERT_NOT_REACHED(); + UnqueueMessage(mbx_cmd); + ASSERT(sLoadDGO.msg_type != ISO_Hdr::MsgType::MSG_0); + SendMbx(g_nISOMbx, &sLoadDGO); + } + } + break; + case ISO_Hdr::MsgType::VAG_PAUSE: + ovrld_log(LogCategory::ISO_QUEUE, "VagPause (all of them)"); + if (g_bExtPause == 0) { + SetVagStreamsNoStart(1); + int iVar3 = AnyVagRunning(); + if (iVar3 != 0) { + PauseVagStreams(0); + } + g_bExtPause = true; + g_bExtResume = iVar3 != 0; + } + ReturnMessage(mbx_cmd); + break; + case ISO_Hdr::MsgType::VAG_UNPAUSE: + ovrld_log(LogCategory::ISO_QUEUE, "VagUnPause (all of them)"); + if (g_bExtPause != 0) { + if (g_bExtResume != false) { + UnPauseVagStreams(0); + } + g_bExtPause = false; + g_bExtResume = false; + } + SetVagStreamsNoStart(0); + ReturnMessage(mbx_cmd); + break; + case ISO_Hdr::MsgType::VAG_SET_PITCH_VOL: + vag_cmd = (ISO_VAGCommand*)mbx_cmd; + ovrld_log(LogCategory::ISO_QUEUE, "VAG_SET_PITCH_VOL (id {})", vag_cmd->id); + internal_vag_cmd = FindVagStreamId(vag_cmd->id); + if (internal_vag_cmd) { + ovrld_log(LogCategory::ISO_QUEUE, "VAG_SET_PITCH_VOL lookup ok, got {}", + internal_vag_cmd->name); + internal_vag_cmd->pitch_cmd = vag_cmd->pitch_cmd; + SetVAGVol(internal_vag_cmd); + } + ReturnMessage(vag_cmd); + break; + case ISO_Hdr::MsgType::ADEADBEE: + ReturnMessage(vag_cmd); + ExitThread(); + goto LAB_00006b18; + break; + default: + ASSERT_NOT_REACHED(); + } + } + } + } else { + if (poll_result == -0x1a9) { + // messagebox was deleted - this means we're shutting down + return 0; + } + if (poll_result != -0x1a8) { + // unknown messagebox error + ASSERT_NOT_REACHED(); + } + } + LAB_00006b18: + // Part 2: music update + // Poll is called here... but we don't use it. + // (**(code**)(*g_pFileSystem + 4))(); + ProcessMusic(); + + // Part 3: service in-progress messages + // get the top priority message + bool buffer_ok = false; + auto* cmd = GetMessage(); + CBaseFile* file = nullptr; + bool known_read_rate = false; + + if (cmd) { + // handle the buffering + // ovrld_log(LogCategory::ISO_QUEUE, "Processing Command 0x{:x} - allocating buffer\n", + // (int)cmd->msg_type); + + // check if we need to initialize a buffer, or if we just need to realloc pages + file = cmd->m_pBaseFile; + bool needs_buffer_init = false; + if (!file) { + needs_buffer_init = true; + // we'd need to set buffer_ok = false later on if this is the case, + // but I dont think this can happen. + ASSERT_NOT_REACHED(); + } else { + if (file->m_Buffer.m_eBufferType == CBuffer::BufferType::EBT_FREE) { + needs_buffer_init = true; + } + } + + // set up buffer + if (needs_buffer_init) { + buffer_ok = + file->InitBuffer(cmd->callback == ProcessVAGData ? CBuffer::BufferType::REQUEST_VAG + : CBuffer::BufferType::REQUEST_NORMAL, + cmd); + } else { + file->AllocPages(); + buffer_ok = true; + } + + file = nullptr; + if (buffer_ok == 0) { + cmd = nullptr; + known_read_rate = false; + } else { + file = cmd->m_pBaseFile; + known_read_rate = false; + if (file && file->m_ReadRate) { + known_read_rate = true; + } + // iVar3 = (**(code**)(file->base).vtable)(file); + // ovrld_log(LogCategory::ISO_QUEUE, "Processing Command 0x{:x} - starting read!\n", + // (int)cmd->msg_type); + + // lg::info("ISO - BeginRead"); + cmd->status = file->BeginRead(); + if (cmd->status != EIsoStatus::OK_2) { + buffer_ok = false; + if (cmd->m_pBaseFile) { + cmd->m_pBaseFile->TerminateBuffer(); + } + cmd = nullptr; + file = nullptr; + } + if (!known_read_rate) { + time_of_last_unknown_rate_drive_op = GetSystemTimeLow(); + } + } + } + + // ovrld_log(LogCategory::ISO_QUEUE, "Processing Command 0x{:x} - handling message data\n", + // (int)cmd->msg_type); + + if (ProcessMessageData(cmd) == 0) { + cmd = nullptr; + } + + if (buffer_ok && cmd) { + EIsoStatus status = EIsoStatus::ERROR_b; + if (file) { + status = file->SyncRead(); + } + if (!known_read_rate) { + time_of_last_unknown_rate_drive_op = GetSystemTimeLow(); + } + if (status == EIsoStatus::ERROR_b) { + if (cmd->m_pBaseFile && cmd->m_pBaseFile->m_Status != EIsoStatus::NONE_0) { + cmd->status = EIsoStatus::OK_2; + } + } else { + cmd->status = status; + if (!cmd->active_c) { + set_active_c(cmd, 1); + } + } + } + + WaitSema(g_RequestedStreamsList.sema); + if (g_RequestedStreamsList.pending_data == 1) { + QueueNewStreamsFromList(&g_RequestedStreamsList); + auto* vag_info = g_NewStreamsList.next; + for (int i = 0; i < 4; i++) { + if (vag_info->id) { + ovrld_log(LogCategory::ISO_QUEUE, "ISO thread: queueing VAG {}", vag_info->name); + QueueVAGStream(vag_info); + } + vag_info = vag_info->next; + } + } + + for (int i = 0; i < 4; i++) { + ISO_VAGCommand* vc = &g_aVagCmds[i]; + if (!vc->music_flag && !vc->flags.stereo_secondary && !vc->flags.scanned && vc->id) { + ovrld_log(LogCategory::ISO_QUEUE, "ISO thread: stopping {} since it is no longer requested", + vc->name); + IsoStopVagStream(vc); + } + } + + SignalSema(g_RequestedStreamsList.sema); + g_RequestedStreamsList.pending_data = 0; + + for (int i = 4; i < 6; i++) { + ISO_VAGCommand* vc = &g_aVagCmds[i]; + if (vc->music_flag && !vc->flags.stereo_secondary && vc->flags.stop && vc->id) { + ovrld_log(LogCategory::ISO_QUEUE, "ISO thread: stopping music {}", vc->name); + IsoStopVagStream(vc); + } + } + + // this logic was changed so that the iso thread doesn't sleep when loading a DGO: otherwise + // we'd spent most of our time letting the thread sleep. + // instead, if there's an in progress DGO command, we yield. + // this yield allows DGO RPCs to run. Additionally, the priority of the ISO thread was lowered + // to allow the RPCs to run during this time. + bool sleep = !DgoCmdWaiting(); + if (sleep) { + if (buffer_ok) { + DelayThread(4000); + } else { + // DelayThread(200); + DelayThread(2000); + } + } else { + YieldThread(); + } + } +} + +/*! + * This function runs the state machine for the double-buffered DGO loading system. + * There are a few tricks here: + * - Each DGO file contains a number of objects. + * - The object loading is double buffered - this state machine toggles between loading to two + * different buffers. While one buffer is being written, the GOAL linker is processing the other. + * - The final object is not double buffered. Instead, it is loaded directly to the top of the heap. + * - New! for jak 2, there is an option to not use the double buffering. + * - New! for jak 3, there is a very complicated load cancel system + */ +EIsoStatus RunDGOStateMachine(ISO_Hdr* m) { + auto* cmd = (ISO_DGOCommand*)m; + // lg::info("ISO - DGO state machine"); + int send_count, receive_count; + + CBaseFile* file = cmd->m_pBaseFile; + EIsoStatus ret_status = EIsoStatus::OK_2; + if (!file) { + return EIsoStatus::OK_2; + } + ASSERT(file->m_Buffer.m_pPageList); + + // handle page boundary crossings - after this call, our CBuffer will be set up properly for + // processing. + file->CheckPageBoundary(); + CBuffer* buffer = &file->m_Buffer; + + int buffer_len = (file->m_Buffer).m_nDataLength; + ASSERT(buffer_len >= 0); + + if (cmd->state == ISO_DGOCommand::State::INIT) { + // these counters are used for debugging the DGO sync stuff. + cmd->sync_mbox_wait_count = 1; + cmd->sync_ret_count = 0; + } + // CpuSuspendIntr(local_30); + + // process this DGO as normal, unless we've been asked to cancel this. + if (cmd->nosync_cancel_pending_flag == 0 || cmd->selected_id != cmd->request_cancel_id) { + // CpuResumeIntr(); + if (buffer_len == 0) { + // nothing we can do with no data... + goto out_of_data; + } + do { + switch (cmd->state) { + case ISO_DGOCommand::State::INIT: + ovrld_log(LogCategory::DGO, "DGO: Starting state machine"); + cmd->state = ISO_DGOCommand::State::READ_DGO_HEADER; + cmd->bytes_processed = 0; + cmd->finished_first_object = 0; + cmd->want_abort = 0; + break; + case ISO_DGOCommand::State::READ_DGO_HEADER: { + // here, we work on reading the DGO file's header into our command. + // first, compute how many bytes we want to read right now, as the max of + // the remaining header size, and what's buffered + int bytes_needed = sizeof(DgoHeader) - cmd->bytes_processed; + if (buffer_len < bytes_needed) { + bytes_needed = buffer_len; + } + + // loop over pages - the header may span multiple pages that aren't adjacent in memory. + while (bytes_needed) { + // determine how many bytes to copy from this page + int bytes_from_this_page = buffer->m_pPageList->m_pCurrentActivePage->m_pPageMemEnd - + file->m_Buffer.m_pCurrentData + 1; + if (bytes_needed <= bytes_from_this_page) { + bytes_from_this_page = bytes_needed; + } + ovrld_log(LogCategory::DGO, "DGO: reading {} bytes of dgo header", + bytes_from_this_page); + // copy data from buffer into command + memcpy(((u8*)&cmd->dgo_header) + cmd->bytes_processed, file->m_Buffer.m_pCurrentData, + bytes_from_this_page); + + // advance buffer and page + buffer->AdvanceCurrentData(bytes_from_this_page); + file->CheckPageBoundary(); + + // advance progress + cmd->bytes_processed = bytes_from_this_page + cmd->bytes_processed; + buffer_len = buffer_len - bytes_from_this_page; + bytes_needed = bytes_needed - bytes_from_this_page; + } + + // check if we got the whole header + if (cmd->bytes_processed == sizeof(DgoHeader)) { + ovrld_log(LogCategory::DGO, "DGO: got dgo header: {} with {} objects", + cmd->dgo_header.name, cmd->dgo_header.object_count); + cmd->bytes_processed = 0; + cmd->objects_loaded = 0; + if (cmd->dgo_header.object_count == 1) { + // if we have only 1 object, go directly to loading to the top buffer + cmd->ee_dest_buffer = cmd->buffer_top; + cmd->state = ISO_DGOCommand::State::READ_OBJ_HEADER; + cmd->buffer_toggle = 0; + } else { + // otherwise, start with buffer! + cmd->buffer_toggle = 1; + cmd->ee_dest_buffer = cmd->buffer1; + cmd->state = ISO_DGOCommand::State::READ_OBJ_HEADER; + } + } + } break; + case ISO_DGOCommand::State::FINISH_OBJ: + + // sync with EE - if we're loading double-buffered, wait on the EE + // note that we don't wait on the first object, since both buffers start empty, + // and we can safely fill both with no syncs. + // the order of synchronization is a little bit strange. The EE must tell us that it's + // finished processing buffer A before we tell the EE the location of buffer B. + // This is needed to get the sync right for the last object - we want the EE to run + // through all the buffers, then we load the final object, then we notify it. If the EE + // wouldn't tell us it was done until it got the next object, we'd be unable to do this. + if (cmd->finished_first_object != 0 && cmd->buffer1 != cmd->buffer2) { + if (LookSyncMbx() == 0) + goto exit_no_sync; + + ovrld_log(LogCategory::DGO, + "DGO: finished object (2buffer), and got sync message from EE or cancel"); + // iVar3 = 6; + if (cmd->want_abort != 0) { + ovrld_log(LogCategory::DGO, "DGO: cancel!! (1)"); + cmd->state = ISO_DGOCommand::State::FINISH_DGO; + break; + } + } + + // for double buffer, notify the EE that we've finished loading. + if (cmd->buffer1 != cmd->buffer2) { + cmd->status = EIsoStatus::OK_2; + cmd->selected_buffer = cmd->buffer_toggle != 1 ? cmd->buffer2 : cmd->buffer1; + ovrld_log(LogCategory::DGO, + "DGO: finished object (2buffer) - notifying EE of location"); + ReturnMessage(cmd); + sLoadDGO.sync_ret_count = sLoadDGO.sync_ret_count + 1; + } + + // for single buffer, sync with EE so we know the next location to load. + // note that we've already returned the message for the single buffer case + if ((cmd->buffer1 == cmd->buffer2) && + (cmd->objects_loaded + 1 < (s32)cmd->dgo_header.object_count)) { + if (LookSyncMbx() == 0) + goto exit_no_sync; + ovrld_log(LogCategory::DGO, + "DGO: finished object (1buffer), and got sync message from EE or cancel"); + if (cmd->want_abort != 0) { + ovrld_log(LogCategory::DGO, "DGO: cancel!! (2)"); + cmd->state = ISO_DGOCommand::State::FINISH_DGO; + break; + } + } + cmd->finished_first_object = 1; + if (cmd->buffer_toggle == 1) { + cmd->ee_dest_buffer = cmd->buffer2; + cmd->buffer_toggle = 2; + } else { + cmd->buffer_toggle = 1; + cmd->ee_dest_buffer = cmd->buffer1; + } + + if (cmd->objects_loaded + 1 == (int)cmd->dgo_header.object_count) { + cmd->state = ISO_DGOCommand::State::READ_LAST_OBJ; + } else { + cmd->state = ISO_DGOCommand::State::READ_OBJ_HEADER; + } + // LAB_000073f8: + // cmd->state = iVar3; + break; + case ISO_DGOCommand::State::READ_LAST_OBJ: + // do an extra sync here to wait for the EE to finish processing both temporary buffers. + // the next load will be to the heap top, which may overlap the temp buffers. + // lg::warn("in read last obj!"); + if (LookSyncMbx() == 0) + goto exit_no_sync; + ovrld_log(LogCategory::DGO, + "DGO: got final object sync message - can start running that now"); + if (cmd->want_abort != 0) { + cmd->state = ISO_DGOCommand::State::FINISH_DGO; + ovrld_log(LogCategory::DGO, "DGO: cancel!! (3)"); + } else { + cmd->ee_dest_buffer = cmd->buffer_top; + cmd->state = ISO_DGOCommand::State::READ_OBJ_HEADER; + cmd->buffer_toggle = 0; + } + + break; + case ISO_DGOCommand::State::READ_OBJ_HEADER: { + int bytes_needed = sizeof(ObjectHeader) - cmd->bytes_processed; + if (buffer_len < bytes_needed) { + bytes_needed = buffer_len; + } + while (bytes_needed) { + int bytes_from_this_page = buffer->m_pPageList->m_pCurrentActivePage->m_pPageMemEnd - + file->m_Buffer.m_pCurrentData + 1; + if (bytes_needed <= bytes_from_this_page) { + bytes_from_this_page = bytes_needed; + } + ASSERT(bytes_from_this_page >= 0); + ovrld_log(LogCategory::DGO, "DGO: reading {} bytes of object header", + bytes_from_this_page); + memcpy(((u8*)&cmd->obj_header) + cmd->bytes_processed, (file->m_Buffer).m_pCurrentData, + bytes_from_this_page); + buffer->AdvanceCurrentData(bytes_from_this_page); + file->CheckPageBoundary(); + cmd->bytes_processed = bytes_from_this_page + cmd->bytes_processed; + buffer_len = buffer_len - bytes_from_this_page; + bytes_needed = bytes_needed - bytes_from_this_page; + } + if (cmd->bytes_processed == sizeof(ObjectHeader)) { + ovrld_log(LogCategory::DGO, "DGO: got object header {} {}", cmd->obj_header.name, + cmd->obj_header.size); + cmd->obj_header.size = (cmd->obj_header.size + 0xf) & 0xfffffff0; + DMA_SendToEE(cmd->ee_dest_buffer, &cmd->obj_header, sizeof(ObjectHeader), nullptr, + nullptr); + cmd->ee_dest_buffer = cmd->ee_dest_buffer + sizeof(ObjectHeader); + cmd->state = ISO_DGOCommand::State::READ_OBJ_DATA; + cmd->bytes_processed = 0; + } + } break; + case ISO_DGOCommand::State::READ_OBJ_DATA: { + int bytes_needed = cmd->obj_header.size - cmd->bytes_processed; + if (buffer_len < bytes_needed) { + bytes_needed = buffer_len; + } + + while (bytes_needed) { + auto* page = buffer->m_pPageList->m_pCurrentActivePage; + int bytes_from_this_page = page->m_pPageMemEnd - file->m_Buffer.m_pCurrentData + 1; + if (bytes_needed <= bytes_from_this_page) { + bytes_from_this_page = bytes_needed; + } + int ret = page->AddDmaRef(); + ASSERT(ret >= 0); + + DMA_SendToEE(cmd->ee_dest_buffer, (file->m_Buffer).m_pCurrentData, bytes_from_this_page, + CopyDataDmaCallback, page); + buffer->AdvanceCurrentData(bytes_from_this_page); + file->CheckPageBoundary(); + cmd->ee_dest_buffer = bytes_from_this_page + cmd->ee_dest_buffer; + cmd->bytes_processed = bytes_from_this_page + cmd->bytes_processed; + buffer_len = buffer_len - bytes_from_this_page; + bytes_needed = bytes_needed - bytes_from_this_page; + + if (!file->m_Buffer.m_pCurrentData) { + buffer_len = 0; + break; + } + } + + if (cmd->bytes_processed == (int)cmd->obj_header.size) { + cmd->objects_loaded = cmd->objects_loaded + 1; + if (cmd->objects_loaded < (int)cmd->dgo_header.object_count) { + if (cmd->buffer1 == cmd->buffer2) { + cmd->state = ISO_DGOCommand::State::FINISH_OBJ_SINGLE_BUFFER; + } else { + cmd->state = ISO_DGOCommand::State::FINISH_OBJ; + } + cmd->bytes_processed = 0; + } else { + ret_status = EIsoStatus::NONE_0; + cmd->state = ISO_DGOCommand::State::FINISH_DGO; + } + } + } break; + case ISO_DGOCommand::State::FINISH_DGO: + ret_status = EIsoStatus::NONE_0; + file->m_Buffer.m_pCurrentData = nullptr; + file->m_Buffer.m_pCurrentPageStart = nullptr; + goto out_of_data; + case ISO_DGOCommand::State::FINISH_OBJ_SINGLE_BUFFER: + cmd->status = EIsoStatus::OK_2; + if (cmd->buffer_toggle == 1) { + cmd->selected_buffer = cmd->buffer1; + } else { + cmd->selected_buffer = cmd->buffer2; + } + ReturnMessage((ISO_VAGCommand*)cmd); + sLoadDGO.sync_ret_count = sLoadDGO.sync_ret_count + 1; + cmd->state = ISO_DGOCommand::State::FINISH_OBJ; + } + } while (buffer_len); + exit_no_sync: + if (ret_status != EIsoStatus::NONE_0) + goto LAB_0000743c; + } else { + cmd->nosync_cancel_ack = 1; + cmd->nosync_cancel_pending_flag = 0; + cmd->acked_cancel_id = cmd->request_cancel_id; + send_count = sLoadDGO.sync_sent_count - sLoadDGO.sync_mbox_wait_count; + receive_count = sLoadDGO.sync_ret_count - sLoadDGO.sync_mbox_wait_count; + // CpuResumeIntr(local_30[0]); + if (0 < send_count) { + receive_count = receive_count + -1; + WaitMbx(g_nSyncMbx); + sLoadDGO.sync_mbox_wait_count = sLoadDGO.sync_mbox_wait_count + 1; + } + ret_status = EIsoStatus::IDLE_1; + if (-1 < receive_count) { + LAB_0000743c: + if (buffer_len) { + file->m_Buffer.m_nDataLength = buffer_len; + return ret_status; + } + goto out_of_data; + } + ret_status = EIsoStatus::NONE_0; + } + (file->m_Buffer).m_pCurrentData = nullptr; + (file->m_Buffer).m_pCurrentPageStart = nullptr; +out_of_data: + (file->m_Buffer).m_nDataLength = 0; + return ret_status; +} + +u32 DGOThread() { + sceSifQueueData dq; + sceSifServeData serve; + + // setup RPC. + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, RpcId::DGO, RPC_DGO, sRPCBuff, kRpcBuffSize, nullptr, nullptr, &dq); + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} + +void* RPC_DGO(unsigned int fno, void* msg_ptr, int) { + RPC_Dgo_Cmd* cmd = (RPC_Dgo_Cmd*)msg_ptr; + switch (fno) { + case DgoFno::LOAD: + LoadDGO(cmd); + break; + case DgoFno::LOAD_NEXT: + LoadNextDGO(cmd); + break; + case DgoFno::CANCEL: + CancelDGO(cmd); + break; + default: + cmd->status = 1; + ASSERT_NOT_REACHED(); + } + return msg_ptr; +} + +void* foo = 0; + +/*! + * Send a sync message to unblock the DGO thread when doing an async cancel. + */ +bool NotifyDGO() { + // CpuSuspendIntr(local_10); + bool pending_cancel = sLoadDGO.nosync_cancel_ack == 0; + if (pending_cancel) { + sLoadDGO.sync_sent_count = sLoadDGO.sync_sent_count + 1; + } + // CpuResumeIntr(local_10[0]); + if (pending_cancel) { + SendMbx(g_nSyncMbx, &foo); + } + return pending_cancel; +} + +void LoadDGO(RPC_Dgo_Cmd* cmd) { + ISOFileDef* file = get_file_system()->Find(cmd->name); + + if (!file) { + ovrld_log(LogCategory::WARN, "DGO RPC: LoadDGO {} file not found\n", cmd->name); + cmd->status = 1; + return; + } + if (sLoadDGO.last_id < cmd->cgo_id) { + ovrld_log(LogCategory::RPC, "DGO RPC: new command ID, starting a load for {}\n", cmd->name); + CancelDGO(nullptr); + sLoadDGO.msg_type = ISO_Hdr::MsgType::DGO_LOAD; + sLoadDGO.selected_id = cmd->cgo_id; + sLoadDGO.mbox_reply = g_nDGOMbx; + sLoadDGO.thread_to_wake = 0; + sLoadDGO.buffer1 = (u8*)(u64)cmd->buffer1; + sLoadDGO.buffer2 = (u8*)(u64)cmd->buffer2; + sLoadDGO.buffer_top = (u8*)(u64)cmd->buffer_heap_top; + sLoadDGO.file_def = file; + // CpuSuspendIntr(local_18); + if (0 < cmd->cgo_id - sLoadDGO.last_id) { + sLoadDGO.last_id = cmd->cgo_id; + } + sLoadDGO.sync_sent_count = 1; + sLoadDGO.nosync_cancel_ack = 0; + // CpuResumeIntr(local_18[0]); + ASSERT(sLoadDGO.msg_type != ISO_Hdr::MsgType::MSG_0); + ovrld_log(LogCategory::RPC, "------------------DGO: RPC sending mbox (reply size is {})", + MbxSize(g_nDGOMbx)); + SendMbx(g_nISOMbx, &sLoadDGO); + ovrld_log(LogCategory::RPC, "DGO: RPC waiting mbox (now has {})", MbxSize(g_nDGOMbx)); + WaitMbx(g_nDGOMbx); + ovrld_log(LogCategory::RPC, "DGO: RPC recv mbox: {}", int(sLoadDGO.status)); + if (sLoadDGO.status == EIsoStatus::OK_2) { + cmd->status = 2; + return; + } + if (sLoadDGO.status != EIsoStatus::NONE_0) { + cmd->status = 1; + sLoadDGO.msg_type = ISO_Hdr::MsgType::MSG_0; + sLoadDGO.selected_id = -1; + return; + } + } else { + ovrld_log(LogCategory::WARN, "DGO RPC: old command ID seen for {} (got {}, saw {}), ignoring\n", + cmd->name, cmd->cgo_id, sLoadDGO.last_id); + } + cmd->buffer1 = cmd->buffer_heap_top; + cmd->status = 0; + sLoadDGO.msg_type = ISO_Hdr::MsgType::MSG_0; + sLoadDGO.selected_id = -1; +} + +void LoadNextDGO(RPC_Dgo_Cmd* cmd) { + if (sLoadDGO.msg_type == ISO_Hdr::MsgType::MSG_0) { + ovrld_log(LogCategory::WARN, "DGO RPC: LoadNextDGO {} load not running! Ignoring\n", cmd->name); + cmd->status = 1; + return; + } + + sLoadDGO.buffer_top = (u8*)(u64)cmd->buffer_heap_top; + sLoadDGO.buffer1 = (u8*)(u64)cmd->buffer1; + sLoadDGO.buffer2 = (u8*)(u64)cmd->buffer2; + bool unblocked = NotifyDGO(); // tell dgo state machine to run + int status = 3; + if (unblocked != 0) { + WaitMbx(g_nDGOMbx); // wait for it to finish, and return to EE + if (sLoadDGO.status == EIsoStatus::OK_2) { + cmd->status = 2; + cmd->buffer1 = (u32)(u64)sLoadDGO.selected_buffer; + return; + } + status = 11; + if (sLoadDGO.status == EIsoStatus::NONE_0) { + cmd->status = 0; + cmd->buffer1 = cmd->buffer_heap_top; + sLoadDGO.msg_type = ISO_Hdr::MsgType::MSG_0; + sLoadDGO.selected_id = -1; + return; + } + } else { + ovrld_log(LogCategory::WARN, "DGO RPC: LoadNextDGO {} already cancelled! Ignoring\n", + cmd->name); + } + cmd->status = status; + sLoadDGO.msg_type = ISO_Hdr::MsgType::MSG_0; + sLoadDGO.selected_id = -1; +} + +void CancelDGO(RPC_Dgo_Cmd* param_1) { + ovrld_log(LogCategory::WARN, "DGO RPC: CancelDGO {}\n", param_1 ? param_1->name : "NO CMD"); + if (sLoadDGO.msg_type != ISO_Hdr::MsgType::MSG_0) { + sLoadDGO.want_abort = 1; + if (NotifyDGO()) { + WaitMbx(g_nDGOMbx); + } + if (param_1) { + param_1->status = 3; + } + sLoadDGO.selected_id = -1; + sLoadDGO.msg_type = ISO_Hdr::MsgType::MSG_0; + } +} + +void CancelDGONoSync(int id) { + ovrld_log(LogCategory::WARN, "DGO RPC: CancelDGONoSync {}\n", id); + // CpuSuspendIntr(local_10); + sLoadDGO.nosync_cancel_pending_flag = 1; + if (0 < id - sLoadDGO.last_id) { + sLoadDGO.last_id = id; + } + sLoadDGO.request_cancel_id = id; + // CpuResumeIntr(local_10[0]); +} + +EIsoStatus CopyDataToEE(ISO_Hdr* msg) { + return CopyData((ISO_LoadSingle*)msg, CopyKind::EE); +} + +EIsoStatus CopyDataToIOP(ISO_Hdr* msg) { + return CopyData((ISO_LoadSingle*)msg, CopyKind::IOP); +} + +EIsoStatus CopyDataSbkLoad(ISO_Hdr* msg) { + return CopyData((ISO_LoadSingle*)msg, CopyKind::SBK); +} + +void CopyDataDmaCallback(void* in) { + ((CPage*)in)->ReleaseDmaRef(); +} + +EIsoStatus CopyData(ISO_LoadCommon* cmd, CopyKind kind) { + ASSERT(cmd); + auto* file = cmd->m_pBaseFile; + if (file == (CISOCDFile*)0x0) { + return EIsoStatus::ERROR_NO_FILE; + } + + EIsoStatus status = EIsoStatus::OK_2; + if (file->m_Buffer.m_eBufferType != CBuffer::BufferType::NORMAL) { + CBuffer* buffer = &file->m_Buffer; + CPage* page = nullptr; + if (buffer->m_pPageList && buffer->m_nDataLength) { + if (file->CheckPageBoundary()) { + page = file->m_Buffer.m_pPageList->m_pCurrentActivePage; + } + if (page && cmd->progress_bytes < cmd->length_to_copy) { + int len; + do { + // length we want + len = cmd->length_to_copy - cmd->progress_bytes; + + // trim to buffered + if (buffer->m_nDataLength < len) { + len = buffer->m_nDataLength; + } + + // trim to page + if (page->m_pPageMemEnd - buffer->m_pCurrentData + 1 < len) { + len = page->m_pPageMemEnd - buffer->m_pCurrentData + 1; + } + + if (0 < len) { + switch (kind) { + case CopyKind::IOP: { + if (page->AddRef() < 1) { + ASSERT_NOT_REACHED(); + } + memcpy(cmd->dest_ptr, buffer->m_pCurrentData, len); + if (page->ReleaseRef() < 0) + ASSERT_NOT_REACHED(); + } break; + case CopyKind::EE: { + if (page->AddDmaRef() < 1) { + ASSERT_NOT_REACHED(); + } + DMA_SendToEE(cmd->dest_ptr, buffer->m_pCurrentData, len, CopyDataDmaCallback, page); + } break; + case CopyKind::SBK: { + WaitSema(g_n989Semaphore); + if (g_bSoundEnable == 0) { + SignalSema(g_n989Semaphore); + return EIsoStatus::ERROR_NO_SOUND; + } + if (page->AddRef() < 1) { + ASSERT_NOT_REACHED(); + SignalSema(g_n989Semaphore); + return EIsoStatus::OK_2; + } + auto* bank_info = ((ISO_LoadSoundbank*)cmd)->bank_info; + + // hack: added + if (cmd->progress_bytes == 0) { + snd_BankLoadFromIOPPartialEx_Start(); + } + snd_BankLoadFromIOPPartialEx(buffer->m_pCurrentData, len, bank_info->m_nSpuMemLoc, + bank_info->m_nSpuMemSize); + if (cmd->progress_bytes + len == cmd->length_to_copy) { + snd_BankLoadFromIOPPartialEx_Completion(); + snd_ResolveBankXREFS(); + // TODO: this also set field_0x28... is that needed?? + } + + if (page->ReleaseRef() < 0) { + ASSERT_NOT_REACHED(); + } + SignalSema(g_n989Semaphore); + } break; + default: + ASSERT_NOT_REACHED(); + } + + cmd->dest_ptr = cmd->dest_ptr + len; + cmd->progress_bytes = len + cmd->progress_bytes; + buffer->AdvanceCurrentData(len); + if (!file->CheckPageBoundary()) + break; + page = buffer->m_pPageList->m_pCurrentActivePage; + } + + if (!page || len < 1) + break; + } while (true); + } + } + if ((u32)cmd->progress_bytes < (u32)cmd->length_to_copy) { + return status; + } + buffer->m_pPageList->CancelActivePages(); + if (status != EIsoStatus::OK_2) { + return status; + } + return EIsoStatus::NONE_0; + } + return EIsoStatus::ERROR_NO_FILE; +} + +EIsoStatus NullCallback(ISO_Hdr*) { + return EIsoStatus::NULL_CALLBACK; +} + +void set_active_a(ISO_Hdr* cmd, int val) { + cmd->active_a = val; +} + +void set_active_b(ISO_Hdr* cmd, int val) { + cmd->active_b = val; +} + +void set_active_c(ISO_Hdr* cmd, int val) { + cmd->active_c = val; +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso.h b/game/overlord/jak3/iso.h new file mode 100644 index 0000000000..72ef8eef31 --- /dev/null +++ b/game/overlord/jak3/iso.h @@ -0,0 +1,92 @@ +#pragma once +#include "common/link_types.h" + +#include "game/overlord/jak3/isocommon.h" + +namespace jak3 { +struct ISOFileDef; +void jak3_overlord_init_globals_iso(); +void InitISOFS(); + +const ISOFileDef* FindISOFile(const char*); +struct ISO_VAGCommand; +struct VagStreamData; +struct RPC_Dgo_Cmd; + +struct ISO_DGOCommand : public ISO_Hdr { + u8* buffer1 = nullptr; + u8* buffer2 = nullptr; + u8* buffer_top = nullptr; + DgoHeader dgo_header; // + ObjectHeader obj_header; // + u8* ee_dest_buffer = nullptr; // 192 + int bytes_processed = 0; // 196 + int objects_loaded = 0; // 200 + enum class State { + INIT = 0, + READ_DGO_HEADER = 1, + FINISH_OBJ = 2, + READ_LAST_OBJ = 3, + READ_OBJ_HEADER = 4, + READ_OBJ_DATA = 5, + FINISH_DGO = 6, + FINISH_OBJ_SINGLE_BUFFER = 7, + } state = State::INIT; // 204 + int finished_first_object = 0; // 208 + int buffer_toggle = 0; // 212 + u8* selected_buffer = nullptr; // 216 + int selected_id = 0; // 220 + int last_id = 0; // 224 + int acked_cancel_id = 0; // 228 + u8 nosync_cancel_pending_flag = 0; // 232 + int request_cancel_id = 0; // 236 + u8 nosync_cancel_ack = 0; // 240 + int sync_sent_count = 0; // 244 + + // the number of times we looked in the iso thread's sync mbox + // I think just used for debugging/asserts. + int sync_mbox_wait_count = 0; // 248 + + int sync_ret_count = 0; // 252 + int want_abort = 0; // 256 +}; + +enum class CopyKind { + EE = 0, + IOP = 1, + SBK = 2, +}; + +void set_active_a(ISO_Hdr* cmd, int val); +void set_active_b(ISO_Hdr* cmd, int val); +void set_active_c(ISO_Hdr* cmd, int val); +void IsoStopVagStream(ISO_VAGCommand* cmd); +void IsoPlayVagStream(ISO_VAGCommand* user_cmd); +EIsoStatus NullCallback(ISO_Hdr* msg); +EIsoStatus CopyDataToIOP(ISO_Hdr* msg); +EIsoStatus CopyDataSbkLoad(ISO_Hdr* msg); +EIsoStatus CopyDataToEE(ISO_Hdr* msg); +EIsoStatus RunDGOStateMachine(ISO_Hdr* msg); +void QueueVAGStream(VagStreamData* cmd); +void CopyDataDmaCallback(void*); +void* RPC_DGO(unsigned int fno, void* msg_ptr, int); +void LoadDGO(RPC_Dgo_Cmd* cmd); +void CancelDGO(RPC_Dgo_Cmd* cmd); +void LoadNextDGO(RPC_Dgo_Cmd* cmd); +EIsoStatus CopyData(ISO_LoadCommon* msg, CopyKind kind); +void CancelDGONoSync(int id); +void IsoPlayMusicStream(ISO_VAGCommand* user_cmd); +void IsoQueueVagStream(ISO_VAGCommand* user_cmd); +extern int g_nISOThreadID; +extern int g_nISOMbx; +extern bool g_bMusicPause; +extern int g_nMusicSemaphore; +extern char g_szTargetMusicName[0x30]; +extern int g_nActiveMusicStreams; +extern bool g_bVagCmdsInitialized; +extern bool g_bMusicIsPaused; +extern bool g_bAnotherMusicPauseFlag; +extern int g_nMusicFade; +extern int g_nMusicTweak; +extern int g_nMusicFadeDir; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso_api.cpp b/game/overlord/jak3/iso_api.cpp new file mode 100644 index 0000000000..acadbdf62a --- /dev/null +++ b/game/overlord/jak3/iso_api.cpp @@ -0,0 +1,229 @@ +#include "iso_api.h" + +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" +#include "common/util/FileUtil.h" + +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/iso_cd.h" +#include "game/overlord/jak3/iso_queue.h" +#include "game/overlord/jak3/srpc.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" + +namespace jak3 { +using namespace iop; +void jak3_overlord_init_globals_iso_api() {} + +/*! + * This file is the "API" that implementations of RPCs or other code can use to submit things to the + * ISO thread. Note that not all RPCs use this API - for example the DGO RPC just manually submits + * messages. Generally, these functions will not return until the actual action is complete, like a + * file is loaded. + * - except for messages to pause/play audio, those functions will return immediately, but there may + * be a delay until they are actually processed. + */ + +// PluginVagAndVagWad not ported. + +u32 EEVagAndVagWad(ISO_VAGCommand* cmd, char* name) { + ISOName name_buff; + if (*name == '$' || strlen(name) < 9) { + if (*name == '$') { + name++; + } + MakeISOName(&name_buff, name); + } else { + file_util::ISONameFromAnimationName(name_buff.data, name); + } + + cmd->vag_dir_entry = get_file_system()->FindVAGFile(name_buff.data); + if (cmd->vag_dir_entry) { + memcpy(name_buff.data, "VAGWAD ", 8); + strncpy(name_buff.data + 8, g_pszLanguage, 4); + auto* file_def = get_file_system()->FindIN(&name_buff); + // piVar1 = g_pFileSystem; + cmd->file_def = file_def; + + strncpy(name_buff.data + 8, "INT", 4); + + cmd->vag_file_def = get_file_system()->FindIN(&name_buff); + if (cmd->vag_dir_entry && cmd->file_def && cmd->vag_file_def) { + return 1; + } + } + + cmd->vag_dir_entry = nullptr; + cmd->file_def = nullptr; + cmd->vag_file_def = nullptr; + return 0; +} + +int LoadISOFileToIOP(const ISOFileDef* file_def, void* addr, int length) { + ISO_LoadSingle cmd; + cmd.msg_type = ISO_Hdr::MsgType::LOAD_IOP; + cmd.mbox_reply = 0; + cmd.thread_to_wake = GetThreadId(); + cmd.file_def = file_def; + cmd.addr = (u8*)addr; + cmd.maxlen = length; + lg::warn("--------------- LoadISOFileToIOP START"); + SendMbx(g_nISOMbx, &cmd); + SleepThread(); + lg::warn("--------------- LoadISOFileToIOP END"); + if (cmd.status == EIsoStatus::NONE_0) { + return cmd.length_to_copy; + } else { + return 0; + } +} + +int LoadISOFileToEE(const ISOFileDef* file_def, u32 addr, int length) { + ISO_LoadSingle cmd; + cmd.msg_type = ISO_Hdr::MsgType::LOAD_EE; + cmd.mbox_reply = 0; + cmd.thread_to_wake = GetThreadId(); + cmd.file_def = file_def; + cmd.addr = (u8*)(u64)addr; + cmd.maxlen = length; + lg::warn("--------------- LoadISOFileToEE START"); + SendMbx(g_nISOMbx, &cmd); + SleepThread(); + lg::warn("--------------- LoadISOFileToEE END"); + if (cmd.status == EIsoStatus::NONE_0) { + return cmd.length_to_copy; + } + return 0; +} + +int LoadISOFileChunkToEE(const ISOFileDef* file_def, u32 addr, int max_len, int sector_offset) { + ISO_LoadSingle cmd; + cmd.msg_type = ISO_Hdr::MsgType::LOAD_EE_CHUNK; + cmd.mbox_reply = 0; + cmd.thread_to_wake = GetThreadId(); + cmd.file_def = file_def; + cmd.addr = (u8*)(u64)addr; + cmd.maxlen = max_len; + cmd.sector_offset = sector_offset; + lg::warn("--------------- LoadISOFileChunkToEE START"); + SendMbx(g_nISOMbx, &cmd); + SleepThread(); + lg::warn("--------------- LoadISOFileChunkToEE END"); + if (cmd.status == EIsoStatus::NONE_0) { + return cmd.length_to_copy; + } + return 0; +} + +u32 LoadSoundBankToIOP(const char* name, SoundBankInfo* bank, u32 mode) { + ISO_LoadSoundbank cmd; + cmd.msg_type = ISO_Hdr::MsgType::LOAD_SOUNDBANK; + cmd.mbox_reply = 0; + cmd.thread_to_wake = GetThreadId(); + cmd.bank_info = bank; + cmd.name = name; + cmd.priority = mode; + lg::warn("--------------- LoadSoundBankToIOP START"); + SendMbx(g_nISOMbx, &cmd); + SleepThread(); + lg::warn("--------------- LoadSoundBankToIOP END"); + + return (u32)cmd.status; +} + +void PlayMusicStream(VagStreamData* stream) { + int iVar1; + ISO_VAGCommand cmd; + + cmd.msg_type = ISO_Hdr::MsgType::PLAY_MUSIC_STREAM; + cmd.mbox_reply = 0; + cmd.thread_to_wake = 0; + iVar1 = EEVagAndVagWad(&cmd, stream->name); + if (iVar1 == 0) { + // if (bWarn == 0) { + // bWarn = 1; + // } + } else { + cmd.play_volume = 0x400; + // bWarn = 0; + strncpy(cmd.name, stream->name, 0x30); + cmd.id = stream->id; + cmd.priority_pq = 9; + cmd.music_flag = 1; + cmd.maybe_sound_handler = 0; + cmd.plugin_id = 0; + cmd.art_flag = 0; + cmd.movie_flag = 0; + cmd.updated_trans = 0; + IsoPlayMusicStream(&cmd); + } +} + +void QueueVAGStream(VagStreamData* stream) { + bool bVar1; + bool bVar2; + ISO_VAGCommand cmd; + + cmd.msg_type = ISO_Hdr::MsgType::VAG_QUEUE; + cmd.mbox_reply = 0; + cmd.thread_to_wake = 0; + if (stream->sound_handler == 0) { + EEVagAndVagWad(&cmd, stream->name); + cmd.play_volume = 0x400; + cmd.play_group = 2; + } else { + ASSERT_NOT_REACHED(); + // PluginVagAndVagWad(&cmd,stream); + // cmd.play_volume = stream->maybe_volume2; + // cmd.oog = stream->maybe_volume_3; + // cmd.play_group = stream->group; + } + strncpy(cmd.name, stream->name, 0x30); + cmd.id = stream->id; + cmd.plugin_id = stream->plugin_id; + cmd.priority_pq = stream->priority; + cmd.maybe_sound_handler = stream->sound_handler; + bVar1 = stream->movie_art_load != 0; + cmd.movie_flag = bVar1; + bVar2 = stream->art_load != 0; + cmd.art_flag = bVar2; + cmd.music_flag = 0; + if (bVar2) { + cmd.flags.art = 1; + } + if (bVar1) { + cmd.flags.movie = 1; + } + cmd.updated_trans = 0; + IsoQueueVagStream(&cmd); +} + +void PauseVAGStreams() { + auto* cmd = GetVAGCommand(); + cmd->msg_type = ISO_Hdr::MsgType::VAG_PAUSE; + cmd->mbox_reply = 0; + cmd->thread_to_wake = 0; + SendMbx(g_nISOMbx, cmd); +} + +void UnpauseVAGStreams() { + auto* cmd = GetVAGCommand(); + cmd->msg_type = ISO_Hdr::MsgType::VAG_UNPAUSE; + cmd->mbox_reply = 0; + cmd->thread_to_wake = 0; + SendMbx(g_nISOMbx, cmd); +} + +void SetVAGStreamPitch(int id, int pitch) { + auto* cmd = GetVAGCommand(); + cmd->msg_type = ISO_Hdr::MsgType::VAG_SET_PITCH_VOL; + cmd->id = id; + cmd->pitch_cmd = pitch; + cmd->mbox_reply = 0; + cmd->thread_to_wake = 0; + SendMbx(g_nISOMbx, cmd); +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso_api.h b/game/overlord/jak3/iso_api.h new file mode 100644 index 0000000000..ca92d5f981 --- /dev/null +++ b/game/overlord/jak3/iso_api.h @@ -0,0 +1,19 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_iso_api(); + +struct ISOFileDef; +struct VagStreamData; +struct SoundBankInfo; + +int LoadISOFileToEE(const ISOFileDef* file_def, u32 addr, int length); +int LoadISOFileToIOP(const ISOFileDef* file_def, void* addr, int length); +void PlayMusicStream(VagStreamData* data); +int LoadISOFileChunkToEE(const ISOFileDef* file_def, u32 addr, int max_len, int sector_offset); +void SetVAGStreamPitch(s32 id, s32 pitch); +void UnpauseVAGStreams(); +u32 LoadSoundBankToIOP(const char* name, SoundBankInfo* bank, u32 mode); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso_cd.cpp b/game/overlord/jak3/iso_cd.cpp new file mode 100644 index 0000000000..a10467fb86 --- /dev/null +++ b/game/overlord/jak3/iso_cd.cpp @@ -0,0 +1,521 @@ +#include "iso_cd.h" + +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" +#include "common/util/FileUtil.h" + +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/isocommon.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/sce/iop.h" + +using namespace iop; + +namespace jak3 { +VagDir g_VagDir; +MusicTweaks gMusicTweakInfo; +CISOCDFile g_CISOCDFiles[kMaxOpenFiles]; + +namespace { +CISOCDFile* g_pReadInfo = nullptr; +std::vector g_FileDefs; +std::unique_ptr g_ISOCDFileSystem; +} // namespace + +void jak3_overlord_init_globals_iso_cd() { + g_pReadInfo = nullptr; + for (auto& f : g_CISOCDFiles) { + f = CISOCDFile(); + } + g_FileDefs.clear(); + g_VagDir = {}; + g_ISOCDFileSystem = std::make_unique(); + gMusicTweakInfo = {}; +} + +CBaseFileSystem* get_file_system() { + return g_ISOCDFileSystem.get(); +} + +CISOCDFile::CISOCDFile() { + m_nSector = -1; + m_nLoaded = 0; + m_nLength = 0; +} + +namespace { +void ReadPagesCallbackF(CISOCDFile* file, Block* block, s32 error) { + file->ReadPagesCallback(block, error); +} +} // namespace + +CISOCDFile::CISOCDFile(const jak3::ISOFileDef* filedef, s32 process_data_semaphore) + : CBaseFile(filedef, process_data_semaphore) { + m_nSector = -1; + m_nLoaded = 0; + m_nLength = 0; + m_Descriptor.m_File = this; + m_Descriptor.m_Callback = ReadPagesCallbackF; +} + +/*! + * Call ReadPages to read data from this file into its PageList. I believe that the read + * is finished after this function returns. Note that this returns COMPLETE enum in many cases, both + * if the read succeeds, or if the read is not attempted for some reasons. + */ +EIsoStatus CISOCDFile::BeginRead() { + ASSERT(m_Buffer.m_pPageList); + ASSERT(m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE); + + if (m_Status == EIsoStatus::NONE_0) { + return EIsoStatus::ERROR_b; + } + + if (m_Status == EIsoStatus::OK_2) { + return EIsoStatus::OK_2; + } + + ASSERT(m_Status == EIsoStatus::IDLE_1); + + auto* plist = m_Buffer.m_pPageList; + + // how many pages are in our list, but not filled? + int num_pages_desired = plist->m_nNumPages - plist->m_nNumActivePages; + + if (!num_pages_desired) { + // no space to read + return EIsoStatus::OK_2; + } + + // convert pages to active, indicating that a read will attempt to fill them: + auto* first_page = plist->AddActivePages(num_pages_desired); + if (!first_page) { + lg::warn("Failed to add {} active pages", num_pages_desired); + } + + // convert buffer type - ?? + switch (m_Buffer.m_eBufferType) { + case CBuffer::BufferType::EBT_FREE: + ASSERT_NOT_REACHED(); + case CBuffer::BufferType::NORMAL: + m_Buffer.m_eBufferType = CBuffer::BufferType::REQUEST_NORMAL; + break; + case CBuffer::BufferType::VAG: + m_Buffer.m_eBufferType = CBuffer::BufferType::REQUEST_VAG; + break; + case CBuffer::BufferType::REQUEST_NORMAL: + case CBuffer::BufferType::REQUEST_VAG: + break; + } + + m_Status = EIsoStatus::OK_2; + + // remember this as our currently reading file + g_pReadInfo = this; + if (m_FileKind != CBaseFile::Kind::LZO_COMPRESSED) { + if (m_nLength == 0 || m_nLoaded < m_nLength) { + ovrld_log(LogCategory::PAGING, "Calling ReadPages: sector {}, pages {}", m_nSector, + num_pages_desired); + ReadPages(m_nSector, first_page, num_pages_desired, nullptr, true); + int bytes = 0x8000 * num_pages_desired; + m_nLoaded += bytes; + m_Buffer.AddData(bytes); + m_nSector += bytes >> 0xb; + } + } else { + ASSERT_NOT_REACHED(); + } + + return EIsoStatus::OK_2; +} + +/*! + * Called by ?? to indicate that the read is done. + */ +EIsoStatus CISOCDFile::SyncRead() { + if (m_Status != EIsoStatus::IDLE_1 && g_pReadInfo) { + if (m_Status == EIsoStatus::OK_2) { + m_Status = EIsoStatus::IDLE_1; + } + g_pReadInfo = nullptr; + return EIsoStatus::OK_2; + } + return EIsoStatus::ERROR_b; +} + +/*! + * As soon as possible, stop ongoing reads and free buffers. + */ +void CISOCDFile::Close() { + // cancel ongoing reading in the driver + get_driver()->CancelRead(&m_Descriptor); + + ASSERT(m_FileKind != CBaseFile::Kind::LZO_COMPRESSED); // unsupported in pc + if (this == g_pReadInfo) { + g_pReadInfo = nullptr; + } + + if (m_Buffer.m_eBufferType != CBuffer::BufferType::EBT_FREE) { + TerminateBuffer(); + } + + // reset self + *this = CISOCDFile(); +} + +/*! + * In the case where we have run out of page memory, take pages that we have loaded, but aren't yet + * using, and discard them back to the pool. + */ +int CISOCDFile::RecoverPages(int num_pages_desired) { + // we only allow ourselves to recover pages for VAG streaming. + if (!m_Buffer.m_pIsoCmd || m_Buffer.m_pIsoCmd->callback != ProcessVAGData) { + return 0; + } + + // lock semaphore for processing + ASSERT(m_ProcessDataSemaphore != -1); + WaitSema(m_ProcessDataSemaphore); + auto* plist = m_Buffer.m_pPageList; + int num_removed = 0; + if (plist) { + int pages_to_ask_for = plist->m_nNumUnsteppedPages; + if (pages_to_ask_for > 1) { + // don't ask for more than user asked for + if (pages_to_ask_for > num_pages_desired) { + pages_to_ask_for = num_pages_desired; + } + num_removed = plist->RemoveActivePages(pages_to_ask_for); + if (num_removed) { + m_nSector -= 0x10 * num_removed; + m_nLoaded -= 0x8000 * num_removed; + // suspend intr + ASSERT(m_Buffer.m_nDataLength >= 0); + if (m_Buffer.m_nDataLength > 0) { + m_Buffer.AddData(-num_removed * 0x8000); + } + // resume intr + ASSERT(m_nLoaded >= 0); + } + } + } + + // move our reading pointer back. + m_PageOffset -= num_removed; + // unlock processing + SignalSema(m_ProcessDataSemaphore); + return num_removed; +} + +/*! + * Get the next sector to read. + */ +int CISOCDFile::GetSector() { + return m_nSector; +} + +// WaitForLZOPages - not ported. + +void CISOCDFile::ReadPages(int sector, + jak3::CPage* destination_page, + int num_pages, + char* done_flag_ptr, + bool sleep_until_done) { + ASSERT(destination_page); + ASSERT(num_pages >= 1); + + constexpr int kMaxPages = 24; + // I _think_ this might have been an alloca... + BlockParams params_array[kMaxPages]; + ASSERT(num_pages <= kMaxPages); + + // iVar2 = -((num_pages + -1) * 0x14 + 0x1bU & 0xfffffff8); + // params = (BlockParams*)((int)local_30 + iVar2); + BlockParams* params = params_array; + + // increment our progress in the file - the distributed update of the various progress integers + // is somewhat confusing, especially since failures here don't seem to reset it properly. + m_PageOffset += num_pages; + + // Set up block params for each page to read + CPage* page = destination_page; + int pg_remaining = num_pages; + do { + page->input_state = CPage::State::READING; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask); + + params->destination = page->m_pPageMemStart; + params->num_sectors = 0x10; + params->sector_num = sector; + params->file_def = m_FileDef; + params->page = page; + + params->flag = nullptr; + if (pg_remaining == 1) { // on the last page... + params->flag = (char*)0xffffffff; // flag this + if (!sleep_until_done) { + params->flag = done_flag_ptr; + } + } + ovrld_log(LogCategory::PAGING, "[paging] building block for driver: 0x{:x}, {}, {}, flag: {}", + (u64)params->destination, params->sector_num, params->file_def->name.data, + (u64)params->flag); + page = page->m_pNextPage; + pg_remaining = pg_remaining + -1; + sector += 0x10; + params++; + } while (page && pg_remaining > 0); + + if (pg_remaining == 0) { + // we set up block params for all the requested pages. + int pages_actually_read = -1; + + ovrld_log(LogCategory::PAGING, "[paging] Submitting {} blocks to driver.\n", num_pages); + int status = get_driver()->ReadMultiple(&m_Descriptor, &pages_actually_read, params_array, + num_pages, true); + if ((status == 0) && pages_actually_read == num_pages) { + if (!sleep_until_done) { + return; + } + // put us to sleep... + ovrld_log(LogCategory::PAGING, "[paging] Sleeping, waiting for driver to read {}", + pages_actually_read); + SleepThread(); + ovrld_log(LogCategory::PAGING, "[paging] Driver woke us up!"); + return; + } + ASSERT_NOT_REACHED(); // unexpected failure to issue read + } else { + // ran out of pages.... + ASSERT_NOT_REACHED(); // this was just a warning.. but I think it shouldn't happen. + if (destination_page) { + do { + destination_page->input_state = CPage::State::ACTIVE; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, + ~destination_page->mask); + destination_page = destination_page->m_pNextPage; + } while (destination_page); + } + } + + ASSERT_NOT_REACHED(); + // only get here is we failed.... set the done flag. + if (sleep_until_done == 0 && done_flag_ptr) { + *done_flag_ptr = 1; + } +} + +/*! + * Callback run by the dvd driver when a page is finished reading. + */ +void CISOCDFile::ReadPagesCallback(jak3::Block* block, int error) { + if (error == 0) { + ASSERT(block->params.page->m_nAllocState == 1); + // flag page as done + block->params.page->input_state = CPage::State::READ_DONE; + // set flag + SetEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, block->params.page->mask); + + // if this block has a notification, process it: + if (block->params.flag == (char*)0xffffffff) { // indicates we should wake caller + // we assume the caller is the iso thread + WakeupThread(g_nISOThreadID); + } else if (block->params.flag) { + // in this case, it's a pointer. + *block->params.flag = 1; + } + } +} + +// DecompressBlock - not ported + +/*! + * Initialize the file system - find all files and set up their definitions. + */ +int CISOCDFileSystem::Init() { + // drive ready event flag - not ported + // get disc type - not ported + + // this is mostly not needed... except for some vag pausing nonsense :( + get_driver()->SetDriverCallback([&](int a) { DvdDriverCallback(a); }); + + // skipped a bunch of lzo pages crap + ReadDirectory(); + LoadMusicTweaks(); + // skip load Disc ID + + // should already be done in the constructor... + for (auto& f : g_CISOCDFiles) { + f.m_FileDef = nullptr; + } + return 0; +} + +// PollDrive - not ported + +/*! + * Find a file definition by name. + */ +ISOFileDef* CISOCDFileSystem::Find(const char* name) { + ISOName iname; + file_util::MakeISOName(iname.data, name); + return FindIN(&iname); +} + +/*! + * Find a file definition by its "ISO Name", a 12-byte name. + */ +ISOFileDef* CISOCDFileSystem::FindIN(const jak3::ISOName* name) { + for (auto& def : g_FileDefs) { + if (def.name == *name) { + return &def; + } + } + return nullptr; +} + +/*! + * Get the length of a file, in bytes. + */ +int CISOCDFileSystem::GetLength(const jak3::ISOFileDef* file) { + return file->length; +} + +/*! + * Open a file for reading. + */ +CBaseFile* CISOCDFileSystem::Open(const jak3::ISOFileDef* file_def, + int sector_offset, + int file_kind) { + auto* file = AllocateFile(file_def); + ASSERT(file); + + // file kind must be known to be non-compressed (1). (TODO: remove arg) + ASSERT(file_kind == 1); + + file->m_FileKind = CBaseFile::Kind::NORMAL; + file->m_nLength = file_def->length; + file->m_LengthPages = (0x7fff + file->m_nLength) >> 0xf; + if (file->m_LengthPages < 1) { + ASSERT_NOT_REACHED(); + } + file->m_nLoaded = 0; + file->m_PageOffset = 0; + + // in the original game, this was the sector of the start of the file + // we just make it 0, since we don't build up a whole iso file. + file->m_nSector = 0; + if (sector_offset != -1) { + file->m_nSector += sector_offset; + } + return file; +} + +/*! + * Open a WAD file for reading, given the offset of the data to read, in pages. + */ +CBaseFile* CISOCDFileSystem::OpenWAD(const jak3::ISOFileDef* file_def, int page_offset) { + auto* file = AllocateFile(file_def); + ASSERT(file); + + file->m_LengthPages = 1; // this is not really true.. + file->m_FileKind = CBaseFile::Kind::NORMAL; + file->m_PageOffset = 0; + file->m_nSector = page_offset * 0x10; + file->m_nLoaded = 0; + file->m_nLength = 0; + return file; +} + +/*! + * Locate the entry for a VAG file in the VAG directory. + */ +VagDirEntry* CISOCDFileSystem::FindVAGFile(const char* name) { + u32 packed_name[2]; + PackVAGFileName(packed_name, name); + for (int i = 0; i < g_VagDir.num_entries; i++) { + auto& entry = g_VagDir.entries[i]; + if (packed_name[0] == entry.words[0] && packed_name[1] == (entry.words[1] & 0x3ff)) { + return &entry; + } + } + return nullptr; +} + +/*! + * Get a CISOCDFile* for a newly opened file. + */ +CISOCDFile* CISOCDFileSystem::AllocateFile(const jak3::ISOFileDef* file_def) { + for (int i = 0; i < kMaxOpenFiles; i++) { + auto* file = &g_CISOCDFiles[i]; + if (!file->m_FileDef) { + *file = CISOCDFile(file_def, m_Sema[i]); + return file; + } + } + ASSERT_NOT_REACHED(); +} + +/*! + * Callback from the DVD driver itself into the filesystem. This was originally used for notifying + * when the tray is opened or closed. + */ +void CISOCDFileSystem::DvdDriverCallback(int) { + // the only callbacks that do anything are tray open/close, which we don't care about + ASSERT_NOT_REACHED(); +} + +// CheckDiscID - not ported +// LoadDiscID - not ported +// ReadSectorsNow - not ported + +/*! + * Find all the files on the disc and set up their information. This is modified for the PC port to + * just search for files in the appropriate out folder. + */ +void CISOCDFileSystem::ReadDirectory() { + for (const auto& f : + fs::directory_iterator(file_util::get_jak_project_dir() / "out" / "jak3" / "iso")) { + if (f.is_regular_file()) { + auto& e = g_FileDefs.emplace_back(); + std::string file_name = f.path().filename().string(); + ASSERT(file_name.length() < 16); // should be 8.3. + MakeISOName(&e.name, file_name.c_str()); + e.full_path = + fmt::format("{}/out/jak3/iso/{}", file_util::get_jak_project_dir().string(), file_name); + auto* fp = file_util::open_file(e.full_path, "rb"); + ASSERT(fp); + fseek(fp, 0, SEEK_END); + e.length = ftell(fp); + fclose(fp); + } + } +} + +/*! + * Load the "Music Tweaks" file, which contains a volume setting per music track. + */ +void CISOCDFileSystem::LoadMusicTweaks() { + ISOName tweakname; + MakeISOName(&tweakname, "TWEAKVAL.MUS"); + auto file = g_ISOCDFileSystem->FindIN(&tweakname); + if (file) { + auto fp = file_util::open_file(file->full_path, "rb"); + ASSERT(fp); + ASSERT(file->length <= sizeof(gMusicTweakInfo)); + auto ret = fread(&gMusicTweakInfo, file->length, 1, fp); + ASSERT(ret == 1); + fclose(fp); + } else { + lg::warn("Failed to open music tweak file."); + gMusicTweakInfo.TweakCount = 0; + } +} + +// Crc32 - not ported +// ReadU32 - not ported + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso_cd.h b/game/overlord/jak3/iso_cd.h new file mode 100644 index 0000000000..1746db0891 --- /dev/null +++ b/game/overlord/jak3/iso_cd.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "game/overlord/jak3/basefile.h" +#include "game/overlord/jak3/basefilesystem.h" +#include "game/overlord/jak3/dvd_driver.h" +#include "game/overlord/jak3/isocommon.h" + +namespace jak3 { +void jak3_overlord_init_globals_iso_cd(); + +CBaseFileSystem* get_file_system(); + +extern VagDir g_VagDir; +extern MusicTweaks gMusicTweakInfo; + +struct CISOCDFile : public CBaseFile { + CISOCDFile(); + CISOCDFile(const ISOFileDef* filedef, s32 process_data_semaphore); + int m_nLoaded = 0; // bytes loaded so far + int m_nSector = 0; // next sector to read. + int m_nLength = 0; // bytes that we want to load. 0 for the whole file + CDescriptor m_Descriptor; + + EIsoStatus BeginRead() override; + + void ReadPages(int sector, + CPage* destination_page, + int num_pages, + char* done_flag_ptr, + bool flag); + EIsoStatus SyncRead() override; + void Close() override; + int RecoverPages(int num_pages) override; + int GetSector() override; + + void ReadPagesCallback(Block* block, int error); +}; + +struct CISOCDFileSystem : public CBaseFileSystem { + CISOCDFileSystem() = default; + int Init() override; + ISOFileDef* Find(const char* name) override; + ISOFileDef* FindIN(const ISOName* name) override; + int GetLength(const ISOFileDef* file) override; + CBaseFile* Open(const ISOFileDef* file_def, int sector_offset, int file_kind) override; + CBaseFile* OpenWAD(const ISOFileDef* file_def, int page_offset) override; + VagDirEntry* FindVAGFile(const char* name) override; + + void DvdDriverCallback(int a); + void ReadDirectory(); + void LoadMusicTweaks(); + CISOCDFile* AllocateFile(const ISOFileDef* file); + + // int m_drive_ready_event_flag = -1; +}; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso_queue.cpp b/game/overlord/jak3/iso_queue.cpp new file mode 100644 index 0000000000..a11401ecc4 --- /dev/null +++ b/game/overlord/jak3/iso_queue.cpp @@ -0,0 +1,732 @@ +#include "iso_queue.h" + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/basefile.h" +#include "game/overlord/jak3/dma.h" +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/isocommon.h" +#include "game/overlord/jak3/pagemanager.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" +#include "game/sound/sndshim.h" + +using namespace iop; + +namespace jak3 { +s32 g_nPriQueueSema = 0; +s32 g_VagCmdSema = 0; +u32 g_auStrmSRAM[6]; +u32 g_auTrapSRAM[6]; +PriStackEntry gPriStack[2]; +extern u32 time_of_last_unknown_rate_drive_op; +u32 g_cmds_with_speed_total = 0; +bool unk_time_mode_flag = false; +ISO_Hdr* g_selected_cmd = nullptr; +bool unk_time_mflag = 0; +s32 unk_sector = 0; +u32 vag_cmd_cnt = 0; +u32 vag_cmd_used = 0; +u32 max_vag_cmd_cnt = 0; +ISO_VAGCommand vag_cmds[16]; + +static constexpr s32 LOOP_END = 1; +static constexpr s32 LOOP_REPEAT = 2; +static constexpr s32 LOOP_START = 4; + +// Empty ADPCM block with loop flags + +// clang-format off +u8 VAG_SilentLoop[0x60] = { + 0x0, LOOP_START | LOOP_REPEAT, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, LOOP_REPEAT, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, LOOP_END | LOOP_REPEAT, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +}; +// clang-format on + +void jak3_overlord_init_globals_iso_queue() { + g_nPriQueueSema = 0; + g_VagCmdSema = 0; + for (auto& x : gPriStack) { + x = {}; + } + g_cmds_with_speed_total = 0; + unk_time_mode_flag = false; + g_selected_cmd = nullptr; + unk_time_mflag = 0; + unk_sector = 0; + vag_cmd_cnt = 0; + vag_cmd_used = 0; + for (auto& x : vag_cmds) { + x = {}; + } +} + +/*! + * Added function to check if there is a pending DGO load command. + * On PC, DOG loads are really the only loading time that users see. If there is a pending + * DGO load, we can modify logic to take advantage of PCs being dramatically faster than PS2 and + * get much better load times. + */ +bool DgoCmdWaiting() { + for (auto& level : gPriStack) { + for (int i = 0; i < level.count; i++) { + auto* cmd = level.cmds[i]; + + if (cmd && cmd->msg_type == ISO_Hdr::MsgType::DGO_LOAD) { + if (cmd->m_pBaseFile) { + auto* file = cmd->m_pBaseFile; + if (file->m_Buffer.m_nDataLength) { + return true; + } + } + } + } + } + return false; +} + +void InitBuffers() { + SemaParam sema_param; + sema_param.max_count = 1; + sema_param.init_count = 1; + sema_param.attr = 0; + sema_param.option = 0; + + g_nPriQueueSema = CreateSema(&sema_param); + ASSERT(g_nPriQueueSema >= 0); + get_page_manager()->Initialize(); + + g_auStrmSRAM[0] = 0x5040; + g_auTrapSRAM[0] = 0x9040; + snd_SRAMMarkUsed(0x5040, 0x4040); + g_auStrmSRAM[1] = 0x9080; + g_auTrapSRAM[1] = 0xd080; + snd_SRAMMarkUsed(0x9080, 0x4040); + g_auStrmSRAM[2] = 0xd0c0; + g_auTrapSRAM[2] = 0x110c0; + snd_SRAMMarkUsed(0xd0c0, 0x4040); + g_auStrmSRAM[3] = 0x11100; + g_auTrapSRAM[3] = 0x15100; + snd_SRAMMarkUsed(0x11100, 0x4040); + g_auStrmSRAM[4] = 0x15140; // 86384 - 48 + g_auTrapSRAM[4] = 0x19140; + snd_SRAMMarkUsed(0x15140, 0x4040); + g_auStrmSRAM[5] = 0x019180; + g_auTrapSRAM[5] = 0x001d180; + snd_SRAMMarkUsed(0x19180, 0x4040); + + for (int i = 0; i < 6; i++) { + if (!DMA_SendToSPUAndSync(VAG_SilentLoop, 0x30, g_auTrapSRAM[i], nullptr, nullptr)) { + DelayThread(1000); + ASSERT_NOT_REACHED(); + break; + } + } + + sema_param.max_count = 1; + sema_param.attr = 1; + sema_param.init_count = 1; + sema_param.option = 0; + g_VagCmdSema = CreateSema(&sema_param); + ASSERT(g_VagCmdSema >= 0); +} + +int QueueMessage(ISO_Hdr* msg, int pri) { + msg->status = EIsoStatus::OK_2; + msg->priority = pri; + WaitSema(g_nPriQueueSema); + int queue_idx = (pri == 5) ? 1 : 0; + bool ok = gPriStack[queue_idx].count != 8; + if (ok) { + gPriStack[queue_idx].cmds[gPriStack[queue_idx].count] = msg; + gPriStack[queue_idx].count++; + SignalSema(g_nPriQueueSema); + } else { + msg->status = EIsoStatus::FAILED_TO_QUEUE_4; + SignalSema(g_nPriQueueSema); + ReturnMessage(msg); + ASSERT_NOT_REACHED(); + } + return ok; +} + +int UnqueueMessage(ISO_Hdr* msg) { + WaitSema(g_nPriQueueSema); + int iVar5 = 0; + PriStackEntry* stack = gPriStack; + do { + int iVar4 = 0; + ISO_Hdr** cmd = stack->cmds; + if (0 < stack->count) { + do { + if (*cmd == msg) + break; + iVar4 = iVar4 + 1; + cmd++; + } while (iVar4 < stack->count); + } + iVar5 = iVar5 + 1; + if (iVar4 < stack->count) { + stack->count = stack->count + -1; + if (iVar4 < stack->count) { + ISO_Hdr** ppIVar3 = stack->cmds + iVar4; + do { + iVar4 = iVar4 + 1; + *ppIVar3 = ppIVar3[1]; + ppIVar3 = ppIVar3 + 1; + } while (iVar4 < stack->count); + } + return SignalSema(g_nPriQueueSema); + } + stack = stack + 1; + if (1 < iVar5) { + return SignalSema(g_nPriQueueSema); + } + } while (true); +} + +/*! + * Select which command to read for next + * This function considers things like seeking time, reading rates of streamed files, + * and buffer sizing. To be entirely honest, I don't understand it almost at all, and it's not + * clear that it works as expected. It seems to work good enough, and no commands get entirely + * starved of data while there are multiple streams. + */ +ISO_Hdr* GetMessage() { + // bool been_a_while; + // bool bVar2; + // int now; + // int iVar3; + // int iVar4; + // CISOCDFile *file; + // CISOCDFile *pCVar5; + // int iVar6; + // CPageList *plist; + // uint uVar7; + // int iVar8; + // int iVar9; + // PriStack *local_t2_216; + // PriStack *pri_level; + // CISOCDFile *tfile4; + // code *pcVar10; + // CISOCDFile *tfile2; + // CISOCDFile *tfile3; + // uint uVar11; + // ISO_VAGCommand *cmd; + // int idx_on_level; + // ISO_VAGCommand **ppIVar12; + // int iVar13; + // CBaseFile *tfile; + // int cmds_total; + // int iVar14; + // uint uVar15; + ISO_Hdr* cmds_array[16]; + u32 read_rates_array[16]; + int num_pages_array[16]; + int unstepped_pages_array[16]; + int remaining_pages_array[16]; + // uint its_been_a_while; + // int pages_total; + // int read_rate_total; + // int min_nospeed_pages_total; + // int max_pages_total; + // int min_speed_pages_total; + // ISO_VAGCommand *selected_cmd; + // int cmds_with_speed_total; + // uint cmd2_read_rate; + // int selected_cmd_rem_sectors; + // int pri_level_idx; + + // simple logic to select which command to use next. + + u32 now = GetSystemTimeLow(); + + bool been_a_while = false; + if (unk_time_mode_flag == 0) { + been_a_while = 0x384000 < (now - time_of_last_unknown_rate_drive_op); + } else { + unk_time_mode_flag = 0; + time_of_last_unknown_rate_drive_op = now; + } + + s32 cmds_total = 0; + get_page_manager()->GarbageCollect(); + s32 pages_total = 0; + s32 read_rate_total = 0; + s32 min_nospeed_pages_total = 0; + s32 max_pages_total = 0; + s32 min_speed_pages_total = 0; +LAB_000080e4: + s32 iVar9 = g_cmds_with_speed_total * 400 + 0x2ee; + s32 iVar13 = 0x7fffffff; + ISO_Hdr* selected_cmd = nullptr; + s32 cmds_with_speed_total = 0; + s32 cmd2_read_rate = 0; + s32 selected_cmd_rem_sectors = -1; + s32 pri_level_idx = 1; + PriStackEntry* pri_level = gPriStack + 1; + s32 iVar8 = iVar13; + + // loop over priority levels + do { + s32 idx_on_level = pri_level->count + -1; + + // if any exist on this level + if (-1 < idx_on_level) { + ISO_Hdr** ppIVar12 = pri_level->cmds + idx_on_level; + // iVar14 = cmds_total << 2; + // loop over commands on this level + do { + ISO_Hdr* cmd = *ppIVar12; + CBaseFile* file = nullptr; + // iVar4 = iVar14; + + // basic check if this command is even valid: + if (cmd && (file = cmd->m_pBaseFile, file) && cmd->status == EIsoStatus::OK_2 && + cmd->active_a != 0) { + u32 read_rate = file->m_ReadRate; + read_rate_total = read_rate_total + read_rate; // maybe this is an inverse rate... + + // build up arrays of info for each command + read_rates_array[cmds_total] = read_rate; + cmds_array[cmds_total] = cmd; + + if ((int)read_rate < 1) { + min_nospeed_pages_total = min_nospeed_pages_total + file->m_Buffer.m_nMinNumPages; + max_pages_total = max_pages_total + file->m_Buffer.m_nMaxNumPages; + } else { + min_speed_pages_total = min_speed_pages_total + file->m_Buffer.m_nMinNumPages; + cmds_with_speed_total = cmds_with_speed_total + 1; + } + + CPageList* plist = file->m_Buffer.m_pPageList; + + s32 npages = 0; + if (plist != (CPageList*)0x0) { + npages = plist->m_nNumPages; + } + pages_total = pages_total + npages; + num_pages_array[cmds_total] = npages; + + s32 n_untepped_pages = 0; + if (plist != (CPageList*)0x0) { + n_untepped_pages = plist->m_nNumUnsteppedPages; + } + unstepped_pages_array[cmds_total] = n_untepped_pages; + + s32 n_remaining_pages = 4; + if (cmd->callback != RunDGOStateMachine) { + n_remaining_pages = n_untepped_pages + file->m_LengthPages - file->m_PageOffset; + // lg::warn("remaining pages is {} = {} + {} - {}", n_remaining_pages, n_untepped_pages, + // file->m_LengthPages, file->m_PageOffset); + } + remaining_pages_array[cmds_total] = n_remaining_pages; + + if (remaining_pages_array[cmds_total] < 1) { + remaining_pages_array[cmds_total] = 1; + } + + // careful, this increments in a weird spot. + // I use cmds_total - 1 below... + int old_cmds_total = cmds_total; + cmds_total = cmds_total + 1; + + // iVar4 = iVar14 + 4; + + // next, we'll determine a desired page cutoff and discard commands where + // we have enough pages. + if (read_rate && plist) { + // 3/4 * mNumPages, this is if our allocated buffer is 75% full. + s32 desired_pages = (int)(file->m_nNumPages * 0x30) >> 6; + + // but, we want at least min pages + if (desired_pages < file->m_Buffer.m_nMinNumPages) { + desired_pages = file->m_Buffer.m_nMinNumPages; + } + + // and we want at least 2 + if (desired_pages < 2) { + desired_pages = 2; + } + + // but, we never want more pages than we plan to eventually read. + if (remaining_pages_array[old_cmds_total] < desired_pages) { + desired_pages = remaining_pages_array[old_cmds_total]; + } + + // if we have that many, we can just forget the command - no point in filling it now. + if (desired_pages <= unstepped_pages_array[old_cmds_total]) + goto LAB_00008420; + } + + s32 iVar14 = iVar9; + if ((0 < (int)read_rate) && + (iVar14 = (int)(file->m_Buffer.m_nDataLength * 1000) / (int)read_rate, + read_rate == 0)) { + ASSERT_NOT_REACHED(); + } + + s32 pri = cmd->priority; + + // really not sure what this is... + if (been_a_while) { + if ((read_rate == 0) || (iVar14 < 0x2ee)) { + been_a_while = false; + goto LAB_000080e4; + } + + // if this isn't the command we said last time. + if (g_selected_cmd != cmd) { + s32 current_sector = file->GetSector(); + current_sector = current_sector - unk_sector; + bool sector_ok = false; + if (current_sector < 0) { + current_sector = -current_sector; + if (unk_time_mflag != 0) { + LAB_000083ac: + current_sector = current_sector + 10000000; + } + sector_ok = current_sector < iVar13; + } else { + sector_ok = current_sector < iVar13; + if ((0 < current_sector) && (unk_time_mflag == 0)) + goto LAB_000083ac; + } + if (sector_ok) { + iVar13 = current_sector; + iVar8 = iVar14; + selected_cmd = cmd; + cmd2_read_rate = read_rate; + } + } + } else { + // normal selection logic??? + if ((((iVar14 == iVar8) && (selected_cmd_rem_sectors < pri)) || (iVar14 < iVar8)) && + (iVar14 <= iVar9)) { + iVar8 = iVar14; + selected_cmd = cmd; + cmd2_read_rate = read_rate; + selected_cmd_rem_sectors = pri; + } + } + } + LAB_00008420: + idx_on_level = idx_on_level + -1; + /* WARNING: ptrarith problems */ + ppIVar12 = ppIVar12 + -1; + // iVar14 = iVar4; + } while (-1 < idx_on_level); + } + pri_level--; + pri_level_idx = pri_level_idx + -1; + } while (-1 < pri_level_idx); + + if (selected_cmd) { + if (cmd2_read_rate == 0) { + unk_time_mode_flag = 1; + time_of_last_unknown_rate_drive_op = now; + } + iVar13 = unk_sector; + if (selected_cmd->m_pBaseFile) { + iVar13 = selected_cmd->m_pBaseFile->GetSector(); + } + if (unk_sector < iVar13) { + unk_time_mflag = 1; + unk_sector = iVar13; + } else { + been_a_while = iVar13 != unk_sector; + unk_sector = iVar13; + if (been_a_while) { + unk_time_mflag = 0; + unk_sector = iVar13; + } + } + } + min_speed_pages_total = min_speed_pages_total + min_nospeed_pages_total; + g_cmds_with_speed_total = cmds_with_speed_total; + g_selected_cmd = selected_cmd; + pages_total = get_page_manager()->m_CCache.m_nNumFreePages + pages_total; + if ((0 < min_speed_pages_total) && (0 < pages_total)) { + if (min_speed_pages_total == 0) { + // trap(0x1c00); + ASSERT_NOT_REACHED(); + } + min_speed_pages_total = (min_nospeed_pages_total * pages_total) / min_speed_pages_total; + if (min_speed_pages_total < min_nospeed_pages_total) { + min_speed_pages_total = min_nospeed_pages_total; + } + if (max_pages_total < min_speed_pages_total) { + min_speed_pages_total = max_pages_total; + } + pages_total = pages_total - min_speed_pages_total; + iVar9 = 0; + iVar13 = min_speed_pages_total; + iVar8 = pages_total; + if (0 < cmds_total) { + // iVar14 = 0; + iVar13 = min_speed_pages_total; + iVar8 = pages_total; + do { + CBaseFile* tfile = cmds_array[iVar9]->m_pBaseFile; + if (read_rates_array[iVar9] < 1) { + s32 uVar11 = (tfile->m_Buffer).m_nMaxNumPages; + if (max_pages_total == 0) { + ASSERT_NOT_REACHED(); + } + s32 uVar15 = (tfile->m_Buffer).m_nMinNumPages; + s32 uVar7 = (int)(uVar11 * min_speed_pages_total) / max_pages_total; + if ((int)uVar7 < (int)uVar15) { + uVar7 = uVar15; + } + + tfile->m_nNumPages = uVar7; + if ((int)uVar11 < (int)uVar7) { + uVar7 = uVar11; + } + tfile->m_nNumPages = uVar7; + if (remaining_pages_array[iVar9] < (int)uVar7) { + uVar7 = remaining_pages_array[iVar9]; + } + tfile->m_nNumPages = uVar7; + // lg::warn("num pages mod {}", uVar7); + iVar13 = iVar13 - uVar7; + } else { + s32 uVar11 = (tfile->m_Buffer).m_nMinNumPages; + s32 uVar7 = (tfile->m_Buffer).m_nMaxNumPages; + // lg::warn("taking else case: {} {} {}", uVar11, uVar7, tfile->m_nNumPages); + + s32 uVar15 = -1; + if (read_rate_total == 0) { + LAB_000085e4: + // lg::warn("going to min! (rrt is {})", read_rate_total); + uVar15 = uVar11; + } else { + if (read_rate_total == 0) { + ASSERT_NOT_REACHED(); + } + uVar15 = (read_rates_array[iVar9] * pages_total) / read_rate_total; + // lg::warn("read rate math is {}", uVar15); + if ((int)uVar15 < (int)uVar11) + goto LAB_000085e4; + } + + // lg::warn("vals {} {}", uVar7, uVar15); + if ((int)uVar7 < (int)uVar15) { + uVar15 = uVar7; + } + // lg::warn("vals 2 {} {}", remaining_pages_array[iVar9], uVar15); + if (remaining_pages_array[iVar9] < (int)uVar15) { + uVar15 = remaining_pages_array[iVar9]; + } + iVar8 = iVar8 - uVar15; + tfile->m_nNumPages = uVar15; + // lg::warn("num pages mod 2 {}", uVar15); + } + iVar9 = iVar9 + 1; + // iVar14 = iVar9 * 4; + } while (iVar9 < cmds_total); + } + while (0 < iVar13) { + iVar9 = 0; + s32 iVar14 = iVar13; + if (0 < cmds_total) { + s32 iVar4 = 0; + iVar14 = iVar13; + while (0 < iVar14) { + CBaseFile* tfile2 = cmds_array[iVar4 / 4]->m_pBaseFile; + iVar9 = iVar9 + 1; + s32 uVar11; + if (read_rates_array[iVar4 / 4] == 0 && + (uVar11 = tfile2->m_nNumPages, (int)uVar11 < remaining_pages_array[iVar4 / 4]) && + ((int)uVar11 < tfile2->m_Buffer.m_nMaxNumPages)) { + tfile2->m_nNumPages = uVar11 + 1; + iVar14 = iVar14 + -1; + } + if (cmds_total <= iVar9) + break; + iVar4 = iVar9 * 4; + } + } + been_a_while = iVar13 == iVar14; + iVar13 = iVar14; + if (been_a_while) { + iVar8 = iVar8 + iVar14; + iVar13 = 0; + } + } + while (0 < iVar8) { + iVar13 = 0; + iVar9 = iVar8; + if (0 < cmds_total) { + s32 iVar14 = 0; + iVar9 = iVar8; + while (0 < iVar9) { + CBaseFile* tfile4 = cmds_array[iVar14 / 4]->m_pBaseFile; + iVar13 = iVar13 + 1; + s32 uVar11; + if (((0 < read_rates_array[iVar14 / 4]) && + (uVar11 = tfile4->m_nNumPages, (int)uVar11 < remaining_pages_array[iVar14 / 4])) && + ((int)uVar11 < tfile4->m_Buffer.m_nMaxNumPages)) { + tfile4->m_nNumPages = uVar11 + 1; + iVar9 = iVar9 + -1; + } + if (cmds_total <= iVar13) + break; + iVar14 = iVar13 * 4; + } + } + been_a_while = iVar8 == iVar9; + iVar8 = iVar9; + if (been_a_while) { + iVar8 = 0; + } + } + iVar13 = 0; + if (0 < cmds_total) { + iVar8 = 0; + do { + CBaseFile* tfile3 = cmds_array[iVar8 / 4]->m_pBaseFile; + + iVar13 = iVar13 + 1; + s32 uVar11 = tfile3->m_nNumPages; + // lg::warn("mystery values: {} - {} = {} (from {})", num_pages_array[iVar8 / 4], + // uVar11, + // num_pages_array[iVar8 / 4] - uVar11, + // cmds_array[iVar8 / 4]->m_pBaseFile->m_FileDef->name.data); + if (((int)uVar11 < num_pages_array[iVar8 / 4]) && + (iVar8 = tfile3->RecoverPages(num_pages_array[iVar8 / 4] - uVar11), 0 < iVar8)) { + get_page_manager()->GarbageCollectPageList(tfile3->m_Buffer.m_pPageList); + } + iVar8 = iVar13 * 4; + } while (iVar13 < cmds_total); + } + } + return selected_cmd; +} + +int ProcessMessageData(ISO_Hdr* tgt_msg) { + EIsoStatus stat; + s32 ret = 1; +LAB_000088a4: + s32 num_remaining = gPriStack[0].count + -1; + if (num_remaining < 0) { + return ret; + } + auto* cmd_array = gPriStack[0].cmds + num_remaining; + ISO_Hdr* cmd = nullptr; + do { + EIsoStatus (*cb)(ISO_Hdr*) = nullptr; + cmd = *cmd_array; + if (cmd && cmd->active_a) { + stat = cmd->status; + if (stat == EIsoStatus::OK_2) { + auto* file = cmd->m_pBaseFile; + auto* buffer = &file->m_Buffer; + if (!file) { + buffer = nullptr; + } + if ((buffer && (buffer->m_eBufferType != CBuffer::BufferType::EBT_FREE)) && + (cb = cmd->callback, cb != ProcessVAGData)) { + stat = (cb)(cmd); + } + } + if (stat != EIsoStatus::OK_2) + break; + cmd->status = EIsoStatus::OK_2; + } + num_remaining = num_remaining + -1; + cmd_array = cmd_array + -1; + if (num_remaining < 0) { + return ret; + } + } while (true); + if (cmd == tgt_msg) { + ret = 0; + } + ReleaseMessage(cmd); + if (stat != EIsoStatus::IDLE_1) { + cmd->status = stat; + ReturnMessage(cmd); + } + goto LAB_000088a4; +} + +void ReturnMessage(ISO_Hdr* msg) { + if (msg->mbox_reply == 0) { + if (msg->thread_to_wake == 0) { + FreeVAGCommand((ISO_VAGCommand*)msg); + } else { + WakeupThread(msg->thread_to_wake); + } + } else { + SendMbx(msg->mbox_reply, msg); + } +} + +void ReleaseMessage(ISO_Hdr* msg) { + if (GetThreadId() == g_nISOThreadID) { + auto* file = msg->m_pBaseFile; + if (file && file->m_Status != EIsoStatus::NONE_0) { + file->Close(); + } + UnqueueMessage(msg); + } else { + if (msg->msg_type != ISO_Hdr::MsgType::ABADBABE) { + set_active_a(msg, 0); + set_active_c(msg, 0); + msg->msg_type = ISO_Hdr::MsgType::ABADBABE; + SendMbx(g_nISOMbx, msg); + } + } +} + +ISO_VAGCommand* GetVAGCommand() { + int iVar1; + u32 uVar2; + u32 uVar3; + + do { + while (vag_cmd_cnt == 0x1f) { + DelayThread(1000); + } + do { + iVar1 = WaitSema(g_VagCmdSema); + uVar2 = 0; + } while (iVar1 != 0); + do { + uVar3 = uVar2 + 1; + if (((vag_cmd_used >> (uVar2 & 0x1f) ^ 1U) & 1) != 0) { + vag_cmd_cnt = vag_cmd_cnt + 1; + vag_cmd_used = vag_cmd_used | 1 << (uVar2 & 0x1f); + if (max_vag_cmd_cnt < vag_cmd_cnt) { + max_vag_cmd_cnt = vag_cmd_cnt; + } + SignalSema(g_VagCmdSema); + return vag_cmds + uVar2; + } + uVar2 = uVar3; + } while ((int)uVar3 < 0x1f); + SignalSema(g_VagCmdSema); + } while (true); +} + +void FreeVAGCommand(ISO_VAGCommand* param_1) { + u32 uVar1; + int iVar2; + + uVar1 = param_1 - vag_cmds; + ASSERT(uVar1 < 16); + if ((uVar1 < 0x1f) && (((vag_cmd_used >> (uVar1 & 0x1f) ^ 1U) & 1) == 0)) { + do { + iVar2 = WaitSema(g_VagCmdSema); + } while (iVar2 != 0); + vag_cmd_cnt = vag_cmd_cnt - 1; + vag_cmd_used = vag_cmd_used & ~(1 << (uVar1 & 0x1f)); + SignalSema(g_VagCmdSema); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/iso_queue.h b/game/overlord/jak3/iso_queue.h new file mode 100644 index 0000000000..2ddcf2fd69 --- /dev/null +++ b/game/overlord/jak3/iso_queue.h @@ -0,0 +1,30 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_iso_queue(); +struct ISO_Hdr; +struct ISO_VAGCommand; +void ReleaseMessage(ISO_Hdr* msg); +void FreeVagCmd(ISO_VAGCommand* msg); +int QueueMessage(ISO_Hdr* msg, int pri); +int UnqueueMessage(ISO_Hdr* msg); +void ReturnMessage(ISO_Hdr* msg); +void InitBuffers(); +bool DgoCmdWaiting(); +ISO_Hdr* GetMessage(); +int ProcessMessageData(ISO_Hdr* msg); +void FreeVAGCommand(ISO_VAGCommand* msg); +ISO_VAGCommand* GetVAGCommand(); + +struct PriStackEntry { + ISO_Hdr* cmds[8]; + int count = 0; +}; + +extern u32 g_auTrapSRAM[6]; +extern u32 g_auStrmSRAM[6]; +extern s32 g_nPriQueueSema; +extern PriStackEntry gPriStack[2]; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/isocommon.cpp b/game/overlord/jak3/isocommon.cpp new file mode 100644 index 0000000000..0caff30852 --- /dev/null +++ b/game/overlord/jak3/isocommon.cpp @@ -0,0 +1,118 @@ +#include "isocommon.h" + +#include "common/util/Assert.h" + +namespace jak3 { +void jak3_overlord_init_globals_isocommon() {} + +/*! + * Convert file name to "ISO Name" + * ISO names are upper case and 12 bytes long. + * xxxxxxxxyyy0 + * + * x - uppercase letter of file name, or space + * y - uppercase letter of file extension, or space + * 0 - null terminator (\0, not the character zero) + */ +void MakeISOName(ISOName* dst, const char* src) { + int i = 0; + const char* src_ptr = src; + char* dst_ptr = dst->data; + + // copy name and upper case + while ((i < 8) && (*src_ptr) && (*src_ptr != '.')) { + char c = *src_ptr; + src_ptr++; + if (('`' < c) && (c < '{')) { // lower case + c -= 0x20; + } + *dst_ptr = c; + dst_ptr++; + i++; + } + + // pad out name with spaces + while (i < 8) { + *dst_ptr = ' '; + dst_ptr++; + i++; + } + + // increment past period + if (*src_ptr == '.') + src_ptr++; + + // same for extension + while (i < 11 && (*src_ptr)) { + char c = *src_ptr; + src_ptr++; + if (('`' < c) && (c < '{')) { // lower case + c -= 0x20; + } + *dst_ptr = c; + dst_ptr++; + i++; + } + + while (i < 11) { + *dst_ptr = ' '; + dst_ptr++; + i++; + } + *dst_ptr = 0; +} + +// UnmakeISOName + +/*! + * Pack an 8-character (64-bits) file name into a packed vag file name (32 + 10 = 42 bit). + */ +int PackVAGFileName(u32* out, const char* name) { + if (!out || !name) { + return 0; + } + int ret = 1; + + // accumulator of up to 4 packed characters + u32 acc = 0; + u32 first_four = 0; + for (int i = 0; i < 8; i++) { + // start the second word: + if (i == 4) { + first_four = acc; + acc = 0; + } + + // read character from input + u32 name_char = *((const u8*)name); + name++; + + u32 remapped_char; + + if (name_char - 'A' < 26) { // capital letter + remapped_char = name_char - 'A' + 1; // so A becomes 1. + } else if (name_char - 'a' < 26) { // lowercase letter + remapped_char = name_char - 'a' + 1; // so a becomes 1. + } else if (name_char - '0' < 10) { // digit + remapped_char = name_char - '0' + 27; // so 0 becomes 27 + } else if (name_char == '-') { + remapped_char = 37; + } else if (name_char == ' ' || name_char == '\0') { + remapped_char = 0; + } else { + ASSERT_NOT_REACHED(); // invalid char in input. + } + + ASSERT((remapped_char & 0xff) == remapped_char); + acc = acc * 38 + remapped_char; // (null + alphabet + 10 + dash) + } + + out[0] = (first_four << 0x15) | acc; + out[1] = first_four >> 0xb; + + return ret; +} + +// UnpackVAGFileName - nobody uses it... + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/isocommon.h b/game/overlord/jak3/isocommon.h new file mode 100644 index 0000000000..a6f4a720ab --- /dev/null +++ b/game/overlord/jak3/isocommon.h @@ -0,0 +1,152 @@ +#pragma once + +#include + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_isocommon(); + +struct CBaseFile; + +struct ISOFileDef; + +enum class EIsoStatus { + NONE_0 = 0, + IDLE_1 = 0x1, + OK_2 = 0x2, // already reading, or no need to read, or no mem to read. + FAILED_TO_QUEUE_4 = 0x4, + ERROR_OPENING_FILE_8 = 0x8, + NULL_CALLBACK = 9, + ERROR_NO_FILE = 0xa, + ERROR_b = 0xb, // tried to do BeginRead on a file that's not allocated. + ERROR_NO_SOUND = 0xc, +}; + +struct SoundBankInfo; + +struct ISO_Hdr { + int unka; + int unkb; + EIsoStatus status; + int8_t active_a; + int8_t active_b; + int8_t active_c; + int8_t pad; + + enum class MsgType : u32 { + MSG_0 = 0, + LOAD_EE = 0x100, + LOAD_IOP = 0x101, + LOAD_EE_CHUNK = 0x102, + LOAD_SOUNDBANK = 0x103, + DGO_LOAD = 0x200, + VAG_QUEUE = 0x400, + VAG_STOP = 0x402, + VAG_PAUSE = 0x403, + VAG_UNPAUSE = 0x404, + VAG_SET_PITCH_VOL = 0x406, + PLAY_MUSIC_STREAM = 0x408, + ABADBABE = 0xabadbabe, + ADEADBEE = 0xadeadbee, + } msg_type = MsgType::MSG_0; + + int mbox_reply; + int thread_to_wake; + + EIsoStatus (*callback)(ISO_Hdr*) = nullptr; + CBaseFile* m_pBaseFile = nullptr; + int priority = 0; + const ISOFileDef* file_def = nullptr; +}; + +struct ISO_LoadCommon : public ISO_Hdr { + u8* addr = 0; // 44 + int maxlen = 0; + int length_to_copy = 0; // 52 + u8* dest_ptr = 0; // 68 (not truly common??) what are they doing here. + int progress_bytes = 0; // 72 +}; + +struct ISO_LoadSingle : public ISO_LoadCommon { + // addr + // maxlen + // maybe size + int sector_offset = 0; // 56 + // 60 + // 64 + // dest_ptr + // maybe ofset +}; + +struct ISO_LoadSoundbank : public ISO_LoadCommon { + const char* name = nullptr; // 60 + int priority; // 64 + SoundBankInfo* bank_info = nullptr; +}; + +struct CPage; + +struct BlockParams { + void* destination; + int num_sectors; + int sector_num; + // ADDED + const ISOFileDef* file_def; + CPage* page; + char* flag; +}; + +struct CDescriptor; + +struct Block { + BlockParams params; + CDescriptor* descriptor; + Block* next; +}; + +struct ISOName { + char data[12]; + + bool operator==(const ISOName& other) { + for (int i = 0; i < 12; i++) { + if (data[i] != other.data[i]) { + return false; + } + } + return true; + } +}; + +struct ISOFileDef { + ISOName name; + std::string full_path; // pc + u32 length; +}; + +struct VagDirEntry { + u32 words[2]; +}; + +struct VagDir { + int vag_magic_1 = 0; + int vag_magic_2 = 0; + int vag_version = 0; + int num_entries = 0; + VagDirEntry entries[4096]; +}; +static_assert(sizeof(VagDir) == 0x8010); + +constexpr int MUSIC_TWEAK_COUNT = 0x40; +struct MusicTweaks { + u32 TweakCount = 0; + struct { + char MusicName[12] = "\0"; + s32 VolumeAdjust = 0; + } MusicTweak[MUSIC_TWEAK_COUNT]; +}; + +int PackVAGFileName(u32* out, const char* name); +void MakeISOName(ISOName* dst, const char* src); + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/list.cpp b/game/overlord/jak3/list.cpp new file mode 100644 index 0000000000..b293894b76 --- /dev/null +++ b/game/overlord/jak3/list.cpp @@ -0,0 +1,54 @@ +#include "list.h" + +#include "common/util/Assert.h" + +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" + +namespace jak3 { +using namespace iop; +void jak3_overlord_init_globals_list() {} + +void InitList(List* list, int count, int element_size) { + VagStreamData* iter; + int iVar1; + SemaParam sema_params; + + list->count = count; + iter = (VagStreamData*)AllocSysMemory(0, count * element_size, 0); + ASSERT(iter); + ASSERT(element_size == sizeof(VagStreamData)); + list->buffer = (u8*)iter; + if (iter != (VagStreamData*)0x0) { + list->next = iter; + iVar1 = 0; + if (0 < count) { + do { + iter->in_use = 0; + if (iVar1 < count + -1) { + iter->next = (VagStreamData*)((u8*)iter + element_size); + } else { + iter->next = nullptr; + } + if (iVar1 == 0) { + iter->prev = nullptr; + } else { + iter->prev = (VagStreamData*)((u8*)iter - element_size); + } + iVar1 = iVar1 + 1; + iter = (VagStreamData*)((u8*)iter + element_size); + } while (iVar1 < count); + } + list->unk_flag = 0; + list->pending_data = 0; + sema_params.max_count = 1; + sema_params.attr = 1; + sema_params.init_count = 1; + sema_params.option = 0; + iVar1 = CreateSema(&sema_params); + list->sema = iVar1; + ASSERT(list->sema >= 0); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/list.h b/game/overlord/jak3/list.h new file mode 100644 index 0000000000..de00d64a75 --- /dev/null +++ b/game/overlord/jak3/list.h @@ -0,0 +1,27 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_list(); + +struct VagStreamData; + +/*! + * The List system is a linked list used to track requested and playing streams. + * Originally, it supported multiple element types. + * One of those types is related to plugin streams which are not supported in PC. + * So, this is just hard-coded to use VagStreamData as the element type. + */ +struct List { + char name[8]; + int sema = 0; + int unk_flag = 0; // set when there's a free node?? + int count = 0; + int pending_data = 0; + VagStreamData* next = nullptr; + u8* buffer; +}; + +void InitList(List* list, int num_elements, int element_size); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/overlord.cpp b/game/overlord/jak3/overlord.cpp new file mode 100644 index 0000000000..368723f452 --- /dev/null +++ b/game/overlord/jak3/overlord.cpp @@ -0,0 +1,151 @@ +#include "overlord.h" + +#include "common/log/log.h" + +#include "game/overlord/jak3/dvd_driver.h" +#include "game/overlord/jak3/init.h" +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/ramdisk.h" +#include "game/overlord/jak3/sbank.h" +#include "game/overlord/jak3/srpc.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/vblank_handler.h" +#include "game/sce/iop.h" + +namespace jak3 { +using namespace iop; + +int g_nServerThreadID = 0; +int g_nPlayerThreadID = 0; +int g_nLoaderThreadID = 0; + +void jak3_overlord_init_globals_overlord() { + g_nServerThreadID = 0; + g_nPlayerThreadID = 0; + g_nLoaderThreadID = 0; +} + +namespace { + +/*! + * Start up overlord threads. After this returns, the overlord is initialized and + * ready to run. (this might have been in start.cpp in the original, but this is close enough) + */ +int start_overlord() { + // Initialize SIF to communicate with the EE. Does nothing in port + if (!sceSifCheckInit()) { + sceSifInit(); + } + // Initialize RPC to EE + sceSifInitRpc(0); + + lg::info("======== overlrd2.irx startup ========"); + // Removed some prints related to IOP memory size + + // C++ ctors ran here. In the port, we've added these "init_globals" function that + // take care of resetting all the global/static variables and constructing C++ objects. + // do_global_ctors(); + jak3_overlord_init_globals_all(); + InitBanks(); + InitSound(); + VBlank_Initialize(); + + // RPC thread to load data from game files to the game memory. + ThreadParam thread_param; + thread_param.initPriority = 0x3b; + thread_param.stackSize = 0x800; + thread_param.entry = LoadToEE_RPC_Thread; + thread_param.attr = TH_C; + thread_param.option = 0; + strcpy(thread_param.name, "load_to_ee"); + g_nServerThreadID = CreateThread(&thread_param); + if (g_nServerThreadID <= 0) { + return 1; + } + + // thread to respond to sound play RPC's + thread_param.entry = Thread_Player; + thread_param.initPriority = 0x36; + thread_param.stackSize = 0xb00; + thread_param.attr = TH_C; + thread_param.option = 0; + strcpy(thread_param.name, "player"); + g_nPlayerThreadID = CreateThread(&thread_param); + if (g_nPlayerThreadID <= 0) { + return 1; + } + + // thread to respond to sound load RPC's + thread_param.attr = TH_C; + thread_param.entry = Thread_Loader; + thread_param.initPriority = 0x3a; + thread_param.stackSize = 0x900; + thread_param.option = 0; + strcpy(thread_param.name, "loader"); + g_nLoaderThreadID = CreateThread(&thread_param); + if (g_nLoaderThreadID <= 0) { + return 1; + } + + // Initialize the dvd driver that will be used to implement the "ISO FS" system + get_driver()->Initialize(); + + // then, initialize ISO FS itself + InitISOFS(); + + // start up RPC threads! + StartThread(g_nServerThreadID, 0); + StartThread(g_nPlayerThreadID, 0); + StartThread(g_nLoaderThreadID, 0); + + lg::info("[overlord2] Threads started"); + return 0; +} + +bool* init_complete; + +/*! + * Wrapper of start_overlord that can be run as an IOP thread. Sets init_complete flag to true. + * This is a bit of hack to transition from the normal and sane game code to the world of IOP + * threading. + */ +u32 call_start() { + start_overlord(); + *init_complete = true; + + // TODO: figure out how to quit this thread. + while (true) { + SleepThread(); + } + return 0; +} +} // namespace + +int start_overlord_wrapper(bool* signal) { + ThreadParam param = {}; + init_complete = signal; + param.attr = TH_C; + param.initPriority = 0; + param.stackSize = 0x800; + param.option = 0; + strcpy(param.name, "start"); // added for debug + param.entry = call_start; + auto start_thread = CreateThread(¶m); + StartThread(start_thread, 0); + + return 0; +} + +/*! + * Copy null-terminated string to destination buffer with the given size. + * If the string doesn't fit, it will be truncated and null terminated. + */ +char* strncpyz(char* dst, const char* src, size_t n) { + if (n) { + strncpy(dst, src, n); + dst[n - 1] = 0; + } + return dst; +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/overlord.h b/game/overlord/jak3/overlord.h new file mode 100644 index 0000000000..80e7322959 --- /dev/null +++ b/game/overlord/jak3/overlord.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "common/common_types.h" +#include "common/log/log.h" + +namespace jak3 { + +/*! + * External entry point for the game to start Overlord. This assumes that the IOP Kernel + * is at least initialized, then sets up all overlord threads/RPCs. Once this returns, + * it's safe to call overlord functions. + */ +int start_overlord_wrapper(bool* signal); +void jak3_overlord_init_globals_overlord(); +char* strncpyz(char* dst, const char* src, size_t n); + +extern int g_nServerThreadID; +extern int g_nPlayerThreadID; +extern int g_nLoaderThreadID; + +enum class LogCategory { + PAGING, + FILESYSTEM, + WARN, + SPU_DMA_STR, + EE_DMA, + ISO_QUEUE, + VAG_SETUP, + DGO, + RPC, + STR_RPC, + PLAYER_RPC, + DRIVER, + NUM_CATETORIES +}; + +constexpr bool g_OverlordLogEnable[(int)LogCategory::NUM_CATETORIES] = { + false, // paging: cpage's, page manager, page crossing, etc + true, // filesystem: opening/finding files + true, // warning: something weird + false, // spu dma streaming: vag streaming, clocks, spu, dma + true, // ee dma: sending stuff to the ee (dgo, etc) + true, // iso queue: message queuing + true, // vag setup: creation of vag commands (lists, etc) + false, // dgo + true, // rpc in general + true, // str rpc + false, // PLAYER + false, // driver +}; + +template +void ovrld_log(LogCategory category, const std::string& format, Args&&... args) { + if (g_OverlordLogEnable[(int)category]) { + lg::info(format, std::forward(args)...); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/pagemanager.cpp b/game/overlord/jak3/pagemanager.cpp new file mode 100644 index 0000000000..88d829f49a --- /dev/null +++ b/game/overlord/jak3/pagemanager.cpp @@ -0,0 +1,852 @@ +#include "pagemanager.h" + +#include +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/overlord.h" +#include "game/sce/iop.h" + +using namespace iop; + +namespace jak3 { +namespace { +std::unique_ptr g_manager; +} +void jak3_overlord_init_globals_pagemanager() { + g_manager = std::make_unique(); +} + +CPageManager* get_page_manager() { + return g_manager.get(); +} + +/*! + * Add the given number of pages to the active list, returning the first in the active list. + * This should be called before writing to the pages. + * This grows the active list from the end and adds a reference count. + */ +CPage* CPageList::AddActivePages(int num_pages) { + ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED); + ASSERT(num_pages > 0); // game warned on this, might be ok to just return null + + if (m_nNumPages < num_pages + m_nNumActivePages) { + // the original game just gave you no pages, but that seems sus, so abort for now. + ASSERT_NOT_REACHED(); + } + + // pick the page to convert from non-active to active + CPage* first_to_convert; + if (m_pLastActivePage) { + // if we have active pages, go to the page after that + first_to_convert = m_pLastActivePage->m_pNextPage; + } else { + // otherwise, start at the beginning of the page list. + first_to_convert = m_pFirstPage; + } + + if (first_to_convert) { + // the first active page should stay the same if we have one + CPage* new_first_active = m_pFirstActivePage; + if (!new_first_active) { + // but if we don't, it'll be the first we convert. + new_first_active = first_to_convert; + } + + int page_idx = 0; // how many we've done + CPage* last_converted = nullptr; // last one that was finished + CPage* to_convert = first_to_convert; // next one to convert + if (0 < num_pages) { + // loop over pages and convert them!! + do { + ASSERT(to_convert->input_state == CPage::State::UNMAKRED); + last_converted = to_convert; + last_converted->input_state = CPage::State::ACTIVE; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~last_converted->mask); + int status = last_converted->AddRef(); + ASSERT(status >= 1); + page_idx = page_idx + 1; + if (!last_converted->m_pNextPage) + break; + to_convert = last_converted->m_pNextPage; + } while (page_idx < num_pages); + } + if (page_idx == num_pages) { + // success! all converted. + // CpuSuspendIntr(local_28); + // update the active range and count + m_pFirstActivePage = new_first_active; + m_pLastActivePage = last_converted; + m_nNumActivePages = m_nNumActivePages + page_idx; + m_nNumUnsteppedPages = m_nNumUnsteppedPages + page_idx; + if (!m_pCurrentActivePage) { + m_pCurrentActivePage = first_to_convert; + } + // CpuResumeIntr(local_28[0]); + return first_to_convert; + } else { + // darn, didn't have enough. undo what we did. + // Not really sure that we need to clear the even flag again. + while (0 < page_idx) { + new_first_active->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, + ~new_first_active->mask); + new_first_active->ReleaseRef(); + } + return nullptr; + } + } + return nullptr; +} + +/*! + * Try to remove count pages from the active list, starting at the end and working back. + * This will remove buffered that the user has not yet read. The data will have to be read from the + * DVD again, so this should only be used when more free pages are absolutely needed. + * + * This may remove fewer than requested. It will not remove the current active page, and always + * leaves at least 1 active page to avoid removing a page that's currently being used. + */ +int CPageList::RemoveActivePages(int count) { + // lg::error("Remove Active Pages {}", count); + int num_removed = 0; + ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED); + + // CpuSuspendIntr(local_28); + num_removed = 0; + + // only attempt if we have more than 1 active + if (count > 0 && m_nNumActivePages > 1) { + CPage* last_active = m_pLastActivePage; + CPage* current_active = m_pCurrentActivePage; + + // I'm not sure what the last->next = current check is actually doing. + // this check is added - I have no idea how this could happen, or why the game avoids removing + // pages in this case. Hopefully we don't hit it. + if (last_active) { + ASSERT(last_active->m_pNextPage != current_active); + } + + if (current_active && last_active && last_active->m_pNextPage != current_active) { + // assume we remove them all except 1. + num_removed = m_nNumActivePages + -1; + // but if that's more than we asked for, reduce. + if (count < num_removed) { + num_removed = count; + } + + // iterate backward to find the first to remove, stopping if we reach current_active, or we + // exceed the count to remove. + int num_pages_back = 0; + CPage* iter = last_active; + if (last_active != current_active && 0 < num_removed) { + do { + iter = iter->m_pPrevPage; + num_pages_back = num_pages_back + 1; + if (!iter) { + ASSERT_NOT_REACHED(); + } + } while (iter != current_active && num_pages_back < num_removed); + + if (0 < num_pages_back) { + // now, iterate forward and convert to inactive. + // go one forward, since the last loop terminated on the page after the last one we want. + CPage* fwd_iter = iter->m_pNextPage; + + // the last one that stays active is one after that + m_pLastActivePage = fwd_iter->m_pPrevPage; + m_nNumActivePages = m_nNumActivePages - num_pages_back; + m_nNumUnsteppedPages = m_nNumUnsteppedPages - num_pages_back; + + // the end of the conversion loop + CPage* end_iter = last_active->m_pNextPage; + num_removed = num_pages_back; + while (fwd_iter && fwd_iter != end_iter) { + fwd_iter->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~fwd_iter->mask); + fwd_iter->ReleaseRef(); + fwd_iter = fwd_iter->m_pNextPage; + } + } else { + // in this case, I think we return the wrong number of removed pages. + ASSERT_NOT_REACHED(); + } + } + } + } + // CpuResumeIntr(local_28[0]); + return num_removed; +} + +/*! + * Remove all active pages. + */ +void CPageList::CancelActivePages() { + ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED); + // CpuSuspendIntr(local_18); + CPage* last_active = m_pLastActivePage; + CPage* iter = m_pCurrentActivePage; + // note: keep the last active page pointing at the right point in the ring to allocate. + m_pLastActivePage = m_pCurrentActivePage; + m_pFirstActivePage = nullptr; + m_pCurrentActivePage = nullptr; + m_nNumActivePages = 0; + m_nNumUnsteppedPages = 0; + if (iter && last_active && last_active->m_pNextPage != iter) { + CPage* end = last_active->m_pNextPage; + do { + if (iter == end) + break; + iter->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~iter->mask); + iter->ReleaseRef(); + iter = iter->m_pNextPage; + } while (iter); + } + // CpuResumeIntr(local_18[0]); +} + +/*! + * Step the current active page forward. This will release the reference count added when the page + * became active. If no other references were added, this page may be Garbage Collected at any time. + */ +CPage* CPageList::StepActivePage() { + ASSERT(m_nAllocState == AllocState::EPLAS_ALLOCATED); + + CPage* new_current_active = nullptr; + // CpuSuspendIntr(local_18); + auto* current_active = m_pCurrentActivePage; + if (current_active && m_pLastActivePage && m_pLastActivePage->m_pNextPage != current_active) { + ASSERT(m_nNumActivePages > 0); + m_nNumUnsteppedPages = m_nNumUnsteppedPages + -1; + current_active->ReleaseRef(); + current_active->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~current_active->mask); + new_current_active = current_active->m_pNextPage; + ASSERT(new_current_active != m_pCurrentActivePage); + m_pCurrentActivePage = new_current_active; + } else { + ASSERT_NOT_REACHED(); // step past end of active warning, seems bad. + } + // CpuResumeIntr(local_18[0]); + return new_current_active; +} + +/*! + * Remove pages that are no longer needed. They will be sent back to the Page Manager. + */ +void CPageList::GarbageCollect() { + ovrld_log(LogCategory::PAGING, "[paging] Garbage collecting, currently have {} pages, {} active", + m_nNumPages, m_nNumActivePages); + + // for (auto* p = m_pFirstPage; p; p = p->m_pNextPage) { + // ovrld_log(LogCategory::PAGING, + // "page 0x{:x}, first active? {} last active? {} current active? {} last? {}", + // p->m_nPageIdx, p == m_pFirstActivePage, p == m_pLastActivePage, + // p == m_pCurrentActivePage, p == m_pLastPage); + // } + // trim pages at the front. Anything unreferenced before the current active page is ok to clean. + CPage* page = m_pFirstPage; + if (page && page != m_pCurrentActivePage) { + ASSERT(page->m_nAllocState == 1); // pages in CPageLists should always be allocated. + + while (page->m_nPageRefCount == 0 && page->m_nDmaRefCount == 0) { // only unref'd pages. + ASSERT(page->m_nAllocState == 1); // prior to active. + CPage* next_page = page->m_pNextPage; + // CpuSuspendIntr(&local_18); + m_nNumPages = m_nNumPages + -1; + // pop page from our normal list + m_pFirstPage = next_page; + if (m_pLastPage == page) { + m_pLastPage = nullptr; + // sanity check - we just killed our last page from the list, count should be 0 + ASSERT(m_nNumPages == 0); + } + + // maintain active list too + if (m_pFirstActivePage == page) { + m_nNumActivePages = m_nNumActivePages + -1; + if (page == m_pLastActivePage) { + // since we're getting rid of this page from our list, don't keep a ref to it. + m_pFirstActivePage = nullptr; + m_pLastActivePage = nullptr; + ASSERT(m_nNumActivePages == 0); // sanity check count. + } else { + m_pFirstActivePage = next_page; + } + } + if (m_pLastActivePage == page) { + m_pLastActivePage = nullptr; + } + if (next_page) { + next_page->m_pPrevPage = nullptr; + } + // CpuResumeIntr(local_18); + + // now clean the page itself + ovrld_log(LogCategory::PAGING, "[paging] GC took page 0x{:x} (fwd)", page->m_nPageIdx); + page->m_pPageList = nullptr; + page->m_pPrevPage = nullptr; + page->m_pNextPage = nullptr; + page->m_nAllocState = 0; + page->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask); + int page_idx = page - get_page_manager()->m_CCache.m_Pages; + if (page_idx < 0x1d) { + get_page_manager()->m_CCache.m_nAllocatedMask &= ~(1 << (page_idx & 0x1f)); + } else { + ASSERT_NOT_REACHED(); // idk + } + get_page_manager()->m_CCache.m_nNumFreePages++; + if (!next_page) { + break; + } + page = next_page; + if (page == m_pCurrentActivePage) { + break; + } + } + } + + // now, start at the end and work backward. We'll stop once we reach the last active page, since + // we expect everything before that to have a nonzero ref count. + page = m_pLastPage; + if (page && page != m_pLastActivePage && page != m_pCurrentActivePage) { + ASSERT(page->m_nAllocState == 1); + while ((page->m_nPageRefCount == 0 && (page->m_nDmaRefCount == 0))) { + ovrld_log(LogCategory::PAGING, "[paging] GC took page 0x{:x} (bwd)", page->m_nPageIdx); + ASSERT(page->m_nAllocState == 1); + CPage* prev = page->m_pPrevPage; + // CpuSuspendIntr(&local_14); + m_nNumPages = m_nNumPages + -1; + m_pLastPage = prev; + if (m_pFirstPage == page) { + m_pFirstPage = nullptr; + ASSERT(m_nNumPages == 0); + } + if (prev != (CPage*)0x0) { + prev->m_pNextPage = (CPage*)0x0; + } + // CpuResumeIntr(local_14); + page->m_pPageList = nullptr; + page->m_pPrevPage = nullptr; + page->m_pNextPage = nullptr; + page->m_nAllocState = 0; + page->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask); + int page_idx = page - get_page_manager()->m_CCache.m_Pages; + if (page_idx < 0x1d) { + get_page_manager()->m_CCache.m_nAllocatedMask &= ~(1 << (page_idx & 0x1f)); + } else { + ASSERT_NOT_REACHED(); + } + get_page_manager()->m_CCache.m_nNumFreePages++; + + if (!prev) { + return; + } + if (prev == m_pLastActivePage) { + return; + } + page = prev; + if (prev == m_pCurrentActivePage) { + return; + } + } + } + + ovrld_log(LogCategory::PAGING, + "[paging] Done Garbage collecting, currently have {} pages, {} active in 0x{:x}", + m_nNumPages, m_nNumActivePages, (u64)this); +} + +/*! + * Wait on the pages indicated by the mask. + */ +void CPageManager::WaitForPagesFilled(u32 mask) { + if ((mask & m_CCache.m_PendingMask) != 0) { + WaitEventFlag(m_CCache.m_PagesFilledEventFlag, mask, 0); + } else { + // waiting for something that's not pending... might be ok. was a warning. + ASSERT_NOT_REACHED(); + } +} + +CPage::CPage(uint8_t* page_mem_start, uint8_t* page_mem_end, int page_idx) { + m_pNextPage = nullptr; + mask = 1 << (page_idx & 0x1f); + m_pPrevPage = nullptr; + m_pPageMemStart = page_mem_start; + m_pPageList = nullptr; + m_pPageMemEnd = page_mem_end; + m_nPageRefCount = 0; + m_nPageIdx = page_idx; + m_nDmaRefCount = 0; + m_nAllocState = 0; + input_state = State::UNMAKRED; +} + +/*! + * Add one to this CPage's reference count, preventing it from being garbage collected + */ +int CPage::AddRef() { + // CpuSuspendIntr(local_18); + auto* page_list = m_pPageList; + int ret = -1; + ASSERT(page_list); + ASSERT(m_nAllocState == 1); + if (m_nAllocState == 1 && page_list) { + page_list->m_nPageRefCnt = page_list->m_nPageRefCnt + 1; + m_nPageRefCount = m_nPageRefCount + 1; + ret = m_nPageRefCount; + } + // CpuResumeIntr(local_18[0]); + return ret; +} + +/*! + * Subtract one from this CPage's reference count. + */ +int CPage::ReleaseRef() { + // CpuSuspendIntr(local_18); + auto* page_list = m_pPageList; + int ret = -1; + ASSERT(page_list); + ASSERT(m_nAllocState == 1); + if (m_nAllocState == 1 && page_list) { + page_list->m_nPageRefCnt = page_list->m_nPageRefCnt - 1; + m_nPageRefCount = m_nPageRefCount - 1; + ret = m_nPageRefCount; + ASSERT(ret >= 0); + } + // CpuResumeIntr(local_18[0]); + return ret; +} + +/*! + * Add one to the DMA reference count of this page + */ +int CPage::AddDmaRef() { + // CpuSuspendIntr(local_18); + auto* page_list = m_pPageList; + int ret = -1; + ASSERT(page_list); + ASSERT(m_nAllocState == 1); + if (m_nAllocState == 1 && page_list) { + page_list->m_nDmaRefCnt = page_list->m_nDmaRefCnt + 1; + m_nDmaRefCount = m_nDmaRefCount + 1; + ret = m_nPageRefCount; + } + // CpuResumeIntr(local_18[0]); + return ret; +} + +int CPage::ReleaseDmaRef() { + // CpuSuspendIntr(local_18); + auto* page_list = m_pPageList; + int ret = -1; + ASSERT(page_list); + ASSERT(m_nAllocState == 1); + if (m_nAllocState == 1 && page_list) { + page_list->m_nDmaRefCnt = page_list->m_nDmaRefCnt - 1; + m_nDmaRefCount = m_nDmaRefCount - 1; + ret = m_nPageRefCount; + ASSERT(ret >= 0); + } + // CpuResumeIntr(local_18[0]); + return ret; +} + +/*! + * Copy data from this page to destination. This works with sizes that are greater than the page + * size, and will look at future pages. However, it does not actually advance progress in the page. + */ +void CPage::FromPagesCopy(uint8_t* in, uint8_t* dest, s32 size) { + ASSERT(in); + ASSERT(dest); + ASSERT(size >= 0); + CPage* page = this; + if (0 < size) { + while (true) { + s32 input_page_left = (page->m_pPageMemEnd - in) + 1; + ASSERT(input_page_left >= 0); + if (size < input_page_left) + break; + size -= input_page_left; + memcpy(dest, in, input_page_left); + dest += input_page_left; + if (size == 0) { + return; + } + ASSERT(size > 0); + ASSERT(page->m_pNextPage); + page = page->m_pNextPage; + in = page->m_pPageMemStart; + } + memcpy(dest, in, size); + } +} + +CCache::CCache() { + m_PendingMask = kAllPagesMask; + m_PagesFilledEventFlag = -1; + m_nNumFreePages = 0; + m_nAllocatedMask = 0; + m_nPagelistAllocatedMask = 0; +} + +void CCache::Initialize() { + static_assert(0xe81d0 == kPageStride * kNumPages); + m_paCache = AllocSysMemory(0, kPageStride * kNumPages, nullptr); + ASSERT(m_paCache); + m_nNumFreePages = kNumPages; + + // initialize pageslists + for (auto& page_list : m_PageLists) { + page_list.m_pFirstActivePage = nullptr; + page_list.m_pLastActivePage = nullptr; + page_list.m_pCurrentActivePage = nullptr; + + page_list.m_pFirstPage = nullptr; + page_list.m_pLastPage = nullptr; + page_list.m_nNumActivePages = 0; + page_list.m_nNumUnsteppedPages = 0; + page_list.m_nPageRefCnt = 0; + page_list.m_nDmaRefCnt = 0; + page_list.m_nAllocState = CPageList::AllocState::EPLAS_FREE; + } + + u8* mem = (u8*)m_paCache; + for (int i = 0; i < kNumPages; i++) { + m_Pages[i] = CPage(mem, mem + kPageSize - 1, i); + // interestingly, the stride is a bit longer. + mem += kPageStride; + } + + m_nAllocatedMask = 0; + m_nPagelistAllocatedMask = 0; + + EventFlagParam param; + param.attr = 2; + param.option = 0; + param.init_pattern = 0; + m_PagesFilledEventFlag = CreateEventFlag(¶m); // TODO args here + ASSERT(m_PagesFilledEventFlag >= 0); +} + +/*! + * Increase the length by the given amount. This is used for the DVD reading side to inform the + * consumer that there is more data available. + */ +void CBuffer::AddData(int len) { + // suspend interrupts + m_nDataLength += len; + // resume interrupts +} + +/*! + * Advance the current point in the buffer. This is used by the consume to mark forward progress. + */ +void CBuffer::AdvanceCurrentData(int len) { + // suspend interrupts + m_nDataLength -= len; + m_pCurrentData += len; + // resume interrupts +} + +/*! + * Set up pages. + */ +void CPageManager::Initialize() { + m_CCache.Initialize(); +} + +CPageList* CPageManager::AllocPageListBytes(int bytes, bool flag) { + return AllocPageList((bytes + kPageSize - 1) / kPageSize, flag); +} + +s32 alloc_bitmask(u32* mask, u32 length, u32 start = 0) { + for (u32 i = start; i < length; i++) { + if ((*mask & (1 << i)) == 0) { + // it's free! + (*mask) |= (1 << i); + return i; + } + } + return -1; +} + +/*! + * Allocate a PageList with the given number of pages. + */ +CPageList* CPageManager::AllocPageList(int count, bool consecutive_pages) { + ASSERT(count > 0); + ASSERT(count <= CCache::kNumPages); + + if (count > m_CCache.m_nNumFreePages) { + // if we're out of pages, use RecoverPages to discard pages that we've already read, but + // nobody is using yet. We'll be able to read them from the DVD again. + lg::warn("Recovering pages - {} requested in AllocPageList, but only {} available", count, + m_CCache.m_nNumFreePages); + RecoverPages(count); + ASSERT(m_CCache.m_nNumFreePages >= count); + } + + // next, find a pagelist. the original game had some fancy bit magic here, but this is simpler + int plist_idx = alloc_bitmask(&m_CCache.m_nPagelistAllocatedMask, CCache::kNumPageLists); + ASSERT(plist_idx >= 0); + CPageList* plist = &m_CCache.m_PageLists[plist_idx]; + + // Fill this array with allocated pages + CPage* pages[CCache::kNumPages]; + int pages_allocated = 0; + int last_page_allocated = -1; + int next_page_to_check = 0; + + while (pages_allocated < count) { + if (next_page_to_check >= CCache::kNumPages) { + break; + } + // grab the next page + int page_idx = alloc_bitmask(&m_CCache.m_nAllocatedMask, CCache::kNumPages, next_page_to_check); + ASSERT(page_idx >= 0); + + // start after this page on the next search + next_page_to_check = page_idx + 1; + + pages[pages_allocated] = &m_CCache.m_Pages[page_idx]; + pages_allocated++; + + // if we asked for consecutive pages, but didn't get them, we need to rewind our progress. + // but, we shouldn't rewind next_page_to_check! + if (consecutive_pages && last_page_allocated != -1 && last_page_allocated + 1 != page_idx) { + page_idx = -1; + while (pages_allocated) { + pages_allocated--; + + u32 i = pages[pages_allocated] - m_CCache.m_Pages; + ASSERT(i >= 0 && i < CCache::kNumPages); + m_CCache.m_nAllocatedMask &= ~(1 << i); + } + } + + last_page_allocated = page_idx; + } + + if (pages_allocated != count) { + // allocation failed + ASSERT_NOT_REACHED(); + } + + m_CCache.m_nNumFreePages -= count; + ASSERT(m_CCache.m_nNumFreePages >= 0); + + // zero everything + plist->m_pFirstPage = nullptr; + plist->m_pLastPage = nullptr; + plist->m_pFirstActivePage = nullptr; + plist->m_pLastActivePage = nullptr; + plist->m_pCurrentActivePage = nullptr; + plist->m_nNumPages = 0; + plist->m_nNumActivePages = 0; + plist->m_nNumUnsteppedPages = 0; + ASSERT(plist->m_nPageRefCnt == 0); + ASSERT(plist->m_nDmaRefCnt == 0); + + plist->m_nAllocState = CPageList::AllocState::EPLAS_ALLOCATED; + + // set up the pages + CPage* prev = nullptr; + CPage* page = nullptr; + for (int i = 0; i < pages_allocated; i++) { + page = pages[i]; + page->m_pPageList = plist; + page->m_pPrevPage = prev; + if (prev) { + prev->m_pNextPage = page; + } + page->m_nAllocState = 1; + page->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask); + prev = page; + } + page->m_pNextPage = nullptr; + plist->m_nNumPages = count; + plist->m_pLastPage = page; + plist->m_pFirstPage = pages[0]; + return plist; +} + +/*! + * Allocate page_count more pages, add them to the end of the list. This can be used to get more + * pages for the DVD driver to write to. + */ +CPageList* CPageManager::GrowPageList(jak3::CPageList* in, int page_count) { + ASSERT(in); + ASSERT(in->m_nAllocState == CPageList::AllocState::EPLAS_ALLOCATED); + ASSERT(page_count >= 0); + + if (page_count > m_CCache.m_nNumFreePages) { + // if we're out of pages, use RecoverPages to discard pages that we've already read, but + // nobody is using yet. We'll be able to read them from the DVD again. + lg::warn("Recovering pages - {} requested in AllocPageList, but only {} available", page_count, + m_CCache.m_nNumFreePages); + RecoverPages(page_count); + ASSERT(m_CCache.m_nNumFreePages >= page_count); + } + + CPage* pages[CCache::kNumPages]; + int next_page_to_check = 0; + int pages_allocated = 0; + while (pages_allocated != page_count) { + if (next_page_to_check >= CCache::kNumPages) { + break; + } + int page_idx = alloc_bitmask(&m_CCache.m_nAllocatedMask, CCache::kNumPages, next_page_to_check); + ASSERT(page_idx >= 0); // otherwise need to handle this better + next_page_to_check = page_idx + 1; + pages[pages_allocated] = &m_CCache.m_Pages[page_idx]; + pages_allocated++; + } + + if (pages_allocated != page_count) { + ASSERT_NOT_REACHED(); + } + + m_CCache.m_nNumFreePages -= pages_allocated; + + // set up the pages + CPage* prev = nullptr; + CPage* page = nullptr; + for (int i = 0; i < pages_allocated; i++) { + page = pages[i]; + ASSERT(page); + ASSERT(page->m_nAllocState == 0); + page->m_pPageList = in; + page->m_pPrevPage = prev; + if (prev) { + prev->m_pNextPage = page; + } + page->m_nAllocState = 1; + page->input_state = CPage::State::UNMAKRED; + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask); + prev = page; + } + page->m_pNextPage = nullptr; + + // suspendintr + if (in->m_nNumPages == 0) { + ASSERT(!in->m_pFirstPage); + ASSERT(!in->m_pLastPage); + ASSERT(!in->m_pFirstActivePage); + ASSERT(!in->m_pLastActivePage); + ASSERT(!in->m_pCurrentActivePage); + in->m_pFirstPage = pages[0]; + } else { + auto* old_end = in->m_pLastPage; + ASSERT(!old_end->m_pNextPage); + old_end->m_pNextPage = pages[0]; + pages[0]->m_pPrevPage = old_end; + } + in->m_pLastPage = page; + in->m_nNumPages += pages_allocated; + // resume intr + return in; +} + +/*! + * Return a PageList and its pages to the CCache. If the PageList is still referenced, the freeing + * will be deferred until GC. + */ +void CPageManager::FreePageList(jak3::CPageList* list) { + ASSERT(list); + ASSERT(list->m_nAllocState != CPageList::AllocState::EPLAS_FREE); + // suspend itr + list->m_nAllocState = CPageList::AllocState::FREE_PENDING; + // resume intr + + if (list->m_nDmaRefCnt || list->m_nPageRefCnt) { + lg::warn("Skipping free since list is referenced!"); + return; + } + + // loop over pages, clearing them. + CPage* page = list->m_pFirstPage; + int pages_count = 0; + int pages[CCache::kNumPages]; + + while (page) { + u32 page_slot = page - m_CCache.m_Pages; + ASSERT(page_slot < CCache::kNumPages); + pages[pages_count] = page_slot; + pages_count++; + auto* next = page->m_pNextPage; + page->m_pPageList = nullptr; + page->m_pPrevPage = nullptr; + page->m_pNextPage = nullptr; + page->m_nAllocState = 0; + page->input_state = CPage::State::UNMAKRED; + ASSERT(!page->m_nPageRefCount); + ASSERT(!page->m_nDmaRefCount); + ClearEventFlag(get_page_manager()->m_CCache.m_PagesFilledEventFlag, ~page->mask); + page = next; + } + + // clear list + if (pages_count != list->m_nNumPages) { + lg::die("Paging error: found {} pages out of {} in FreePageList", pages_count, + list->m_nNumPages); + } + ASSERT(pages_count == list->m_nNumPages); + list->m_pFirstActivePage = nullptr; + list->m_nNumPages = 0; + list->m_pLastActivePage = nullptr; + list->m_pCurrentActivePage = nullptr; + list->m_nNumActivePages = 0; + list->m_nNumUnsteppedPages = 0; + list->m_nAllocState = CPageList::AllocState::EPLAS_FREE; + m_CCache.m_nNumFreePages += pages_count; + ASSERT(m_CCache.m_nNumFreePages <= CCache::kNumPages); + list->m_pFirstPage = nullptr; + list->m_pLastPage = nullptr; + // unset bits in allocated mask + while (0 < pages_count) { + pages_count--; + m_CCache.m_nAllocatedMask &= ~(1 << pages[pages_count]); + } + // unset bit for the pagelist itself + u32 plist_idx = list - m_CCache.m_PageLists; + ASSERT(plist_idx >= 0 && plist_idx < CCache::kNumPageLists); + m_CCache.m_nPagelistAllocatedMask &= ~(1 << plist_idx); +} + +int CPageManager::RecoverPages(int) { + ASSERT_NOT_REACHED(); // TODO, this looks at pristack +} + +/*! + * Call GarbageCollect on all allocate CPageLists. + */ +void CPageManager::GarbageCollect() { + for (u32 i = 0; i < CCache::kNumPageLists; i++) { + if (m_CCache.m_nPagelistAllocatedMask & (1 << i)) { + GarbageCollectPageList(&m_CCache.m_PageLists[i]); + } + } +} + +/*! + * Do garbage collection of pages and page lists. + */ +void CPageManager::GarbageCollectPageList(jak3::CPageList* list) { + ASSERT(list); + list->GarbageCollect(); + // if we tried to free the list in the past, but failed, try to do it again now. + if (list->m_nAllocState == CPageList::AllocState::FREE_PENDING) { + FreePageList(list); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/pagemanager.h b/game/overlord/jak3/pagemanager.h new file mode 100644 index 0000000000..c82c62f042 --- /dev/null +++ b/game/overlord/jak3/pagemanager.h @@ -0,0 +1,181 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_pagemanager(); + +/*! + * CPages Overview + * + * Data is read from the DVD driver into CPages. The CPages are then given to consumers. + * Each file that's opened has an associated CPageList. + * + * The CPageList is a linked list of pages. Within this linked list, there is a section of "active + * pages" which have been filled by the DVD driver. There is also the "current active page", which + * is the page that the user will read from next. Note that pages before the current active page + * and still be referenced by the user, but they should keep a nonzero reference count so the CPage + * is not Garbage Collected. + * + * By default, each active page will have a ref count of 1. + * + * The CBuffer object owned by CBaseFile uses memory managed by this CPage system. + * + * The pages are preallocated by the CPageManager, which gives out pages as needed. + */ + +struct CPage; +struct CPageList; +struct ISO_Hdr; + +constexpr int kPageSize = 0x8000; +constexpr int kPageStride = 0x8010; + +/*! + * A CBuffer stores file data in pages, and tracks the progress through the page data as it is fed + * into the ISO data handling system. + */ +struct CBuffer { + // The pages in this buffer are managed by a PageList + CPageList* m_pPageList = nullptr; + + // The ISO command that requested us to load this data + ISO_Hdr* m_pIsoCmd = nullptr; + + // Current progress through the data (todo: load or process?) + uint8_t* m_pCurrentData = nullptr; + + // First address in the current page + uint8_t* m_pCurrentPageStart = nullptr; + + // todo + int m_nDataLength = 0; + + enum class BufferType { + // this is a really confusing enum... + EBT_FREE = 0, // there is no buffer allocate + NORMAL = 1, // a buffer sized for non-VAG stream operations (several possible sizes) + VAG = 2, // a buffer size for VAG streams (larger than normal sizes) + REQUEST_NORMAL = 3, // argument passed to InitBuffer to get a normal buffer + REQUEST_VAG = 4, // argument passed to InitBuffer to get a VAG size buffer + } m_eBufferType = BufferType::EBT_FREE; + + // Try to have at least this many pages filled + int m_nMinNumPages = 0; + + // Don't fill more than this number of pages + int m_nMaxNumPages = 0; + + void AddData(int len); + void AdvanceCurrentData(int len); +}; + +/*! + * List of pages for a file. + */ +struct CPageList { + CPageList() = default; // ??? + + // list of all pages + CPage* m_pFirstPage = nullptr; + CPage* m_pLastPage = nullptr; + + // list of active pages. This is part of the all page list + CPage* m_pFirstActivePage = nullptr; + CPage* m_pLastActivePage = nullptr; + + // page that the application is currently reading from + CPage* m_pCurrentActivePage = nullptr; + + // total number of CPage, including both active/inactive + int m_nNumPages = 0; + + // number of cpages in the active page list + int m_nNumActivePages = 0; + + // number of CPages remaining for the user, including the current active page + int m_nNumUnsteppedPages = 0; + + // Reference counters to know if this data is still needed or not. + int m_nPageRefCnt = 0; + int m_nDmaRefCnt = 0; + + enum class AllocState { + EPLAS_FREE = 0, + EPLAS_ALLOCATED = 1, + FREE_PENDING = 2, // FreePageList called, but no + } m_nAllocState = AllocState::EPLAS_FREE; + + CPage* StepActivePage(); + CPage* AddActivePages(int num_pages); + int RemoveActivePages(int num_pages); + void CancelActivePages(); + void GarbageCollect(); +}; + +/*! + * A single page, pointing to a contiguous buffer of file data. + */ +struct CPage { + CPage(uint8_t* page_mem_start, uint8_t* page_mem_end, int page_idx); + CPage() = default; // ??? + CPage* m_pNextPage = nullptr; + CPage* m_pPrevPage = nullptr; + CPageList* m_pPageList = nullptr; + int m_nPageRefCount = 0; + int m_nDmaRefCount = 0; + int m_nAllocState = 0; + enum class State { + UNMAKRED = 0, + ACTIVE = 1, + READING = 2, + READ_DONE = 3 + } input_state = State::UNMAKRED; + uint8_t* m_pPageMemStart = nullptr; + uint8_t* m_pPageMemEnd = nullptr; + u32 m_nPageIdx = 0; + u32 mask = 0; + + int AddRef(); + int ReleaseRef(); + int AddDmaRef(); + int ReleaseDmaRef(); + void FromPagesCopy(uint8_t* in, uint8_t* dest, s32 size); +}; + +/*! + * CCache contains the actual CPage/CPageList objects to be given out to files. + */ +struct CCache { + static constexpr int kNumPages = 29; + static constexpr int kNumPageLists = 29; + static constexpr int kAllPagesMask = (1 << kNumPages) - 1; + + CCache(); + void Initialize(); + void* m_paCache = nullptr; + CPageList m_PageLists[kNumPageLists]; + CPage m_Pages[kNumPages]; + int m_nNumFreePages = 0; + u32 m_nAllocatedMask; + u32 m_nPagelistAllocatedMask; + int m_PagesFilledEventFlag; + int m_PendingMask; +}; + +struct CPageManager { + CCache m_CCache; + + CPageList* GrowPageList(CPageList* in, int page_count); + CPageList* AllocPageList(int count, bool flag); + CPageList* AllocPageListBytes(int bytes, bool flag); + void FreePageList(CPageList* list); + void WaitForPagesFilled(u32 mask); + void Initialize(); + int RecoverPages(int k); + void GarbageCollect(); + void GarbageCollectPageList(CPageList* list); +}; + +CPageManager* get_page_manager(); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/ramdisk.cpp b/game/overlord/jak3/ramdisk.cpp new file mode 100644 index 0000000000..33676d252d --- /dev/null +++ b/game/overlord/jak3/ramdisk.cpp @@ -0,0 +1,50 @@ +#include "ramdisk.h" + +#include "common/util/Assert.h" + +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/iso_api.h" +#include "game/overlord/jak3/rpc_interface.h" +#include "game/sce/iop.h" + +namespace jak3 { + +constexpr int kRamdiskBufSize = 512; +u8 gRamdiskRpcBuf[kRamdiskBufSize]; + +void jak3_overlord_init_globals_ramdisk() {} + +/*! + * RPC Handler for "load to ee" rpc. + */ +void* RPC_LoadToEE(unsigned int fno, void* msg_ptr, int) { + switch (fno) { + case LoadToEEFno::LOAD_FILE: { + RpcLoadToEEMsg* msg = (RpcLoadToEEMsg*)(msg_ptr); + auto f = FindISOFile(msg->name); + ASSERT(f); + LoadISOFileToEE(f, msg->addr, msg->length); + } break; + default: + ASSERT_NOT_REACHED(); + } + return nullptr; +} + +u32 LoadToEE_RPC_Thread() { + using namespace iop; + + sceSifQueueData dq; + sceSifServeData serve; + + // set up RPC + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, RpcId::LoadToEE, RPC_LoadToEE, gRamdiskRpcBuf, kRamdiskBufSize, nullptr, + nullptr, &dq); + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/ramdisk.h b/game/overlord/jak3/ramdisk.h new file mode 100644 index 0000000000..acd14e58b8 --- /dev/null +++ b/game/overlord/jak3/ramdisk.h @@ -0,0 +1,10 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_ramdisk(); + +u32 LoadToEE_RPC_Thread(); + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/rpc_interface.h b/game/overlord/jak3/rpc_interface.h new file mode 100644 index 0000000000..4b2d4d49aa --- /dev/null +++ b/game/overlord/jak3/rpc_interface.h @@ -0,0 +1,280 @@ +#pragma once + +#include "common/common_types.h" + +/*! + * This file has structs that are shared between GOAL and Overlord. + * The memory layout of these structs should not be changed. + */ + +namespace jak3 { + +struct SoundStreamName { + char chars[48]; +}; + +// vblank message + +struct SoundIOPInfo { + u32 frame; // 0 + s32 strpos; // 4 + u32 str_id; // 8 + u32 freemem; // 12 + u8 chinfo[48]; // 16 + u32 freemem2; // 64 + u32 nocd; // 68 + u32 dirtycd; // 72 + u32 diskspeed[2]; // 76 + u32 lastspeed; // 84 + s32 dupseg; // 88 + s32 times[41]; // 92 + u32 times_seq; // 256 + u32 iop_ticks; // 260 + u32 pad0[2]; + u32 stream_position[4]; // 272 + u32 stream_status[4]; // 288 + SoundStreamName stream_name[4]; + u32 stream_id[4]; + u8 music_register[17]; + // s8 music_excite; + char ramdisk_name[16]; + u32 pad[11]; + char sound_bank0[16]; + char sound_bank1[16]; + char sound_bank2[16]; + char sound_bank3[16]; + char sound_bank4[16]; + char sound_bank5[16]; + char sound_bank6[16]; + char sound_bank7[16]; +}; +// static_assert(offsetof(SoundIOPInfo, stream_name) == 304); +// static_assert(offsetof(SoundIOPInfo, stream_id) == 496); +// static_assert(offsetof(SoundIOPInfo, music_register) == 512); +// static_assert(offsetof(SoundIOPInfo, ramdisk_name) == 529); +// static_assert(offsetof(SoundIOPInfo, sound_bank0) == 592); +static_assert(sizeof(SoundIOPInfo) == 0x2d0); + +// static_assert(sizeof(SoundIOPInfo) == 288); + +// Common + +enum RpcId { + Player = 0xfab0, // sound effects playback + Loader = 0xfab1, // sound effects loading. + LoadToEE = 0xfab2, // was ramdisk, now just a simple way to load a file to EE memory. + DGO = 0xfab3, // level/engine .DGO loading + STR = 0xfab4, // loading .str files of animations or other streamed data + PLAY = 0xfab5, // playing and queueing vag streams +}; + +// RAMDISK RPC (renamed to LoadToEE for jak 3, kinda) +struct RpcLoadToEEMsg { + u32 unk; + u32 addr; + u32 unk2; + u32 length; + char name[16]; +}; +static_assert(sizeof(RpcLoadToEEMsg) == 32); + +enum LoadToEEFno { + LOAD_FILE = 4, +}; + +// DGO RPC + +struct RPC_Dgo_Cmd { + uint16_t rsvd; + uint16_t status; + uint32_t buffer1; + uint32_t buffer2; + uint32_t buffer_heap_top; + char name[16]; + int32_t cgo_id; + uint8_t pad[28]; +}; +static_assert(sizeof(RPC_Dgo_Cmd) == 0x40); + +enum DgoFno { + LOAD = 0, + LOAD_NEXT = 1, + CANCEL = 2, +}; + +// STR RPC +struct RPC_Str_Cmd { + u16 rsvd; + u16 result; // 2 + u32 address; + s32 section; // 8 + u32 maxlen; + u32 dummy[4]; + char basename[48]; // 32 +}; + +// PLAYER/LOADER RPCS + +struct RPC_Play_Cmd { + u16 rsvd; + u16 result; + u32 address; + u32 section; + u32 maxlen; + u32 id[4]; + SoundStreamName names[4]; + u32 pad[8]; +}; + +struct SoundName { + char data[16]; +}; + +enum class SoundCommand : u16 { + // IOP_STORE = 0, + // IOP_FREE = 1, + LOAD_BANK = 2, + // LOAD_BANK_FROM_IOP = 3, + // LOAD_BANK_FROM_EE = 4, + LOAD_MUSIC = 5, + UNLOAD_BANK = 6, + PLAY = 7, + PAUSE_SOUND = 8, + STOP_SOUND = 9, + CONTINUE_SOUND = 10, + SET_PARAM = 11, + SET_MASTER_VOLUME = 12, + PAUSE_GROUP = 13, + STOP_GROUP = 14, + CONTINUE_GROUP = 15, + GET_IRX_VERSION = 16, + // SET_FALLOFF_CURVE = 17, + // SET_SOUND_FALLOFF = 18, + // RELOAD_INFO = 19, + SET_LANGUAGE = 20, + // SET_FLAVA = 21, + SET_MIDI_REG = 22, + SET_REVERB = 23, + SET_EAR_TRANS = 24, + SHUTDOWN = 25, + // LIST_SOUNDS = 26, + UNLOAD_MUSIC = 27, + SET_FPS = 28, + // BOOT_LOAD = 29, + // GAME_LOAD = 30, + // NUM_TESTS = 31, + // NUM_TESTRUNS = 32, + // NUM_SECTORS = 33, + // NUM_STREAMSECTORS = 34, + // NUM_STREMBANKS = 35, + // TRACK_PITCH = 36, + // LINVEL_NOM = 37, + CANCEL_DGO = 49, + SET_STEREO_MODE = 50, +}; + +struct SoundPlayParams { + u16 mask; + s16 pitch_mod; + s16 bend; + s16 fo_min; + s16 fo_max; + s8 fo_curve; + s8 priority; + s32 volume; + s32 trans[3]; + u8 group; + u8 reg[3]; +}; + +struct Rpc_Player_Base_Cmd { + u16 rsvd1 = 0; + SoundCommand command; +}; +static_assert(sizeof(Rpc_Player_Base_Cmd) == 4); + +struct Rpc_Player_Sound_Cmd : public Rpc_Player_Base_Cmd { + s32 sound_id = 0; +}; +static_assert(sizeof(Rpc_Player_Sound_Cmd) == 8); + +struct Rpc_Player_Group_Cmd : public Rpc_Player_Base_Cmd { + u32 group = 0; +}; +static_assert(sizeof(Rpc_Player_Group_Cmd) == 8); + +struct Rpc_Player_Play_Cmd : public Rpc_Player_Sound_Cmd { + s32 pad[2]; + SoundName name; + SoundPlayParams params; +}; +static_assert(sizeof(Rpc_Player_Play_Cmd) == 0x40); + +struct Rpc_Player_Set_Param_Cmd : public Rpc_Player_Sound_Cmd { + SoundPlayParams params; + s32 auto_time; + s32 auto_from; +}; +static_assert(sizeof(Rpc_Player_Set_Param_Cmd) == 0x30); + +struct Rpc_Player_Set_Master_Volume_Cmd : public Rpc_Player_Group_Cmd { + s32 volume; +}; +static_assert(sizeof(Rpc_Player_Set_Master_Volume_Cmd) == 12); + +struct Rpc_Player_Set_Ear_Trans_Cmd : public Rpc_Player_Base_Cmd { + s32 ear_trans1[3]; + s32 ear_trans0[3]; + s32 ear_trans[3]; + s32 cam_forward[3]; + s32 cam_left[3]; + s32 cam_scale; + s32 cam_inverted; +}; +static_assert(sizeof(Rpc_Player_Set_Ear_Trans_Cmd) == 0x48); + +struct Rpc_Player_Set_Fps_Cmd : public Rpc_Player_Base_Cmd { + u8 fps; + u8 pad; +}; +static_assert(sizeof(Rpc_Player_Set_Fps_Cmd) == 5 + 1); + +struct Rpc_Player_Cancel_Dgo_Cmd : public Rpc_Player_Group_Cmd { + u32 id; +}; +static_assert(sizeof(Rpc_Player_Cancel_Dgo_Cmd) == 12); + +struct Rpc_Loader_Bank_Cmd : public Rpc_Player_Base_Cmd { + u32 pad[3]; + SoundName bank_name; +}; +static_assert(sizeof(Rpc_Loader_Bank_Cmd) == 32); + +struct Rpc_Loader_Load_Bank_Cmd : public Rpc_Loader_Bank_Cmd { + u32 ee_addr; + u32 mode; + u32 priority; +}; +static_assert(sizeof(Rpc_Loader_Load_Bank_Cmd) == 0x2c); + +struct Rpc_Loader_Get_Irx_Version : public Rpc_Player_Base_Cmd { + u32 major; + u32 minor; + u32 ee_addr; +}; +static_assert(sizeof(Rpc_Loader_Get_Irx_Version) == 16); + +struct Rpc_Loader_Set_Language : public Rpc_Player_Base_Cmd { + u32 lang; +}; +static_assert(sizeof(Rpc_Loader_Set_Language) == 8); + +struct Rpc_Loader_Set_Stereo_Mode : public Rpc_Player_Base_Cmd { + s32 mode; +}; +static_assert(sizeof(Rpc_Loader_Set_Stereo_Mode) == 8); + +constexpr int kPlayerCommandStride = 0x50; +constexpr int kLoaderCommandStride = 0x50; + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/sbank.cpp b/game/overlord/jak3/sbank.cpp new file mode 100644 index 0000000000..dbbf3e9896 --- /dev/null +++ b/game/overlord/jak3/sbank.cpp @@ -0,0 +1,167 @@ +#include "sbank.h" + +#include "common/util/Assert.h" + +#include "game/overlord/jak3/overlord.h" + +namespace jak3 { + +constexpr int kNumBanks = 8; +SoundBankInfo* gBanks[kNumBanks]; + +SoundBankInfo gCommonBank; +SoundBankInfo gModeBank; +SoundBankInfo gLevel0Bank, gLevel0hBank; +SoundBankInfo gLevel1Bank, gLevel1hBank; +SoundBankInfo gLevel2Bank, gLevel2hBank; + +void jak3_overlord_init_globals_sbank() { + gBanks[0] = &gCommonBank; + gBanks[1] = &gModeBank; + gBanks[2] = &gLevel0Bank; + gBanks[3] = &gLevel0hBank; + gBanks[4] = &gLevel1Bank; + gBanks[5] = &gLevel1hBank; + gBanks[6] = &gLevel2Bank; + gBanks[7] = &gLevel2hBank; +} + +void InitBanks() { + for (int i = 0; i < kNumBanks; i++) { + auto* bank = gBanks[i]; + bank->in_use = 0; + bank->snd_handle = nullptr; + bank->loaded = false; + bank->idx = i; + bank->unk0 = 0; + } + + strncpyz(gBanks[0]->m_name2, "common", 0x10); + gBanks[0]->m_nSpuMemSize = 0xbbe40; + gBanks[0]->m_nSpuMemLoc = 0x1d1c0; + + strncpyz(gBanks[1]->m_name2, "mode", 0x10); + gBanks[1]->m_nSpuMemSize = 0x25400; + gBanks[1]->m_nSpuMemLoc = 0xe0000; + + strncpyz(gBanks[2]->m_name2, "level0", 0x10); + gBanks[2]->m_nSpuMemLoc = 0x105400; + gBanks[2]->m_nSpuMemSize = 0x51400; + + strncpyz(gBanks[3]->m_name2, "level0h", 0x10); + gBanks[3]->m_nSpuMemLoc = 0x12de00; + gBanks[3]->m_nSpuMemSize = 0x28a00; + + strncpyz(gBanks[4]->m_name2, "level1", 0x10); + gBanks[4]->m_nSpuMemSize = 0x51400; + gBanks[4]->m_nSpuMemLoc = 0x156800; + + strncpyz(gBanks[5]->m_name2, "level1h", 0x10); + gBanks[5]->m_nSpuMemSize = 0x28a00; + gBanks[5]->m_nSpuMemLoc = 0x17f200; + + strncpyz(gBanks[6]->m_name2, "level2", 0x10); + gBanks[6]->m_nSpuMemSize = 0x51400; + gBanks[6]->m_nSpuMemLoc = 0x1a7c00; + + strncpyz(gBanks[7]->m_name2, "level2h", 0x10); + gBanks[7]->m_nSpuMemSize = 0x28a00; + gBanks[7]->m_nSpuMemLoc = 0x1d0600; +} + +SoundBankInfo* AllocateBankName(const char* name, u32 mode) { + int iVar1; + int mem_sz; + SoundBankInfo** ppSVar2; + int iVar3; + int iVar4; + SoundBankInfo* pSVar5; + int iVar6; + int iVar6_d4 = 2; + SoundBankInfo* bank = nullptr; + + // handle common case + if (memcmp(name, "common", 7) == 0 || memcmp(name, "commonj", 8) == 0) { + if (!gBanks[0]->in_use) { + bank = gBanks[0]; + } + } else if (memcmp(name, "mode", 4) == 0) { + if (!gBanks[1]->in_use) { + bank = gBanks[1]; + } + } + + if (mode == 4) { + for (int bank_idx = 2; bank_idx < kNumBanks; bank_idx += 2) { + if (!gBanks[bank_idx]->in_use && !gBanks[bank_idx + 1]->in_use) { + bank = gBanks[bank_idx]; + bank->m_nSpuMemSize = 0x51400; + break; + } + } + } else if (mode > 3 && (mode - 6u < 3)) { // wtf + iVar1 = 2; + // iVar6 = 8; + iVar6_d4 = 2; + while (gBanks[iVar6_d4]->in_use == 0 || gBanks[iVar6_d4]->mode != mode) { + auto* sbi = gBanks[iVar6_d4 + 1]; + iVar6 = iVar6 + 8; + if (((sbi->in_use != 0) && (iVar4 = iVar1, sbi->mode == mode)) || + (iVar1 = iVar1 + 2, iVar4 = -1, 7 < iVar1)) + goto LAB_0000c2fc; + } + iVar4 = iVar1 + 1; + LAB_0000c2fc: + if (iVar4 < 0) { + iVar1 = 2; + ppSVar2 = gBanks; + LAB_0000c36c: + ppSVar2 = ppSVar2 + 2; + pSVar5 = *ppSVar2; + iVar1 = iVar1 + 2; + if ((pSVar5->in_use != 0) || (ppSVar2[1]->in_use != 0)) + goto LAB_0000c39c; + mem_sz = 0x28a00; + pSVar5->m_nSpuMemSize = mem_sz; + bank = pSVar5; + goto LAB_0000c3a4; + } + pSVar5 = gBanks[iVar4]; + if (pSVar5->in_use == 0) { + gBanks[iVar1]->m_nSpuMemSize = 0x28a00; + bank = pSVar5; + } + } +LAB_0000c3a4: + if (bank) { + bank->mode = mode; + bank->snd_handle = nullptr; + bank->unk0 = 0; + } + return bank; + +LAB_0000c39c: + if (7 < iVar1) + goto LAB_0000c3a4; + goto LAB_0000c36c; +} + +SoundBankInfo* LookupBank(const char* name) { + for (int i = kNumBanks; i-- > 0;) { + if (memcmp(name, gBanks[i]->m_name1, 16) == 0) { + return gBanks[i]; + } + } + return nullptr; +} + +int GetFalloffCurve(int x) { + if (x < 0) { + return 1; + } + if (x == 0 || 0x28 < x) { + x = 2; + } + return x; +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/sbank.h b/game/overlord/jak3/sbank.h new file mode 100644 index 0000000000..6882e78950 --- /dev/null +++ b/game/overlord/jak3/sbank.h @@ -0,0 +1,29 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/sound/sndshim.h" + +namespace jak3 { + +struct SoundBankInfo { + // int m_name1[4]; + char m_name1[16]; + char m_name2[16]; + int m_nSpuMemLoc = 0; + int m_nSpuMemSize = 0; + // s32 snd_handle = 0; + snd::BankHandle snd_handle = nullptr; + u8 in_use = 0; + u8 loaded = 0; + u8 mode = 0; + u8 idx = 0; + int unk0 = 0; +}; + +void jak3_overlord_init_globals_sbank(); +void InitBanks(); +SoundBankInfo* LookupBank(const char* name); +SoundBankInfo* AllocateBankName(const char* name, u32 mode); +extern SoundBankInfo* gBanks[8]; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/soundcommon.cpp b/game/overlord/jak3/soundcommon.cpp new file mode 100644 index 0000000000..b69feea1c0 --- /dev/null +++ b/game/overlord/jak3/soundcommon.cpp @@ -0,0 +1,19 @@ +#include "soundcommon.h" + +#include +#include +#include + +namespace jak3 { +void jak3_overlord_init_globals_soundcommon() {} + +// Only for use with 16 character sound names! +void strcpy_toupper(char* dest, const char* source) { + // clear the dest string + memset(dest, 0, 16); + std::string string(source); + std::transform(string.begin(), string.end(), string.begin(), ::toupper); + std::replace(string.begin(), string.end(), '-', '_'); + string.copy(dest, 16); +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/soundcommon.h b/game/overlord/jak3/soundcommon.h new file mode 100644 index 0000000000..760ddf46f1 --- /dev/null +++ b/game/overlord/jak3/soundcommon.h @@ -0,0 +1,7 @@ +#pragma once + +namespace jak3 { +void jak3_overlord_init_globals_soundcommon(); + +void strcpy_toupper(char* dest, const char* src); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/spustreams.cpp b/game/overlord/jak3/spustreams.cpp new file mode 100644 index 0000000000..df518ccf83 --- /dev/null +++ b/game/overlord/jak3/spustreams.cpp @@ -0,0 +1,1066 @@ +#include "spustreams.h" + +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/basefile.h" +#include "game/overlord/jak3/dma.h" +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/iso_queue.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" +#include "game/sound/sdshim.h" + +namespace jak3 { +using namespace iop; +void jak3_overlord_init_globals_spustreams() {} + +u32 bswap_read32(u32 param_1) { + return param_1 >> 0x18 | ((int)param_1 >> 8 & 0xff00U) | (param_1 & 0xff00) << 8 | + param_1 << 0x18; +} + +void UpdateIsoBuffer(ISO_VAGCommand* cmd, int length) { + ASSERT(cmd); + ASSERT(cmd->m_pBaseFile); + + auto* file = cmd->m_pBaseFile; + int transfer_size = cmd->xfer_size; + + // advance progress in file + file->m_Buffer.m_pCurrentData += length; + + // this part is weird - we've advanced the current data pointer, and now we're updating the + // size field in the buffer. But, we're potentially adjusting the size in between these. + // This means that our m_pCurrentData + m_DataLength may go off the end of the buffer?? + int tranferred_already = -length; + if (transfer_size < length) { + tranferred_already = -transfer_size; + length = transfer_size; + // ASSERT_NOT_REACHED(); // for now... + } + cmd->xfer_size = transfer_size + tranferred_already; + file->m_Buffer.AddData(-length); + cmd->num_isobuffered_chunks = cmd->num_isobuffered_chunks + 1; +} + +/*! + * Callback for handling VAG data. This transfers data from the Page buffers to SPU memory. + */ +EIsoStatus ProcessVAGData(ISO_Hdr* _msg) { + int spu_addr; + u32 uVar1; + int iVar2; + u32 val; + uint8_t* iop_mem; + int* piVar4; + int iVar5; + int iVar6; + ISO_VAGCommand* sibling; + u32 val2; + CBuffer* buffer; + CBaseFile* file; + int length; + // int iVar9; + // undefined4 local_28[2]; + bool got_chunks; + + ASSERT(_msg); + auto* msg = (ISO_VAGCommand*)_msg; + ASSERT(msg->m_pBaseFile); + file = msg->m_pBaseFile; + ASSERT(file->m_ProcessDataSemaphore != -1); + + // lock the semaphore + // lg::error("--------------- proces vag!!! {}\n", msg->name); + WaitSema(file->m_ProcessDataSemaphore); + buffer = &file->m_Buffer; + + if (msg->unk_gvsp_flag) { + lg::error("unk gvsp flag set in process data - no more needed??"); + } + + // reject if errored/stopped - in this case no more data is needed. + if (msg->unk_gvsp_flag || msg->flags.stop || msg->flags.file_disappeared || + msg->flags.stereo_secondary) { + goto exit; + } + + // reject if error. + if (msg->error != 0) { + file->m_Buffer.m_nDataLength = 0; + goto exit; + } + + // reject if dma in progress - wait for that to finish first + if (msg->safe_to_modify_dma == 0) { + goto exit; + } + + // determine the length we need to do an upload. + sibling = msg->stereo_sibling; + got_chunks = msg->num_isobuffered_chunks != 0; + length = 0x2000; + if (sibling) { + length = 0x4000; + } + + // reject if not enough data. + if (got_chunks) { + int desired_length = length; + if (msg->xfer_size < length) { + desired_length = msg->xfer_size; + } + if ((int)file->m_Buffer.m_nDataLength < desired_length) { + lg::warn("ProcessVAG data is starved."); + goto exit; + } + } + + // reject if no buffer + if (!buffer->m_pPageList || !buffer->m_pPageList->m_pCurrentActivePage) + goto exit; + + // update buffer so it handles page crossing + file->CheckPageBoundary(); + CPage* page; + + // add ref count to the page. + if (!buffer->m_pPageList || ((page = buffer->m_pPageList->m_pCurrentActivePage, + !page || (spu_addr = page->AddRef(), spu_addr < 1)))) + goto exit; + + // make sure the page has the right state. I think this should never really happen, but I guess + // they are paranoid. + if (page->input_state != CPage::State::READ_DONE) { + int status = page->ReleaseRef(); + ASSERT(status >= 0); + goto exit; + } + + if (got_chunks) { // if we've already started streaming, all way have to do is update. + if (msg->num_isobuffered_chunks & 1u) { + int vag_transfer_size = msg->xfer_size; + if ((length < vag_transfer_size) || ((u32)msg->unk_spu_mem_offset < 0x4000)) { + VAG_MarkLoopEnd(file->m_Buffer.m_pCurrentData, 0x2000); + VAG_MarkLoopStart(file->m_Buffer.m_pCurrentData); + if (sibling) { + VAG_MarkLoopEnd(file->m_Buffer.m_pCurrentData, 0x4000); + VAG_MarkLoopStart(file->m_Buffer.m_pCurrentData + 0x2000); + } + } else { + spu_addr = 0x2010; + if ((0x1f < (int)vag_transfer_size) && + (spu_addr = vag_transfer_size + 0x1ff0, sibling != (ISO_VAGCommand*)0x0)) { + spu_addr = ((int)(vag_transfer_size + (vag_transfer_size >> 0x1f)) >> 1) + 0x1ff0; + } + msg->unk_spu_mem_offset = spu_addr; + if (sibling != (ISO_VAGCommand*)0x0) { + sibling->unk_spu_mem_offset = msg->unk_spu_mem_offset; + } + } + // CpuSuspendIntr(local_28); + iop_mem = file->m_Buffer.m_pCurrentData; + spu_addr = msg->stream_sram + 0x2000; + } else { + int vag_transfer_size = msg->xfer_size; + if ((length < (int)vag_transfer_size) || ((u32)msg->unk_spu_mem_offset < 0x4000)) { + VAG_MarkLoopEnd(file->m_Buffer.m_pCurrentData, 0x2000); + VAG_MarkLoopStart(file->m_Buffer.m_pCurrentData); + if (sibling) { + VAG_MarkLoopEnd(file->m_Buffer.m_pCurrentData, 0x4000); + VAG_MarkLoopStart(file->m_Buffer.m_pCurrentData + 0x2000); + } + } else { + spu_addr = 0x10; + if ((0x1f < (int)vag_transfer_size) && + (spu_addr = vag_transfer_size - 0x10, sibling != (ISO_VAGCommand*)0x0)) { + spu_addr = ((int)(vag_transfer_size + (vag_transfer_size >> 0x1f)) >> 1) + -0x10; + } + msg->unk_spu_mem_offset = spu_addr; + if (sibling != (ISO_VAGCommand*)0x0) { + sibling->unk_spu_mem_offset = msg->unk_spu_mem_offset; + } + } + // CpuSuspendIntr(local_28); + iop_mem = file->m_Buffer.m_pCurrentData; + spu_addr = msg->stream_sram; + } + spu_addr = DMA_SendToSPUAndSync(iop_mem, 0x2000, spu_addr, msg, page); + if (spu_addr != 0) { + set_active_b(msg, 0); + goto LAB_000106a0; + } + LAB_0001067c: + // + { + int status = page->ReleaseRef(); + ASSERT(status >= 0); + } + + } else { + // we're starting the vag file! check the header: + piVar4 = (int*)file->m_Buffer.m_pCurrentData; + if ((*piVar4 != 0x70474156) && (*piVar4 != 0x56414770)) { + lg::error("Invalid VAG data is 0x{:x}", *piVar4); + ASSERT_NOT_REACHED(); // invalid data + msg->error = 1; + file->m_Buffer.m_nDataLength = 0; + int status = page->ReleaseRef(); + ASSERT(status >= 0); + goto exit; + } + + // read the rate/xfer size. + val = piVar4[4]; + msg->vag_file_rate = val; + val2 = piVar4[3]; + msg->unk_gvsp_cntr = 0; + msg->xfer_size = val2; + if (*piVar4 == 0x70474156) { + uVar1 = bswap_read32(val); + msg->vag_file_rate = uVar1; + uVar1 = bswap_read32(val2); + msg->xfer_size = uVar1; + } + + // lg::die("xfer size is {}", msg->xfer_size); + + // set sibling properties + if (sibling) { + spu_addr = msg->xfer_size; + sibling->vag_file_rate = msg->vag_file_rate; + sibling->xfer_size = spu_addr; + sibling->unk_gvsp_cntr = 0; + } + + spu_addr = msg->xfer_size; + if (sibling != (ISO_VAGCommand*)0x0) { + spu_addr = spu_addr >> 1; + } + uVar1 = spu_addr + 0x30; + if ((uVar1 & 0x1fff) != 0) { + uVar1 = (uVar1 - (uVar1 & 0x1fff)) + 0x2000; + } + if (sibling != (ISO_VAGCommand*)0x0) { + uVar1 = uVar1 * 2; + } + uVar1 = (uVar1 + 0x7fff) >> 0xf; + if (uVar1 == 0) { + uVar1 = 1; + } + file->m_LengthPages = uVar1; + iVar6 = msg->vag_file_rate; + iVar5 = msg->xfer_size; + spu_addr = iVar5 + 0x30; + msg->unk_spu_mem_offset = 0x4000; + msg->xfer_size = spu_addr; + uVar1 = (u32)(iVar6 << 0xc) / 48000; + msg->pitch1_file = uVar1; + msg->pitch1 = uVar1; + if (spu_addr < 0x2001) { + iVar2 = 0x10; + if (0x1f < spu_addr) { + iVar2 = iVar5 + 0x20; + } + msg->unk_spu_mem_offset = iVar2; + if (sibling != (ISO_VAGCommand*)0x0) { + sibling->xfer_size = msg->xfer_size; + sibling->unk_spu_mem_offset = msg->unk_spu_mem_offset; + sibling->vag_file_rate = iVar6; + sibling->pitch1 = msg->pitch1; + sibling->pitch1_file = msg->pitch1_file; + sibling->xfer_size = msg->xfer_size; + sibling->unk_spu_mem_offset = msg->unk_spu_mem_offset; + sibling->unk_gvsp_cntr = 0; + } + } + // CpuSuspendIntr(local_28); + spu_addr = + DMA_SendToSPUAndSync(file->m_Buffer.m_pCurrentData, 0x2000, msg->stream_sram, msg, page); + if (spu_addr == 0) + goto LAB_0001067c; + msg->position_for_ee = 0; + msg->unk_gvsp_len = 0; + if (sibling) { // added. + sibling->position_for_ee = 0; + } + LAB_000106a0: + UpdateIsoBuffer(msg, length); + } + // CpuResumeIntr(local_28[0]); +exit: + SignalSema(file->m_ProcessDataSemaphore); + return EIsoStatus::OK_2; +} + +u32 GetSpuRamAddress(ISO_VAGCommand* cmd) { + return sceSdGetAddr(SD_VA_NAX | cmd->voice); + + u32 voice = cmd->voice; + u32 current_addr = cmd->stream_sram; + BlockUntilVoiceSafe(voice, 0xf00); + u32 sce_addr = sceSdGetAddr(voice | 0x2240); + // lg::info("sce addr {}", sce_addr); + if ((sce_addr < current_addr) || (current_addr + 0x4040 <= sce_addr)) { + ASSERT_NOT_REACHED(); + sce_addr = current_addr + 0x4040; + } + return sce_addr; +} + +u32 GetVAGStreamPos(ISO_VAGCommand* cmd) { + u32 uVar1; + int iVar2; + u32 uVar3; + u32 uVar4; + ISO_VAGCommand* sibling; + u32 uVar5; + // undefined4 local_20[2]; + + sibling = cmd->stereo_sibling; + if (cmd->id == 0) { + cmd->position_for_ee = 0; + if (sibling == (ISO_VAGCommand*)0x0) { + return 0; + } + sibling->position_for_ee = 0; + return 0; + } + if (cmd->flags.file_disappeared != 0) { + cmd->position_for_ee = cmd->clockd; + if (sibling == (ISO_VAGCommand*)0x0) { + return 0; + } + sibling->position_for_ee = sibling->clockd; + return 0; + } + if (((cmd->flags.running == 0) || (cmd->flags.saw_chunks1 == 0)) || (cmd->flags.paused != 0)) { + cmd->position_for_ee = cmd->clocka; + if (sibling == (ISO_VAGCommand*)0x0) { + return 0; + } + sibling->position_for_ee = sibling->clocka; + return 0; + } + if (cmd->flags.stereo_secondary != 0) { + cmd->position_for_ee = cmd->clocka; + return 0; + } + // CpuSuspendIntr(local_20); + uVar1 = GetSpuRamAddress(cmd); + uVar5 = 0; + // lg::info("offset is {}, {} - {}\n", uVar1 - cmd->stream_sram, uVar1, cmd->stream_sram); + uVar1 = uVar1 - cmd->stream_sram; + if (sibling != (ISO_VAGCommand*)0x0) { + uVar5 = GetSpuRamAddress(sibling); + uVar5 = uVar5 - sibling->stream_sram; + } + // CpuResumeIntr(local_20[0]); + if (sibling != (ISO_VAGCommand*)0x0) { + if (((uVar1 < 0x4000) && (uVar5 < 0x4000)) && + ((cmd->flags.bit20 == 0 && (sibling->flags.bit20 == 0)))) { + iVar2 = (int)((uVar1 - uVar5) * 0x40000) >> 0x12; + if (iVar2 < 0) { + iVar2 = -iVar2; + } + if (4 < iVar2) { + PauseVAG(cmd); + uVar1 = cmd->current_spu_address - cmd->stream_sram; + uVar5 = sibling->current_spu_address - sibling->stream_sram; + UnPauseVAG(cmd); + } + } + if (sibling == (ISO_VAGCommand*)0x0) + goto LAB_00011010; + // CpuSuspendIntr(local_20); + // lg::info("inner values are: {} {}", uVar1, uVar5); + if ((0x4000 < uVar1) && (cmd->flags.bit20 == 0)) { + cmd->flags.bit20 = 1; + cmd->flags.bit21 = 0; + cmd->flags.bit22 = 0; + sibling->flags.bit20 = 1; + sibling->flags.bit21 = 0; + sibling->flags.bit22 = 0; + } + if (uVar5 < 0x4001) { + if (uVar1 < 0x2000) { + if (cmd->flags.bit21 == 0) { + cmd->unk_gvsp_cntr = cmd->unk_gvsp_cntr + 1; + cmd->flags.bit21 = 1; + cmd->flags.bit22 = 0; + LAB_000109b8: + cmd->flags.bit20 = 0; + } + } else { + if (cmd->flags.bit22 == 0) { + cmd->unk_gvsp_cntr = cmd->unk_gvsp_cntr + 1; + cmd->flags.bit22 = 1; + cmd->flags.bit21 = 0; + goto LAB_000109b8; + } + } + if (uVar5 < 0x2000) { + if (sibling->flags.bit21 == 0) { + sibling->unk_gvsp_cntr = sibling->unk_gvsp_cntr + 1; + sibling->flags.bit21 = 1; + sibling->flags.bit22 = 0; + LAB_00010a1c: + sibling->flags.bit20 = 0; + } + } else { + if (sibling->flags.bit22 == 0) { + sibling->unk_gvsp_cntr = sibling->unk_gvsp_cntr + 1; + sibling->flags.bit22 = 1; + sibling->flags.bit21 = 0; + goto LAB_00010a1c; + } + } + } else { + if (sibling->flags.bit20 == 0) { + cmd->flags.bit20 = 1; + cmd->flags.bit21 = 0; + cmd->flags.bit22 = 0; + sibling->flags.bit20 = 1; + sibling->flags.bit21 = 0; + sibling->flags.bit22 = 0; + } + } + // lg::info("bits are {} {} {}\n", cmd->flags.bit20, cmd->flags.bit21, cmd->flags.bit22); + // CpuResumeIntr(local_20[0]); + if (cmd->unk_gvsp_flag != 0) + goto switchD_00010a60_caseD_1; + + // lg::info("switching {}", cmd->unk_gvsp_state2); + switch (cmd->unk_gvsp_state2) { + case 0: + if ((((cmd->flags.dma_complete_even_chunk_count == 0) || (cmd->flags.bit21 == 0)) || + (sibling->flags.dma_complete_even_chunk_count == 0)) || + (sibling->flags.bit21 == 0)) + goto switchD_00010a60_caseD_1; + cmd->flags.dma_complete_even_chunk_count = 0; + cmd->unk_gvsp_state2 = 2; + sibling->flags.dma_complete_even_chunk_count = 0; + sibling->unk_gvsp_state2 = 2; + case 2: + if ((cmd->flags.dma_complete_odd_chunk_count == 0) || + (sibling->flags.dma_complete_odd_chunk_count == 0)) { + if ((cmd->flags.bit20 != 0) || (sibling->flags.bit20 != 0)) { + uVar1 = 0x2000; + uVar5 = 0x2000; + cmd->flags.bit17 = 1; + cmd->flags.bit16 = 0; + cmd->unk_gvsp_state2 = 4; + sibling->flags.bit17 = 1; + sibling->unk_gvsp_state2 = 4; + LAB_00010efc: + sibling->flags.bit16 = 0; + } + goto switchD_00010a60_caseD_1; + } + if ((cmd->flags.bit20 == 0) && (sibling->flags.bit20 == 0)) { + BlockUntilVoiceSafe(cmd->voice, 0xf00); + BlockUntilVoiceSafe(sibling->voice, 0xf00); + // CpuSuspendIntr(local_20); + // lg::info("sax case 1 (stream)"); + sceSdSetAddr(cmd->voice | 0x2140, cmd->stream_sram + 0x2000); + sceSdSetAddr(sibling->voice | 0x2140, sibling->stream_sram + 0x2000); + iVar2 = 3; + cmd->flags.bit15 = 1; + cmd->flags.bit14 = 0; + cmd->flags.bit13 = 0; + sibling->flags.bit15 = 1; + sibling->flags.bit14 = 0; + LAB_00010d78: + sibling->flags.bit13 = 0; + LAB_00010e78: + cmd->unk_gvsp_state2 = iVar2; + sibling->unk_gvsp_state2 = iVar2; + // CpuResumeIntr(local_20[0]); + goto switchD_00010a60_caseD_1; + } + LAB_00010bc4: + RestartVag(cmd, 1); + uVar1 = 0x2000; + iVar2 = 9; + uVar5 = 0x2000; + break; + default: + goto switchD_00010a60_caseD_1; + case 3: + // lg::info("case 3 bits: ({} {}) ({} {})\n", cmd->flags.bit20, cmd->flags.bit22, + // sibling->flags.bit20, sibling->flags.bit22); + if ((cmd->flags.bit20 != 0) || (sibling->flags.bit20 != 0)) + goto LAB_00010bc4; + if ((cmd->flags.bit22 == 0) || (sibling->flags.bit22 == 0)) + goto switchD_00010a60_caseD_1; + BlockUntilVoiceSafe(cmd->voice, 0xf00); + BlockUntilVoiceSafe(sibling->voice, 0xf00); + // CpuSuspendIntr(local_20); + // lg::info("sax case 2 (trap)"); + sceSdSetAddr(cmd->voice | 0x2140, cmd->trap_sram); + sceSdSetAddr(sibling->voice | 0x2140, sibling->trap_sram); + iVar2 = 5; + cmd->flags.bit13 = 1; + cmd->flags.bit14 = 0; + cmd->flags.bit15 = 0; + sibling->flags.bit13 = 1; + sibling->flags.bit14 = 0; + sibling->flags.bit15 = 0; + cmd->flags.dma_complete_odd_chunk_count = 0; + sibling->flags.dma_complete_odd_chunk_count = 0; + goto LAB_00010e78; + case 4: + uVar1 = cmd->unk_gvsp_len; + uVar5 = sibling->unk_gvsp_len; + if ((cmd->flags.dma_complete_odd_chunk_count == 0) || + (sibling->flags.dma_complete_odd_chunk_count == 0)) + goto switchD_00010a60_caseD_1; + RestartVag(cmd, 1); + iVar2 = 9; + break; + case 5: + if ((cmd->flags.dma_complete_even_chunk_count == 0) || + (sibling->flags.dma_complete_even_chunk_count == 0)) { + if (cmd->flags.bit20 == 0) + goto switchD_00010a60_caseD_1; + cmd->flags.bit16 = 1; + cmd->flags.bit17 = 0; + cmd->unk_gvsp_state2 = 7; + uVar1 = 0x4000; + uVar5 = 0x4000; + sibling->flags.bit16 = 1; + sibling->unk_gvsp_state2 = 7; + goto LAB_00010db0; + } + if ((cmd->flags.bit20 != 0) || (sibling->flags.bit20 != 0)) + goto LAB_00010dd8; + BlockUntilVoiceSafe(cmd->voice, 0xf00); + BlockUntilVoiceSafe(sibling->voice, 0xf00); + // CpuSuspendIntr(local_20); + // lg::info("sax case 3 (stream)"); + sceSdSetAddr(cmd->voice | 0x2140, cmd->stream_sram); + sceSdSetAddr(sibling->voice | 0x2140, sibling->stream_sram); + iVar2 = 6; + cmd->flags.bit14 = 1; + cmd->flags.bit15 = 0; + cmd->flags.bit13 = 0; + sibling->flags.bit14 = 1; + sibling->flags.bit15 = 0; + goto LAB_00010d78; + case 6: + if ((cmd->flags.bit20 == 0) && (sibling->flags.bit20 == 0)) { + if ((cmd->flags.bit21 == 0) || (sibling->flags.bit21 == 0)) + goto switchD_00010a60_caseD_1; + BlockUntilVoiceSafe(cmd->voice, 0xf00); + BlockUntilVoiceSafe(sibling->voice, 0xf00); + // CpuSuspendIntr(local_20); + // lg::info("sax case 4 (trap)"); + sceSdSetAddr(cmd->voice | 0x2140, cmd->trap_sram); + sceSdSetAddr(sibling->voice | 0x2140, sibling->trap_sram); + cmd->flags.bit13 = 1; + cmd->flags.bit14 = 0; + cmd->flags.bit15 = 0; + iVar2 = 2; + sibling->flags.bit13 = 1; + sibling->flags.bit14 = 0; + sibling->flags.bit15 = 0; + cmd->flags.dma_complete_even_chunk_count = 0; + sibling->flags.dma_complete_even_chunk_count = 0; + goto LAB_00010e78; + } + LAB_00010dd8: + RestartVag(cmd, 0); + uVar1 = 0x4000; + iVar2 = 8; + uVar5 = 0x4000; + break; + case 7: + uVar1 = cmd->unk_gvsp_len; + uVar5 = uVar1; + if ((cmd->flags.dma_complete_even_chunk_count == 0) || + (uVar5 = uVar1, sibling->flags.dma_complete_even_chunk_count == 0)) + goto switchD_00010a60_caseD_1; + RestartVag(cmd, 0); + iVar2 = 8; + uVar5 = uVar1; + break; + case 8: + if ((cmd->flags.bit21 == 0) || (sibling->flags.bit21 == 0)) { + uVar1 = cmd->unk_gvsp_len; + uVar5 = uVar1; + goto switchD_00010a60_caseD_1; + } + cmd->unk_gvsp_state2 = 6; + cmd->flags.bit16 = 0; + sibling->unk_gvsp_state2 = 6; + goto LAB_00010efc; + case 9: + if ((cmd->flags.bit22 == 0) || (sibling->flags.bit22 == 0)) { + uVar1 = cmd->unk_gvsp_len; + uVar5 = sibling->unk_gvsp_len; + goto switchD_00010a60_caseD_1; + } + cmd->unk_gvsp_state2 = 3; + cmd->flags.bit17 = 0; + sibling->unk_gvsp_state2 = 3; + LAB_00010db0: + sibling->flags.bit17 = 0; + goto switchD_00010a60_caseD_1; + } + cmd->unk_gvsp_state2 = iVar2; + sibling->unk_gvsp_state2 = iVar2; + switchD_00010a60_caseD_1: + if (cmd->unk_gvsp_cntr == 0) { + cmd->clockc = uVar1; + sibling->clockc = uVar1; + } else { + iVar2 = sibling->unk_gvsp_cntr; + cmd->clockc = uVar1 + cmd->unk_gvsp_cntr * 0x2000 + -0x2000; + sibling->clockc = uVar5 + iVar2 * 0x2000 + -0x2000; + if (0x2000 < uVar1) { + cmd->clockc = cmd->clockc + -0x2000; + } + if (0x2000 < uVar5) { + sibling->clockc = sibling->clockc + -0x2000; + } + } + uVar4 = cmd->vag_file_rate; + if (uVar4 == 0) { + uVar3 = 0; + } else { + uVar3 = (u32)(cmd->clockc * 0x1c0) / uVar4; + if (uVar4 == 0) { + ASSERT_NOT_REACHED(); + } + } + iVar2 = sibling->clockc; + uVar4 = sibling->vag_file_rate; + cmd->unk_gvsp_len = uVar1; + cmd->position_for_ee = uVar3 << 2; + cmd->clocka = uVar3 << 2; + if (uVar4 == 0) { + uVar1 = 0; + } else { + uVar1 = (u32)(iVar2 * 0x1c0) / uVar4; + if (uVar4 == 0) { + ASSERT_NOT_REACHED(); + } + } + sibling->unk_gvsp_len = uVar5; + sibling->position_for_ee = uVar1 << 2; + sibling->clocka = uVar1 << 2; + return 0; + } +LAB_00011010: + if (uVar1 < 0x4001) { + if (uVar1 < 0x2000) { + if (cmd->flags.bit21 == 0) { + cmd->unk_gvsp_cntr = cmd->unk_gvsp_cntr + 1; + cmd->flags.bit21 = 1; + cmd->flags.bit22 = 0; + LAB_00011098: + cmd->flags.bit20 = 0; + } + } else { + if (cmd->flags.bit22 == 0) { + cmd->unk_gvsp_cntr = cmd->unk_gvsp_cntr + 1; + cmd->flags.bit22 = 1; + cmd->flags.bit21 = 0; + goto LAB_00011098; + } + } + } else { + if (cmd->flags.bit20 == 0) { + cmd->flags.bit20 = 1; + cmd->flags.bit21 = 0; + cmd->flags.bit22 = 0; + } + } + if (cmd->unk_gvsp_flag != 0) + goto switchD_000110d0_caseD_1; + switch (cmd->unk_gvsp_state2) { + case 0: + if ((cmd->flags.dma_complete_even_chunk_count == 0) || (cmd->flags.bit21 == 0)) + goto switchD_000110d0_caseD_1; + cmd->unk_gvsp_state2 = 2; + cmd->flags.dma_complete_even_chunk_count = 0; + switchD_000110d0_caseD_2: + if (cmd->flags.dma_complete_odd_chunk_count == 0) { + if (cmd->flags.bit20 != 0) { + uVar1 = 0x2000; + iVar2 = 4; + cmd->flags.bit17 = 1; + LAB_00011368: + cmd->unk_gvsp_state2 = iVar2; + cmd->flags.bit16 = 0; + } + } else { + if (cmd->flags.bit20 == 0) { + BlockUntilVoiceSafe(cmd->voice, 0xf00); + // CpuSuspendIntr(local_20); + sceSdSetAddr(cmd->voice | 0x2140, cmd->stream_sram + 0x2000); + cmd->flags.bit15 = 1; + cmd->unk_gvsp_state2 = 3; + cmd->flags.bit14 = 0; + LAB_00011284: + cmd->flags.bit13 = 0; + LAB_00011324:; + // CpuResumeIntr(local_20[0]); + } else { + LAB_00011188: + RestartVag(cmd, 1); + uVar1 = 0x2000; + LAB_0001120c: + iVar2 = 9; + LAB_00011350: + cmd->unk_gvsp_state2 = iVar2; + } + } + switchD_000110d0_caseD_1: + if (cmd->unk_gvsp_cntr == 0) { + cmd->clockc = uVar1; + } else { + iVar2 = uVar1 + cmd->unk_gvsp_cntr * 0x2000; + cmd->clockc = iVar2 + -0x2000; + if (0x2000 < uVar1) { + cmd->clockc = iVar2 + -0x4000; + } + } + uVar5 = cmd->vag_file_rate; + if (uVar5 == 0) { + uVar4 = 0; + } else { + uVar4 = (u32)(cmd->clockc * 0x1c0) / uVar5; + if (uVar5 == 0) { + // trap(0x1c00); + ASSERT_NOT_REACHED(); + } + } + cmd->unk_gvsp_len = uVar1; + cmd->position_for_ee = uVar4 << 2; + cmd->clocka = uVar4 << 2; + return 0; + default: + goto switchD_000110d0_caseD_1; + case 2: + goto switchD_000110d0_caseD_2; + case 3: + if (cmd->flags.bit20 != 0) + goto LAB_00011188; + if (cmd->flags.bit22 == 0) + goto switchD_000110d0_caseD_1; + BlockUntilVoiceSafe(cmd->voice, 0xf00); + // CpuSuspendIntr(local_20); + sceSdSetAddr(cmd->voice | 0x2140, cmd->trap_sram); + cmd->flags.bit13 = 1; + cmd->unk_gvsp_state2 = 5; + cmd->flags.bit14 = 0; + cmd->flags.bit15 = 0; + cmd->flags.dma_complete_odd_chunk_count = 0; + goto LAB_00011324; + case 4: + uVar1 = cmd->unk_gvsp_len; + if (cmd->flags.dma_complete_odd_chunk_count == 0) + goto switchD_000110d0_caseD_1; + RestartVag(cmd, 1); + goto LAB_0001120c; + case 5: + if (cmd->flags.dma_complete_even_chunk_count == 0) { + if (cmd->flags.bit20 == 0) + goto switchD_000110d0_caseD_1; + uVar1 = 0x4000; + cmd->flags.bit16 = 1; + iVar2 = 7; + goto LAB_000112a0; + } + if (cmd->flags.bit20 != 0) + goto LAB_000112bc; + BlockUntilVoiceSafe(cmd->voice, 0xf00); + // CpuSuspendIntr(local_20); + sceSdSetAddr(cmd->voice | 0x2140, cmd->stream_sram); + cmd->flags.bit14 = 1; + cmd->unk_gvsp_state2 = 6; + cmd->flags.bit15 = 0; + goto LAB_00011284; + case 6: + if (cmd->flags.bit20 == 0) { + if (cmd->flags.bit21 == 0) + goto switchD_000110d0_caseD_1; + BlockUntilVoiceSafe(cmd->voice, 0xf00); + // CpuSuspendIntr(local_20); + sceSdSetAddr(cmd->voice | 0x2140, cmd->trap_sram); + cmd->flags.bit13 = 1; + cmd->unk_gvsp_state2 = 2; + cmd->flags.bit14 = 0; + cmd->flags.bit15 = 0; + cmd->flags.dma_complete_even_chunk_count = 0; + goto LAB_00011324; + } + LAB_000112bc: + RestartVag(cmd, 0); + uVar1 = 0x4000; + goto LAB_0001134c; + case 7: + uVar1 = cmd->unk_gvsp_len; + if (cmd->flags.dma_complete_even_chunk_count == 0) + goto switchD_000110d0_caseD_1; + RestartVag(cmd, 0); + LAB_0001134c: + iVar2 = 8; + goto LAB_00011350; + case 8: + iVar2 = 6; + if (cmd->flags.bit21 == 0) { + LAB_00011374: + uVar1 = cmd->unk_gvsp_len; + goto switchD_000110d0_caseD_1; + } + goto LAB_00011368; + case 9: + iVar2 = 3; + if (cmd->flags.bit22 == 0) + goto LAB_00011374; + LAB_000112a0: + cmd->unk_gvsp_state2 = iVar2; + cmd->flags.bit17 = 0; + goto switchD_000110d0_caseD_1; + } +} + +u32 CheckVAGStreamProgress(ISO_VAGCommand* cmd) { + u32 uVar1; + u32 last_offset_in_stream_sram; + ISO_VAGCommand* pIVar3; + + if (cmd->flags.file_disappeared == 0) { + if (cmd->flags.stereo_secondary != 0) { + return 1; + } + if (cmd->error != 0) { + return 0; + } + if ((cmd->flags.bit20 != 0) && (cmd->unk_gvsp_flag != 0)) { + return 0; + } + if (cmd->flags.saw_chunks1 == 0) { + return 1; + } + if (cmd->flags.paused != 0) { + return 1; + } + uVar1 = cmd->unk_spu_mem_offset; + pIVar3 = cmd->stereo_sibling; + last_offset_in_stream_sram = cmd->unk_gvsp_len; + if ((uVar1 < 0x4000) && (((last_offset_in_stream_sram < 0x2001 && (uVar1 < 0x2001)) || + ((0x1fff < last_offset_in_stream_sram && (0x1fff < uVar1)))))) { + if (uVar1 <= (last_offset_in_stream_sram & 0xfffffff0)) { + return 0; + } + // CpuSuspendIntr(local_18); + if ((cmd->unk_gvsp_flag == 0) && + (last_offset_in_stream_sram < (u32)cmd->unk_spu_mem_offset)) { + BlockUntilVoiceSafe(cmd->voice, 0xf00); + sceSdSetAddr(cmd->voice | 0x2140, cmd->stream_sram + cmd->unk_spu_mem_offset); + cmd->unk_gvsp_flag = 1; + if (pIVar3 != (ISO_VAGCommand*)0x0) { + BlockUntilVoiceSafe(pIVar3->voice, 0xf00); + sceSdSetAddr(pIVar3->voice | 0x2140, pIVar3->stream_sram + cmd->unk_spu_mem_offset); + pIVar3->unk_gvsp_flag = 1; + } + } + set_active_a(cmd, 0); + set_active_b(cmd, 0); + // CpuResumeIntr(local_18[0]); + return 1; + } + if (cmd->flags.saw_chunks1 == 0) { + return 1; + } + if (cmd->active_b != 0) { + return 1; + } + if (cmd->safe_to_modify_dma == 0) { + return 1; + } + if (cmd->unk_gvsp_flag != 0) { + return 1; + } + if (last_offset_in_stream_sram < 0x2000) { + uVar1 = cmd->num_isobuffered_chunks; + } else { + uVar1 = cmd->num_isobuffered_chunks ^ 1; + } + if ((uVar1 & 1) == 0) { + return 1; + } + + set_active_b(cmd, 1); + } + return 1; +} + +void StopVagStream(ISO_VAGCommand* cmd) { + ISO_VAGCommand* sibling; + VagStreamData vsd; + // undefined auStack72 [36]; + // int local_24; + // int local_20; + // undefined4 local_18 [2]; + + // CpuSuspendIntr(local_18); + sibling = cmd->stereo_sibling; + cmd->flags.saw_chunks1 = 0; + if (sibling != (ISO_VAGCommand*)0x0) { + sibling->flags.saw_chunks1 = 0; + } + if ((cmd->music_flag == 0) && (cmd->maybe_sound_handler != 0)) { + PauseVAG(cmd); + strncpy(vsd.name, cmd->name, 0x30); + vsd.id = cmd->id; + // RemoveVagStreamFromList(&vsd, &g_PluginStreamsList); + RemoveVagStreamFromList(&vsd, &g_EEPlayList); + // local_20 = cmd->plugin_id; + // local_24 = cmd->id; + // RemoveLfoStreamFromList(auStack72,&g_LfoStreamsList); + } else { + PauseVAG(cmd); + cmd->flags.stop = 1; + if (sibling != (ISO_VAGCommand*)0x0) { + PauseVAG(sibling); + cmd->flags.stop = 1; + } + } + // CpuResumeIntr(local_18[0]); +} + +void ProcessStreamData() { + EIsoStatus iVar2; + CBuffer* pCVar3; + ISO_VAGCommand* msg; + int iVar5; + ISO_VAGCommand** ppIVar6; + + WaitSema(g_nPriQueueSema); + if (gPriStack[1].count < 8) { + iVar5 = gPriStack[1].count + -1; + if (-1 < iVar5) { + ppIVar6 = (ISO_VAGCommand**)gPriStack[0].cmds + iVar5; + do { + msg = ppIVar6[9]; + if (msg != (ISO_VAGCommand*)0x0) { + // lg::warn("process stream data for {}, {} {} {}\n", msg->name, + // (msg->status == EIsoStatus::OK_2), (msg->active_b != 0), + // (msg->active_c != 0)); + if (((msg->status == EIsoStatus::OK_2) && (msg->active_b != 0)) && (msg->active_c != 0)) { + auto* file = msg->m_pBaseFile; + pCVar3 = &file->m_Buffer; + if (!file) { + pCVar3 = (CBuffer*)0x0; + } + if (((pCVar3 != (CBuffer*)0x0) && + (pCVar3->m_eBufferType != CBuffer::BufferType::EBT_FREE)) && + (msg->callback == ProcessVAGData)) { + iVar2 = ProcessVAGData(msg); + msg->status = iVar2; + if ((iVar2 != EIsoStatus::OK_2) && (msg->safe_to_modify_dma != 0)) { + ReleaseMessage(msg); + } + } + } + if (msg->status != EIsoStatus::OK_2) { + ReleaseMessage(msg); + } + } + iVar5 = iVar5 + -1; + ppIVar6 = ppIVar6 + -1; + } while (-1 < iVar5); + } + } + SignalSema(g_nPriQueueSema); +} + +void CheckVagStreamsProgress() { + int now; + int iVar1; + bool* flags; + int iVar2; + u32* times; + ISO_VAGCommand* cmd; + + if ((g_bVagCmdsInitialized != 0) && (g_bSoundEnable != 0)) { + now = GetSystemTimeLow(); + times = voice_key_times; + flags = voice_key_flags; + iVar2 = 0x2f; + do { + iVar2 = iVar2 + -1; + if ((*flags != 0) && (0x17ff < (u32)(*times - now))) { + *flags = 0; + } + flags = flags + 1; + times = times + 1; + } while (-1 < iVar2); + if ((g_bRecentlyKeyedVoice != 0) && (0x17ff < (u32)(g_nTimeOfLastVoiceKey - now))) { + g_bRecentlyKeyedVoice = 0; + } + iVar2 = 5; + ProcessStreamData(); + // CpuSuspendIntr(local_18); + cmd = g_aVagCmds; + do { + if (((cmd->flags.saw_chunks1 != 0) || + ((cmd->flags.running != 0 && (cmd->flags.file_disappeared != 0)))) || + ((cmd->active_b != 0 && (cmd->id != 0)))) { + iVar1 = CheckVAGStreamProgress(cmd); + if (iVar1 == 0) { + if (cmd->flags.stereo_secondary == 0) { + StopVagStream(cmd); + } + } else { + GetVAGStreamPos(cmd); + } + } + iVar2 = iVar2 + -1; + cmd = cmd + 1; + } while (-1 < iVar2); + // CpuResumeIntr(local_18[0]); + } +} + +void BlockUntilAllVoicesSafe() { + int now; + int last_time; + + last_time = g_nTimeOfLastVoiceKey; + if (g_bRecentlyKeyedVoice != 0) { + do { + now = GetSystemTimeLow(); + } while ((u32)(now - last_time) < 0x900); + } +} + +void BlockUntilVoiceSafe(int voice, u32 delay) { + int iVar1; + int iVar2; + + if (voice_key_flags[voice] != 0) { + iVar2 = voice_key_times[voice]; + do { + iVar1 = GetSystemTimeLow(); + } while ((u32)(iVar1 - iVar2) < delay); + } +} + +void MarkVoiceKeyedOnOff(int voice, u32 time) { + g_nTimeOfLastVoiceKey = time; + voice_key_times[voice] = time; + voice_key_flags[voice] = 1; + g_bRecentlyKeyedVoice = 1; +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/spustreams.h b/game/overlord/jak3/spustreams.h new file mode 100644 index 0000000000..1c292e3953 --- /dev/null +++ b/game/overlord/jak3/spustreams.h @@ -0,0 +1,14 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/overlord/jak3/isocommon.h" + +namespace jak3 { +struct ISO_Hdr; +struct ISO_VAGCommand; +void jak3_overlord_init_globals_spustreams(); +EIsoStatus ProcessVAGData(ISO_Hdr* msg); +void StopVagStream(ISO_VAGCommand* cmd); +u32 GetSpuRamAddress(ISO_VAGCommand* cmd); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/srpc.cpp b/game/overlord/jak3/srpc.cpp new file mode 100644 index 0000000000..3df21824d4 --- /dev/null +++ b/game/overlord/jak3/srpc.cpp @@ -0,0 +1,604 @@ +#include "srpc.h" + +#include "common/util/Assert.h" + +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/iso_api.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/rpc_interface.h" +#include "game/overlord/jak3/sbank.h" +#include "game/overlord/jak3/soundcommon.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/vag.h" +#include "game/overlord/jak3/vblank_handler.h" +#include "game/sce/iop.h" +#include "game/sound/sndshim.h" + +namespace jak3 { + +using namespace iop; + +// This file has two RPCs: PLAYER and LOADER +// Generally, PLAYER receives commands to play/pause sound effects or streams, which complete +// quickly. + +// LOADER will load soundbanks, and can take some time to complete - likely why it is moved +// into its own RPC, to avoid having soundbank loads block playback of other sounds. + +constexpr int kPlayerBufSize = 0x50 * 128; +static uint8_t s_anRPC_PlayerBuf[kPlayerBufSize]; + +constexpr int kLoaderBufSize = 0x50; +static uint8_t s_anRPC_LoaderBuf[kLoaderBufSize]; + +constexpr u32 kNumLanguages = 12; +static const char* languages[kNumLanguages] = {"ENG", "FRE", "GER", "SPA", "ITA", "COM", + "JAP", "KOR", "RUS", "POR", "DUT", "UKE"}; + +const char* g_pszLanguage = languages[0]; +u8 g_nFPS = 60; +SoundBankInfo* g_LoadingSoundBank = nullptr; + +void jak3_overlord_init_globals_srpc() { + g_nFPS = 60; + g_LoadingSoundBank = nullptr; + g_pszLanguage = languages[0]; +} + +u32 Thread_Player() { + sceSifQueueData dq; + sceSifServeData serve; + + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, RpcId::Player, RPC_Player, &s_anRPC_PlayerBuf, kPlayerBufSize, nullptr, + nullptr, &dq); + + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} + +u32 Thread_Loader() { + sceSifQueueData dq; + sceSifServeData serve; + + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, RpcId::Loader, RPC_Loader, &s_anRPC_LoaderBuf, kLoaderBufSize, nullptr, + nullptr, &dq); + + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} + +void* RPC_Player(unsigned int, void* msg, int size) { + if (!g_bSoundEnable) { + return nullptr; + } + + // const auto* cmd = (RPC_Player_Cmd*)msg; + ovrld_log(LogCategory::PLAYER_RPC, "Got Player RPC with {} cmds", size / kPlayerCommandStride); + const u8* m_ptr = (const u8*)msg; + const u8* end = m_ptr + size; + + for (; m_ptr < end; m_ptr += kPlayerCommandStride) { + switch (((const Rpc_Player_Base_Cmd*)m_ptr)->command) { + case SoundCommand::PLAY: { + const auto* cmd = (const Rpc_Player_Play_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[Player RPC] command PLAY {} id {}", cmd->name.data, + cmd->sound_id); + s32 id = cmd->sound_id; + if (id) { + auto* sound = LookupSound(id); + if (!sound) { + ovrld_log(LogCategory::PLAYER_RPC, "[Player RPC] allocating a new one"); + sound = AllocateSound(); + if (sound) { + SFXUserData user_val; + strcpy_toupper(sound->name.data, cmd->name.data); + sound->params = cmd->params; + sound->auto_time = 0; + s32 get_status = + snd_GetSoundUserData(nullptr, nullptr, -1, sound->name.data, &user_val); + s32 mask = sound->params.mask; + if ((mask & 8) == 0) { + sound->params.group = 0; + } + if ((mask & 0x40) == 0) { + if (get_status == 0 || user_val.data[0] == 0) { + sound->params.fo_min = 5; + } else { + sound->params.fo_min = (int16_t)user_val.data[0]; + } + } + if ((mask & 0x80) == 0) { + if (get_status == 0 || user_val.data[1] == 0) { + sound->params.fo_max = 0x1e; + } else { + sound->params.fo_max = (int16_t)user_val.data[1]; + } + } + if ((mask & 0x100) == 0) { + u32 fo_curve = 0; + if (get_status != 0) { + fo_curve = user_val.data[2]; + } + (sound->params).fo_curve = fo_curve; + } + (sound->params).fo_curve = GetFalloffCurve(sound->params.fo_curve); + auto handle = snd_PlaySoundByNameVolPanPMPB( + 0, 0, sound->name.data, GetVolume(sound), GetPan(sound), + (int)(sound->params).pitch_mod, (int)(sound->params).bend); + sound->id = id; + sound->sound_handle = handle; + if (handle != 0) { + if ((sound->params.mask & 0x800) != 0) { + snd_SetSoundReg(sound->sound_handle, 0, sound->params.reg[0]); + } + if ((sound->params.mask & 0x1000) != 0) { + snd_SetSoundReg(sound->sound_handle, 1, sound->params.reg[1]); + } + if ((sound->params.mask & 0x2000) != 0) { + snd_SetSoundReg(sound->sound_handle, 2, sound->params.reg[2]); + } + } + } + } else { + SFXUserData user_val; + sound->params = cmd->params; + s32 get_status = + snd_GetSoundUserData(nullptr, nullptr, -1, sound->name.data, &user_val); + s32 mask = (sound->params).mask; + if ((mask & 8) == 0) { + (sound->params).group = 0; + } + if ((mask & 0x40) == 0) { + if (get_status == 0 || user_val.data[0] == 0) { + (sound->params).fo_min = 5; + } else { + (sound->params).fo_min = user_val.data[0]; + } + } + if ((mask & 0x80) == 0) { + if (get_status == 0 || user_val.data[1] == 0) { + (sound->params).fo_max = 0x1e; + } else { + (sound->params).fo_max = user_val.data[1]; + } + } + if ((mask & 0x100) == 0) { + s8 fo_curve = 0; + if (get_status != 0) { + fo_curve = user_val.data[2]; + } + (sound->params).fo_curve = fo_curve; + } + (sound->params).fo_curve = GetFalloffCurve(sound->params.fo_curve); + UpdateVolume(sound); + snd_SetSoundPitchModifier(sound->sound_handle, (int)(sound->params).pitch_mod); + if (((sound->params).mask & 4) != 0) { + snd_SetSoundPitchBend(sound->sound_handle, (int)(sound->params).bend); + } + if (((sound->params).mask & 0x800) != 0) { + snd_SetSoundReg(sound->sound_handle, 0, sound->params.reg[0]); + } + if (((sound->params).mask & 0x1000) != 0) { + snd_SetSoundReg(sound->sound_handle, 1, (int)(char)(sound->params).reg[1]); + } + if (((sound->params).mask & 0x2000) != 0) { + snd_SetSoundReg(sound->sound_handle, 2, sound->params.reg[2]); + } + } + } + } break; + case SoundCommand::PAUSE_SOUND: { + const auto* cmd = (const Rpc_Player_Sound_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Pause Sound ID {}", cmd->sound_id); + if (cmd->sound_id) { + auto* sound = LookupSound(cmd->sound_id); + if (sound) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to pause", + sound->name.data); + snd_PauseSound(sound->sound_handle); + } else { + auto* vag = FindVagStreamId(cmd->sound_id); + if (vag) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to pause", + vag->name); + PauseVAG(vag); + } + } + } + } break; + case SoundCommand::STOP_SOUND: { + const auto* cmd = (const Rpc_Player_Sound_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] stop Sound ID {}", cmd->sound_id); + if (cmd->sound_id) { + auto* sound = LookupSound(cmd->sound_id); + if (sound) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to stop", + sound->name.data); + snd_StopSound(sound->sound_handle); + } else { + auto* vag = FindVagStreamId(cmd->sound_id); + if (vag) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to stop", + vag->name); + StopVagStream(vag); + } + } + } + } break; + case SoundCommand::CONTINUE_SOUND: { + const auto* cmd = (const Rpc_Player_Sound_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] continue Sound ID {}", cmd->sound_id); + if (cmd->sound_id) { + auto* sound = LookupSound(cmd->sound_id); + if (sound) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to continue", + sound->name.data); + snd_ContinueSound(sound->sound_handle); + } else { + auto* vag = FindVagStreamId(cmd->sound_id); + if (vag) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to continue", + vag->name); + UnPauseVAG(vag); + } + } + } + } break; + case SoundCommand::SET_PARAM: { + const auto* cmd = (const Rpc_Player_Set_Param_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] SET_PARAM Sound ID {}", cmd->sound_id); + if (cmd->sound_id) { + auto* sound = LookupSound(cmd->sound_id); + if (sound) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching Sound {} to SET_PARAM", + sound->name.data); + auto& params = cmd->params; + u16 mask = cmd->params.mask; + s32 atime = cmd->auto_time; + s32 afrom = cmd->auto_from; + if ((mask & 1) != 0) { + if ((mask & 0x10) == 0) { + sound->params.volume = params.volume; + } else { + sound->auto_time = atime; + sound->new_volume = params.volume; + } + } + if ((mask & 0x20) != 0) { + sound->params.trans[0] = params.trans[0]; + sound->params.trans[1] = params.trans[1]; + sound->params.trans[2] = params.trans[2]; + } + if ((mask & 0x21) != 0) { + UpdateVolume(sound); + } + if ((mask & 2) != 0) { + auto pitch_mod = params.pitch_mod; + sound->params.pitch_mod = pitch_mod; + if ((mask & 0x10) == 0) { + snd_SetSoundPitchModifier(sound->sound_handle, params.pitch_mod); + } else { + snd_AutoPitch(sound->sound_handle, pitch_mod, atime, afrom); + } + } + if ((mask & 4) != 0) { + auto bend = params.bend; + sound->params.bend = bend; + if ((mask & 0x10) == 0) { + snd_SetSoundPitchBend(sound->sound_handle, params.bend); + } else { + snd_AutoPitchBend(sound->sound_handle, (int)bend, atime, afrom); + } + } + if ((mask & 0x400) != 0) { + sound->params.priority = params.priority; + } + if ((mask & 8) != 0) { + sound->params.group = params.group; + } + if ((mask & 0x40) != 0) { + sound->params.fo_min = params.fo_min; + } + if ((mask & 0x80) != 0) { + sound->params.fo_max = params.fo_max; + } + if ((mask & 0x100) != 0) { + sound->params.fo_curve = GetFalloffCurve(params.fo_curve); + } + if ((mask & 0x800) != 0) { + sound->params.reg[0] = params.reg[0]; + snd_SetSoundReg(sound->sound_handle, 0, params.reg[0]); + } + if ((mask & 0x1000) != 0) { + sound->params.reg[1] = params.reg[1]; + snd_SetSoundReg(sound->sound_handle, 1, params.reg[1]); + } + if ((mask & 0x2000) != 0) { + sound->params.reg[2] = params.reg[2]; + snd_SetSoundReg(sound->sound_handle, 2, params.reg[2]); + } + } else { + auto* vag = FindVagStreamId(cmd->sound_id); + if (vag) { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Found matching VAG {} to SET_PARAM", + vag->name); + auto& params = cmd->params; + auto mask = params.mask; + if ((mask & 2) != 0) { + SetVAGStreamPitch(cmd->sound_id, params.pitch_mod); + } + if ((mask & 0x20) != 0) { + vag->trans[0] = params.trans[0]; + vag->trans[1] = params.trans[1]; + vag->trans[2] = params.trans[2]; + vag->updated_trans = 1; + } + if ((mask & 8) != 0) { + vag->play_group = params.group; + } + if ((mask & 0x40) != 0) { + vag->fo_min = (int)params.fo_min; + } + if ((mask & 0x80) != 0) { + vag->fo_max = (int)params.fo_max; + } + if ((mask & 0x100) != 0) { + vag->fo_curve = GetFalloffCurve(params.fo_curve); + } + if ((mask & 1) != 0) { + vag->play_volume = params.volume; + } + } + } + } + } break; + case SoundCommand::SET_MASTER_VOLUME: { + const auto* cmd = (const Rpc_Player_Set_Master_Volume_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Set Master Volume to {}", cmd->volume); + for (int i = 0; i < 17; i++) { + if (cmd->group & (1 << i)) { + g_anMasterVolume[i] = cmd->volume; + snd_SetMasterVolume(i, cmd->volume); + SetAllVagsVol(i); + } + } + } break; + case SoundCommand::PAUSE_GROUP: { + const auto* cmd = (const Rpc_Player_Group_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Pause groups 0b{:b}", cmd->group); + snd_PauseAllSoundsInGroup(cmd->group); + if (cmd->group & 4) { + PauseVAGStreams(); + } + if (cmd->group & 2) { + g_bMusicPause = true; + } + } break; + case SoundCommand::STOP_GROUP: { + const auto* cmd = (const Rpc_Player_Group_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Stop groups 0b{:b}", cmd->group); + KillSoundsInGroup(cmd->group); + if (cmd->group & 4) { + ISO_VAGCommand vag_cmd; + vag_cmd.msg_type = ISO_Hdr::MsgType::VAG_STOP; // seems unsupported by iso thread. + vag_cmd.mbox_reply = 0; + vag_cmd.thread_to_wake = 0; + vag_cmd.vag_dir_entry = nullptr; + vag_cmd.name[0] = 0; + vag_cmd.maybe_sound_handler = 0; + vag_cmd.id = 0; + vag_cmd.priority_pq = 0; + StopVagStream(&vag_cmd); + } + } break; + case SoundCommand::CONTINUE_GROUP: { + const auto* cmd = (const Rpc_Player_Group_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Continue groups 0b{:b}", cmd->group); + snd_ContinueAllSoundsInGroup(cmd->group); + if (cmd->group & 4) { + UnpauseVAGStreams(); + } + if (cmd->group & 2) { + g_bMusicPause = false; + } + } break; + case SoundCommand::SET_REVERB: { + ovrld_log(LogCategory::WARN, "[RPC Player] Unimplemented set reverb."); + } break; + case SoundCommand::SET_EAR_TRANS: { + const auto* cmd = (const Rpc_Player_Set_Ear_Trans_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] set ear trans"); + SetEarTrans(cmd->ear_trans0, cmd->ear_trans1, cmd->ear_trans, cmd->cam_forward, + cmd->cam_left, cmd->cam_scale, (cmd->cam_inverted != 0)); + } break; + case SoundCommand::SHUTDOWN: { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] Shutdown!"); + WaitSema(g_n989Semaphore); + if (g_bSoundEnable) { + g_bSoundEnable = false; + snd_StopSoundSystem(); + } + SignalSema(g_n989Semaphore); + } break; + case SoundCommand::SET_FPS: { + const auto* cmd = (const Rpc_Player_Set_Fps_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] set fps {}", (int)cmd->fps); + g_nFPS = cmd->fps; + } break; + case SoundCommand::CANCEL_DGO: { + const auto* cmd = (const Rpc_Player_Cancel_Dgo_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Player] cancel dgo {}", cmd->id); + CancelDGONoSync(cmd->id); + } break; + case SoundCommand::SET_MIDI_REG: + // this is what the real overlord does - just ignore it! + break; + default: + ovrld_log(LogCategory::WARN, "[RPC Player] Unsupported Player {}", + (int)((const Rpc_Player_Base_Cmd*)m_ptr)->command); + ASSERT_NOT_REACHED(); + } + } + + return nullptr; +} + +void* RPC_Loader(unsigned int, void* msg, int size) { + if (!g_bSoundEnable) { + return nullptr; + } + + // const auto* cmd = (RPC_Player_Cmd*)msg; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got Loader RPC with {} cmds", + size / kLoaderCommandStride); + u8* m_ptr = (u8*)msg; + const u8* end = m_ptr + size; + + for (; m_ptr < end; m_ptr += kLoaderCommandStride) { + switch (((const Rpc_Player_Base_Cmd*)m_ptr)->command) { + case SoundCommand::LOAD_BANK: { + auto* cmd = (const Rpc_Loader_Load_Bank_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got sound bank load command: {}", + cmd->bank_name.data); + // src = &cmd->bank_name; + if (!LookupBank(cmd->bank_name.data)) { + auto* info = AllocateBankName(cmd->bank_name.data, cmd->mode); + if (info) { + strncpyz(info->m_name1, cmd->bank_name.data, 0x10); + info->in_use = 1; + info->unk0 = 0; + g_LoadingSoundBank = info; + if (LoadSoundBankToIOP(cmd->bank_name.data, info, cmd->priority) == 0) { + info->loaded = 1; + } else { + info->loaded = 0; + info->in_use = 0; + } + g_LoadingSoundBank = nullptr; + } + } + } break; + case SoundCommand::LOAD_MUSIC: { + auto* cmd = (const Rpc_Loader_Bank_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got music load command: {}", + cmd->bank_name.data); + + // lock + u32 wait_status = 1; + while (wait_status) { + wait_status = WaitSema(g_nMusicSemaphore); + } + + // set music name + if ((cmd->bank_name).data[0] == 0) { + g_szTargetMusicName[0] = 0; + } else { + strcpy(g_szTargetMusicName, cmd->bank_name.data); + } + + // release + SignalSema(g_nMusicSemaphore); + } break; + + case SoundCommand::UNLOAD_BANK: { + auto* cmd = (const Rpc_Loader_Bank_Cmd*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got bank load unload command: {}", + cmd->bank_name.data); + SoundBankInfo* ifno = LookupBank(cmd->bank_name.data); + if (ifno) { + auto snd_handle = ifno->snd_handle; + ifno->snd_handle = nullptr; + if (ifno->unk0 == 0) { + ifno->in_use = 0; + } + ifno->mode = 0; + ifno->loaded = 0; + snd_UnloadBank(snd_handle); + snd_ResolveBankXREFS(); + } + } break; + + case SoundCommand::GET_IRX_VERSION: { + auto* cmd = (Rpc_Loader_Get_Irx_Version*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got IRX version command"); + g_nInfoEE = cmd->ee_addr; + cmd->major = 4; + cmd->minor = 0; + return cmd; + } break; + + case SoundCommand::SET_LANGUAGE: { + auto* cmd = (Rpc_Loader_Set_Language*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got set language command {}", cmd->lang); + ASSERT(cmd->lang < kNumLanguages); + g_pszLanguage = languages[cmd->lang]; + } break; + + case SoundCommand::UNLOAD_MUSIC: { + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got unload music command"); + + // lock + u32 wait_status = 1; + while (wait_status) { + wait_status = WaitSema(g_nMusicSemaphore); + } + + // set music name + g_szTargetMusicName[0] = 0; + + // release + SignalSema(g_nMusicSemaphore); + } break; + + case SoundCommand::SET_STEREO_MODE: { + auto* cmd = (Rpc_Loader_Set_Stereo_Mode*)m_ptr; + ovrld_log(LogCategory::PLAYER_RPC, "[RPC Loader] Got set stereo command {}", cmd->mode); + + switch (cmd->mode) { + case 0: + SetPlaybackMode(1); + break; + case 1: + SetPlaybackMode(2); + break; + case 2: + SetPlaybackMode(0); + break; + default: + ASSERT_NOT_REACHED(); + } + } break; + + default: + ovrld_log(LogCategory::WARN, "[RPC Loader] Unsupported Loader {}", + (int)((const Rpc_Player_Base_Cmd*)m_ptr)->command); + ASSERT_NOT_REACHED(); + break; + } + } + return nullptr; +} + +void SetVagStreamName(ISO_VAGCommand* cmd, int len) { + ASSERT(cmd); + if (!cmd->music_flag && cmd->info_idx < 4) { + if (!len) { + g_SRPCSoundIOPInfo.stream_name[cmd->info_idx].chars[0] = 0; + } else { + strncpy(g_SRPCSoundIOPInfo.stream_name[cmd->info_idx].chars, cmd->name, 0x30); + } + } else { + // ASSERT_NOT_REACHED(); + } +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/srpc.h b/game/overlord/jak3/srpc.h new file mode 100644 index 0000000000..48fe12a26e --- /dev/null +++ b/game/overlord/jak3/srpc.h @@ -0,0 +1,16 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_srpc(); +u32 Thread_Player(); +u32 Thread_Loader(); +struct ISO_VAGCommand; +void SetVagStreamName(ISO_VAGCommand* cmd, int len); +void* RPC_Player(unsigned int fno, void* msg, int size); +void* RPC_Loader(unsigned int fno, void* msg, int size); +extern const char* g_pszLanguage; +extern u8 g_nFPS; + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/ssound.cpp b/game/overlord/jak3/ssound.cpp new file mode 100644 index 0000000000..b1f21aef0e --- /dev/null +++ b/game/overlord/jak3/ssound.cpp @@ -0,0 +1,995 @@ +#include "ssound.h" + +#include + +#include "common/util/Assert.h" + +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" +#include "game/sound/sdshim.h" +#include "game/sound/sndshim.h" + +namespace jak3 { + +struct Curve { + s32 a, b, c, d; +}; + +constexpr int kNumSounds = 0x40; + +using namespace iop; +s32 g_n989Semaphore = -1; +s32 g_EarTransSema = -1; +bool g_bSoundEnable = true; +u32 g_anStreamVoice[6]; +VolumePair g_aPanTable[361]; +SoundInfo gSounds[kNumSounds]; +s32 gEarTrans[6]; +s32 gCamTrans[3]; +s32 gCamForward[3]; +s32 gCamLeft[3]; +s32 gCamScale; +Curve gCurves[0x29]; +std::array unktable; +bool g_CameraInvert = false; +u32 gLastTick = 0; + +static s32 sqrt_table[256] = { + 0, 4096, 5793, 7094, 8192, 9159, 10033, 10837, 11585, 12288, 12953, 13585, 14189, + 14768, 15326, 15864, 16384, 16888, 17378, 17854, 18318, 18770, 19212, 19644, 20066, 20480, + 20886, 21283, 21674, 22058, 22435, 22806, 23170, 23530, 23884, 24232, 24576, 24915, 25249, + 25580, 25905, 26227, 26545, 26859, 27170, 27477, 27780, 28081, 28378, 28672, 28963, 29251, + 29537, 29819, 30099, 30377, 30652, 30924, 31194, 31462, 31727, 31991, 32252, 32511, 32768, + 33023, 33276, 33527, 33776, 34024, 34270, 34514, 34756, 34996, 35235, 35472, 35708, 35942, + 36175, 36406, 36636, 36864, 37091, 37316, 37540, 37763, 37985, 38205, 38424, 38642, 38858, + 39073, 39287, 39500, 39712, 39923, 40132, 40341, 40548, 40755, 40960, 41164, 41368, 41570, + 41771, 41972, 42171, 42369, 42567, 42763, 42959, 43154, 43348, 43541, 43733, 43925, 44115, + 44305, 44494, 44682, 44869, 45056, 45242, 45427, 45611, 45795, 45977, 46160, 46341, 46522, + 46702, 46881, 47059, 47237, 47415, 47591, 47767, 47942, 48117, 48291, 48465, 48637, 48809, + 48981, 49152, 49322, 49492, 49661, 49830, 49998, 50166, 50332, 50499, 50665, 50830, 50995, + 51159, 51323, 51486, 51649, 51811, 51972, 52134, 52294, 52454, 52614, 52773, 52932, 53090, + 53248, 53405, 53562, 53719, 53874, 54030, 54185, 54340, 54494, 54647, 54801, 54954, 55106, + 55258, 55410, 55561, 55712, 55862, 56012, 56162, 56311, 56459, 56608, 56756, 56903, 57051, + 57198, 57344, 57490, 57636, 57781, 57926, 58071, 58215, 58359, 58503, 58646, 58789, 58931, + 59073, 59215, 59357, 59498, 59639, 59779, 59919, 60059, 60199, 60338, 60477, 60615, 60753, + 60891, 61029, 61166, 61303, 61440, 61576, 61712, 61848, 61984, 62119, 62254, 62388, 62523, + 62657, 62790, 62924, 63057, 63190, 63323, 63455, 63587, 63719, 63850, 63982, 64113, 64243, + 64374, 64504, 64634, 64763, 64893, 65022, 65151, 65279, 65408, +}; + +void jak3_overlord_init_globals_ssound() { + g_bSoundEnable = true; + g_n989Semaphore = -1; + g_EarTransSema = -1; + for (auto& x : g_anStreamVoice) { + x = 0; + } + for (auto& x : gSounds) { + x = {}; + } + unktable.fill(0); + g_CameraInvert = false; + gLastTick = 0; +} +void InitSound() { + for (auto& sound : gSounds) { + sound.id = 0; + } + + int j = 0; + do { + unktable[j] = 0; + unktable[j + 0x2c] = 0; + unktable[j + 0x58] = 0; + unktable[j + 0x84] = 0; + unktable[j + 0xb0] = 0; + j = j + 1; + } while (j < 0x29); + + SetCurve(0, 0, 0, 1, 0, 0, 0, 0); + SetCurve(1, 0, 0, 0, 0, 0, 1, 0); + SetCurve(2, 0, 0, 1, 0, 0, 0, 0); + SetCurve(3, 0x1000, 0, 1, 0, 0, 0, 0); + SetCurve(4, 0, 0x1000, 1, 0, 0, 0, 0); + SetCurve(5, 0x800, 0, 1, 0, 0, 0, 0); + SetCurve(6, 0x800, 0x800, 1, 0, 0, 0, 0); + SetCurve(7, 0xfffff000, 0, 1, 0, 0, 0, 0); + SetCurve(8, 0xfffff800, 0, 1, 0, 0, 0, 0); + SetCurve(9, 0, 0, 1, 0, 0, 0, 0); + SetCurve(10, 0, 0, 0, 0, 0, 0, 0); + SetCurve(0xb, 0, 0, 1, 0, 0, 0, 0); + SetCurve(0xc, 0, 0, 1, 0, 1, 0, 0); + SetCurve(0xd, 0x1000, 0, 1, 0, 1, 0, 0); + SetCurve(0xe, 0, 0x1000, 1, 0, 1, 0, 0); + SetCurve(0xf, 0x800, 0, 1, 0, 1, 0, 0); + SetCurve(0x10, 0x800, 0x800, 1, 0, 1, 0, 0); + SetCurve(0x11, 0xfffff000, 0, 1, 0, 1, 0, 0); + SetCurve(0x12, 0xfffff800, 0, 1, 0, 1, 0, 0); + SetCurve(0x13, 0, 0, 0, 0, 1, 0, 0); + SetCurve(0x14, 0, 0, 0, 0, 0, 1, 1); + SetCurve(0x15, 0, 0, 1, 0, 0, 0, 1); + SetCurve(0x16, 0x1000, 0, 1, 0, 0, 0, 1); + SetCurve(0x17, 0, 0x1000, 1, 0, 0, 0, 1); + SetCurve(0x18, 0x800, 0, 1, 0, 0, 0, 1); + SetCurve(0x19, 0x800, 0x800, 1, 0, 0, 0, 1); + SetCurve(0x1a, 0xfffff000, 0, 1, 0, 0, 0, 1); + SetCurve(0x1b, 0xfffff800, 0, 1, 0, 0, 0, 1); + SetCurve(0x1c, 0, 0, 1, 0, 0, 0, 1); + SetCurve(0x1d, 0, 0, 0, 0, 0, 0, 1); + SetCurve(0x1e, 0, 0, 1, 0, 0, 0, 1); + SetCurve(0x1f, 0, 0, 1, 0, 1, 0, 1); + SetCurve(0x20, 0x1000, 0, 1, 0, 1, 0, 1); + SetCurve(0x21, 0, 0x1000, 1, 0, 1, 0, 1); + SetCurve(0x22, 0x800, 0, 1, 0, 1, 0, 1); + SetCurve(0x23, 0x800, 0x800, 1, 0, 1, 0, 1); + SetCurve(0x24, 0xfffff000, 0, 1, 0, 1, 0, 1); + SetCurve(0x25, 0xfffff800, 0, 1, 0, 1, 0, 1); + SetCurve(0x26, 0, 0, 0, 0, 1, 0, 1); + SetCurve(0x27, 0, 0, 1, 1, 0, 1, 0); + SetCurve(0x28, 0, 0, 1, 1, 0, 1, 1); + + // changed + // snd_StartSoundSystemEx(2); + snd_StartSoundSystem(); + + // iVar4 = 5; + // snd_RegisterIOPMemAllocator(FUN_0000dc7c,FUN_0000de84); + // snd_LockVoiceAllocatorEx(1,0x12345678); + // piVar1 = g_anStreamVoice; + // do { + // iVar2 = snd_ExternVoiceAlloc(2,0x7f); + // iVar4 = iVar4 + -1; + // *piVar1 = iVar2 * 2 + ((iVar2 / 6 + (iVar2 >> 0x1f) >> 2) - (iVar2 >> 0x1f)) * -0x2f; + // piVar1 = piVar1 + 1; + // } while (-1 < iVar4); + + g_anStreamVoice[0] = SD_VOICE(0, 0); + g_anStreamVoice[1] = SD_VOICE(0, 1); + g_anStreamVoice[2] = SD_VOICE(0, 2); + g_anStreamVoice[3] = SD_VOICE(0, 3); + g_anStreamVoice[4] = SD_VOICE(0, 4); + g_anStreamVoice[5] = SD_VOICE(0, 5); + + // snd_UnlockVoiceAllocator(); + // snd_SetMixerMode(0,0); + // iVar4 = 0; + // do { + // iVar2 = iVar4 + 1; + // snd_SetGroupVoiceRange(iVar4,6,0x2f); + // iVar4 = iVar2; + // } while (iVar2 < 0xe); + // snd_SetGroupVoiceRange(2,0,5); + + // what is this even doing. + // sceSdGetAddr(0x1c00); + // sceSdGetAddr(0x1d00); + // sceSdGetAddr(0x1c01); + // sceSdGetAddr(0x1d01); + // CpuSuspendIntr(local_18); + // sceSdSetAddr(0,0); + // sceSdSetAddr(1,0); + // sceSdSetAddr(0,0xff); + // sceSdSetAddr(1,0xff); + // CpuResumeIntr(local_18[0]); + + // uVar3 = sceSdGetAddr(0x1c01); + // snd_SRAMMarkUsed(uVar3,0x7000); + // uVar3 = sceSdGetAddr(0x1c00); + // snd_SRAMMarkUsed(uVar3,0x7000); + // local_3c = 0x104; + // local_36 = 0xa7b; + // local_38 = 0xa7b; + // g_nCore1ReverbMode = 4; + // g_nCore0ReverbMode = 4; + // local_34 = 0; + // local_30 = 0; + // local_40 = 0; + // sceSdSetEffectAttr(0, &local_40); + // local_40 = 1; + // sceSdSetEffectAttr(1, &local_40); + // maybe_sceSdSetCoreAttr(2, 1); + // maybe_sceSdSetCoreAttr(3, 1); + + // TODO: this is possibly very wrong: + // g_aPanTable = snd_GetPanTable(); + for (int i = 0; i < 91; i++) { + s16 opposing_front = static_cast(((i * 0x33ff) / 90) + 0xc00); + + s16 rear_right = static_cast(((i * -0x2800) / 90) + 0x3400); + s16 rear_left = static_cast(((i * -0xbff) / 90) + 0x3fff); + + g_aPanTable[90 - i].left = 0x3FFF; + g_aPanTable[180 - i].left = opposing_front; + g_aPanTable[270 - i].left = rear_right; + g_aPanTable[360 - i].left = rear_left; + + g_aPanTable[i].right = opposing_front; + g_aPanTable[90 + i].right = 0x3FFF; + g_aPanTable[180 + i].right = rear_left; + g_aPanTable[270 + i].right = rear_right; + } + + SetPlaybackMode(2); + + SemaParam param; + param.attr = 1; + param.init_count = 1; + param.max_count = 1; + param.option = 0; + g_nMusicSemaphore = CreateSema(¶m); + ASSERT(g_nMusicSemaphore >= 0); + param.attr = 1; + param.init_count = 1; + param.max_count = 1; + param.option = 0; + g_n989Semaphore = CreateSema(¶m); + ASSERT(g_n989Semaphore >= 0); + + param.max_count = 1; + param.attr = 1; + param.init_count = 1; + param.option = 0; + g_EarTransSema = CreateSema(¶m); + ASSERT(g_EarTransSema >= 0); + + // Init989Plugins(); + // InitStreamLfoHandler(); + // InitVagStreamList((List*)&g_PluginStreamsList, 4, s_plugin_00015918); + InitVagStreamList(&g_EEStreamsList, 4, "ee"); + InitVagStreamList(&g_EEPlayList, 8, "play"); + InitVagStreamList(&g_RequestedStreamsList, 8, "streams"); + InitVagStreamList(&g_NewStreamsList, 4, "new"); +} + +SoundInfo* LookupSound(int id) { + if (id == 0) { + return nullptr; + } + + for (auto& sound : gSounds) { + if (sound.id == id) { + s32 handle = snd_SoundIsStillPlaying(sound.sound_handle); + sound.sound_handle = handle; + if (handle) { + return &sound; + } else { + sound.id = 0; + return nullptr; + } + } + } + + return nullptr; +} + +void CleanSounds() { + for (auto& sound : gSounds) { + if (sound.id) { + s32 handle = snd_SoundIsStillPlaying(sound.sound_handle); + sound.sound_handle = handle; + if (handle == 0) { + sound.id = 0; + } + } + } +} + +void KillSoundsInGroup(u32 group) { + for (auto& sound : gSounds) { + if (sound.id) { + s32 handle = snd_SoundIsStillPlaying(sound.sound_handle); + sound.sound_handle = handle; + if (handle) { + if (sound.params.group & group) { + snd_StopSound(handle); + sound.id = 0; + } + } else { + sound.id = 0; + } + } + } +} + +void KillLeastUsefulSound() { + int unique_sounds = 0; + struct Entry { + u32 id; + u32 count; + SoundInfo* info; + }; + Entry entries[kNumSounds]; + Entry* best_entry = nullptr; + + for (auto& sound : gSounds) { + if (sound.id) { + Entry* existing_entry = nullptr; + u32 uid = snd_GetSoundID(sound.sound_handle); + + // look for entry: + for (int i = 0; i < unique_sounds; i++) { + if (entries[i].id == uid) { + existing_entry = &entries[i]; + break; + } + } + + // if none found, create + if (!existing_entry) { + existing_entry = &entries[unique_sounds]; + unique_sounds++; + existing_entry->id = uid; + existing_entry->count = 0; + existing_entry->info = &sound; + } + + // update + existing_entry->count++; + + // se if we're best + if (!best_entry) { + best_entry = existing_entry; + } else { + if (best_entry->count < existing_entry->count) { + best_entry = existing_entry; + } + } + + // update entry: + + // update best: + } + } + + if (best_entry) { + snd_StopSound(best_entry->info->sound_handle); + best_entry->info->id = 0; + } +} + +SoundInfo* AllocateSound() { + for (auto& sound : gSounds) { + if (!sound.id) { + return &sound; + } + } + + CleanSounds(); + for (auto& sound : gSounds) { + if (!sound.id) { + return &sound; + } + } + + KillLeastUsefulSound(); + + for (auto& sound : gSounds) { + if (!sound.id) { + return &sound; + } + } + + ASSERT_NOT_REACHED(); + return nullptr; +} + +u32 CalculateFalloffVolume(s32* trans, + u32 vol, + u32 fo_curve, + u32 fo_min, + u32 fo_max, + u32* outa, + u32* outb) { + ASSERT(fo_curve < 0x29); + // undefined4 uVar1; + u32 uVar2; + int iVar3; + int iVar4; + u32 uVar5; + int iVar6; + int iVar7; + int iVar8; + u32 uVar9; + u32 uVar10; + + uVar10 = 0; + WaitSema(g_EarTransSema); + if (outa) { + *outa = 0; + } + if (outb) { + *outb = 0; + } + if (unktable[fo_curve + 0x84] != 0) { + SignalSema(g_EarTransSema); + return vol; + } + if (unktable[fo_curve + 0x58] != 0) { + trans = gEarTrans + 3; + } + switch (fo_curve) { + case 9: + case 0xb: + case 0x1c: + case 0x1e: + iVar8 = gEarTrans[3] - *trans; + iVar3 = gEarTrans[4] - trans[1]; + iVar7 = gEarTrans[5] - trans[2]; + uVar2 = 3; + if (outa) { + LAB_0000d094: + *outa = uVar2; + } + break; + case 10: + case 0x13: + case 0x1d: + case 0x26: + iVar8 = 0; + iVar3 = gEarTrans[1] - trans[1]; + iVar7 = 0; + if (outa) { + *outa = 2; + } + goto LAB_0000d0a4; + default: + iVar8 = gEarTrans[0] - *trans; + iVar7 = gEarTrans[2] - trans[2]; + iVar3 = gEarTrans[1] - trans[1]; + if (outa) { + uVar2 = 1; + goto LAB_0000d094; + } + } + if (iVar8 < 0) { + iVar8 = -iVar8; + } +LAB_0000d0a4: + if (iVar3 < 0) { + iVar3 = -iVar3; + } + if (iVar7 < 0) { + iVar7 = -iVar7; + } + fo_min = fo_min << 8; + fo_max = fo_max << 8; + uVar9 = 0; + iVar6 = iVar3; + if (iVar3 < iVar7) { + iVar6 = iVar7; + } + iVar4 = fo_max; + if (fo_max < iVar8) { + iVar4 = iVar8; + } + if (iVar4 < iVar6) { + iVar4 = iVar6; + } + while (0x7fff < iVar4) { + fo_max = fo_max >> 1; + fo_min = fo_min >> 1; + iVar8 = iVar8 >> 1; + iVar3 = iVar3 >> 1; + iVar7 = iVar7 >> 1; + uVar9 = uVar9 + 1; + iVar4 = iVar4 >> 1; + } + if (gCamScale != 0x10000) { + iVar8 = iVar8 * gCamScale >> 0x10; + iVar3 = iVar3 * gCamScale >> 0x10; + iVar7 = iVar7 * gCamScale >> 0x10; + if (0x10000 < gCamScale) { + iVar6 = iVar3; + if (iVar3 < iVar7) { + iVar6 = iVar7; + } + iVar4 = fo_max; + if (fo_max < iVar8) { + iVar4 = iVar8; + } + if (iVar4 < iVar6) { + iVar4 = iVar6; + } + while (0x7fff < iVar4) { + fo_max = fo_max >> 1; + fo_min = fo_min >> 1; + iVar8 = iVar8 >> 1; + iVar3 = iVar3 >> 1; + iVar7 = iVar7 >> 1; + uVar9 = uVar9 + 1; + iVar4 = iVar4 >> 1; + } + } + } + if ((outb) || (((iVar8 <= fo_max && (iVar3 <= fo_max)) && (iVar7 <= fo_max)))) { + uVar10 = iVar8 * iVar8 + iVar3 * iVar3 + iVar7 * iVar7; + iVar8 = 0; + if (uVar10 != 0) { + uVar5 = 0; + while ((uVar10 & 0xc0000000) == 0) { + uVar10 = uVar10 << 2; + uVar5 = uVar5 + 1; + } + iVar8 = (int)(u32)sqrt_table[uVar10 >> 0x18] >> (uVar5 & 0x1f); + } + if (outb) { + *outb = iVar8 << (uVar9 & 0x1f); + } + uVar10 = vol; + if ((fo_min < iVar8) && (uVar10 = 0, iVar8 < fo_max)) { + uVar10 = iVar8 - fo_min; + uVar9 = fo_max - fo_min; + while (0xffff < uVar10) { + uVar10 = uVar10 >> 1; + uVar9 = (int)uVar9 >> 1; + } + uVar5 = (uVar10 << 0x10) / uVar9; + if (uVar9 == 0) { + ASSERT_NOT_REACHED(); + } + uVar10 = vol; + if (uVar5 != 0x10000) { + uVar10 = uVar5 * uVar5 >> 0x10; + uVar10 = gCurves[fo_curve].c * uVar5 + gCurves[fo_curve].b * uVar10 + + gCurves[fo_curve].d * 0x10000 + + gCurves[fo_curve].a * (uVar10 * uVar5 >> 0x10) >> + 0xc; + if ((int)uVar10 < 0) { + uVar10 = 0; + } else { + if (0x10000 < uVar10) { + uVar10 = 0x10000; + } + } + uVar10 = (int)(uVar10 * vol) >> 0x10; + } + } + } + if ((fo_curve == 0xb) && (uVar10 < 0x180)) { + uVar10 = 0x180; + } + SignalSema(g_EarTransSema); + return uVar10; +} + +constexpr s16 unk_table_2[2056] = { + 0xB4, 0x0, 0xB4, 0x0, 0x5A, 0x5A, 0x10E, 0x10E, 0xB4, 0x0, 0xB4, 0x0, 0x5A, + 0x5A, 0x10E, 0x10E, 0xB4, 0x0, 0xB4, 0x0, 0x5A, 0x5A, 0x10E, 0x10E, 0xB4, 0x0, + 0xB4, 0x0, 0x5A, 0x5A, 0x10E, 0x10E, 0xB4, 0x0, 0xB4, 0x0, 0x5A, 0x5A, 0x10E, + 0x10E, 0xB3, 0x1, 0xB5, 0x167, 0x5B, 0x59, 0x10D, 0x10F, 0xB3, 0x1, 0xB5, 0x167, + 0x5B, 0x59, 0x10D, 0x10F, 0xB3, 0x1, 0xB5, 0x167, 0x5B, 0x59, 0x10D, 0x10F, 0xB3, + 0x1, 0xB5, 0x167, 0x5B, 0x59, 0x10D, 0x10F, 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, + 0x10C, 0x110, 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, 0x10C, 0x110, 0xB2, 0x2, 0xB6, + 0x166, 0x5C, 0x58, 0x10C, 0x110, 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, 0x10C, 0x110, + 0xB2, 0x2, 0xB6, 0x166, 0x5C, 0x58, 0x10C, 0x110, 0xB1, 0x3, 0xB7, 0x165, 0x5D, + 0x57, 0x10B, 0x111, 0xB1, 0x3, 0xB7, 0x165, 0x5D, 0x57, 0x10B, 0x111, 0xB1, 0x3, + 0xB7, 0x165, 0x5D, 0x57, 0x10B, 0x111, 0xB1, 0x3, 0xB7, 0x165, 0x5D, 0x57, 0x10B, + 0x111, 0xB0, 0x4, 0xB8, 0x164, 0x5E, 0x56, 0x10A, 0x112, 0xB0, 0x4, 0xB8, 0x164, + 0x5E, 0x56, 0x10A, 0x112, 0xB0, 0x4, 0xB8, 0x164, 0x5E, 0x56, 0x10A, 0x112, 0xB0, + 0x4, 0xB8, 0x164, 0x5E, 0x56, 0x10A, 0x112, 0xB0, 0x4, 0xB8, 0x164, 0x5E, 0x56, + 0x10A, 0x112, 0xAF, 0x5, 0xB9, 0x163, 0x5F, 0x55, 0x109, 0x113, 0xAF, 0x5, 0xB9, + 0x163, 0x5F, 0x55, 0x109, 0x113, 0xAF, 0x5, 0xB9, 0x163, 0x5F, 0x55, 0x109, 0x113, + 0xAF, 0x5, 0xB9, 0x163, 0x5F, 0x55, 0x109, 0x113, 0xAE, 0x6, 0xBA, 0x162, 0x60, + 0x54, 0x108, 0x114, 0xAE, 0x6, 0xBA, 0x162, 0x60, 0x54, 0x108, 0x114, 0xAE, 0x6, + 0xBA, 0x162, 0x60, 0x54, 0x108, 0x114, 0xAE, 0x6, 0xBA, 0x162, 0x60, 0x54, 0x108, + 0x114, 0xAE, 0x6, 0xBA, 0x162, 0x60, 0x54, 0x108, 0x114, 0xAD, 0x7, 0xBB, 0x161, + 0x61, 0x53, 0x107, 0x115, 0xAD, 0x7, 0xBB, 0x161, 0x61, 0x53, 0x107, 0x115, 0xAD, + 0x7, 0xBB, 0x161, 0x61, 0x53, 0x107, 0x115, 0xAD, 0x7, 0xBB, 0x161, 0x61, 0x53, + 0x107, 0x115, 0xAC, 0x8, 0xBC, 0x160, 0x62, 0x52, 0x106, 0x116, 0xAC, 0x8, 0xBC, + 0x160, 0x62, 0x52, 0x106, 0x116, 0xAC, 0x8, 0xBC, 0x160, 0x62, 0x52, 0x106, 0x116, + 0xAC, 0x8, 0xBC, 0x160, 0x62, 0x52, 0x106, 0x116, 0xAC, 0x8, 0xBC, 0x160, 0x62, + 0x52, 0x106, 0x116, 0xAB, 0x9, 0xBD, 0x15F, 0x63, 0x51, 0x105, 0x117, 0xAB, 0x9, + 0xBD, 0x15F, 0x63, 0x51, 0x105, 0x117, 0xAB, 0x9, 0xBD, 0x15F, 0x63, 0x51, 0x105, + 0x117, 0xAB, 0x9, 0xBD, 0x15F, 0x63, 0x51, 0x105, 0x117, 0xAB, 0x9, 0xBD, 0x15F, + 0x63, 0x51, 0x105, 0x117, 0xAA, 0xA, 0xBE, 0x15E, 0x64, 0x50, 0x104, 0x118, 0xAA, + 0xA, 0xBE, 0x15E, 0x64, 0x50, 0x104, 0x118, 0xAA, 0xA, 0xBE, 0x15E, 0x64, 0x50, + 0x104, 0x118, 0xAA, 0xA, 0xBE, 0x15E, 0x64, 0x50, 0x104, 0x118, 0xA9, 0xB, 0xBF, + 0x15D, 0x65, 0x4F, 0x103, 0x119, 0xA9, 0xB, 0xBF, 0x15D, 0x65, 0x4F, 0x103, 0x119, + 0xA9, 0xB, 0xBF, 0x15D, 0x65, 0x4F, 0x103, 0x119, 0xA9, 0xB, 0xBF, 0x15D, 0x65, + 0x4F, 0x103, 0x119, 0xA9, 0xB, 0xBF, 0x15D, 0x65, 0x4F, 0x103, 0x119, 0xA8, 0xC, + 0xC0, 0x15C, 0x66, 0x4E, 0x102, 0x11A, 0xA8, 0xC, 0xC0, 0x15C, 0x66, 0x4E, 0x102, + 0x11A, 0xA8, 0xC, 0xC0, 0x15C, 0x66, 0x4E, 0x102, 0x11A, 0xA8, 0xC, 0xC0, 0x15C, + 0x66, 0x4E, 0x102, 0x11A, 0xA8, 0xC, 0xC0, 0x15C, 0x66, 0x4E, 0x102, 0x11A, 0xA7, + 0xD, 0xC1, 0x15B, 0x67, 0x4D, 0x101, 0x11B, 0xA7, 0xD, 0xC1, 0x15B, 0x67, 0x4D, + 0x101, 0x11B, 0xA7, 0xD, 0xC1, 0x15B, 0x67, 0x4D, 0x101, 0x11B, 0xA7, 0xD, 0xC1, + 0x15B, 0x67, 0x4D, 0x101, 0x11B, 0xA6, 0xE, 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, + 0xA6, 0xE, 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, 0xA6, 0xE, 0xC2, 0x15A, 0x68, + 0x4C, 0x100, 0x11C, 0xA6, 0xE, 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, 0xA6, 0xE, + 0xC2, 0x15A, 0x68, 0x4C, 0x100, 0x11C, 0xA5, 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, + 0x11D, 0xA5, 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, 0x11D, 0xA5, 0xF, 0xC3, 0x159, + 0x69, 0x4B, 0xFF, 0x11D, 0xA5, 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, 0x11D, 0xA5, + 0xF, 0xC3, 0x159, 0x69, 0x4B, 0xFF, 0x11D, 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, + 0xFE, 0x11E, 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, 0xFE, 0x11E, 0xA4, 0x10, 0xC4, + 0x158, 0x6A, 0x4A, 0xFE, 0x11E, 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, 0xFE, 0x11E, + 0xA4, 0x10, 0xC4, 0x158, 0x6A, 0x4A, 0xFE, 0x11E, 0xA3, 0x11, 0xC5, 0x157, 0x6B, + 0x49, 0xFD, 0x11F, 0xA3, 0x11, 0xC5, 0x157, 0x6B, 0x49, 0xFD, 0x11F, 0xA3, 0x11, + 0xC5, 0x157, 0x6B, 0x49, 0xFD, 0x11F, 0xA3, 0x11, 0xC5, 0x157, 0x6B, 0x49, 0xFD, + 0x11F, 0xA3, 0x11, 0xC5, 0x157, 0x6B, 0x49, 0xFD, 0x11F, 0xA2, 0x12, 0xC6, 0x156, + 0x6C, 0x48, 0xFC, 0x120, 0xA2, 0x12, 0xC6, 0x156, 0x6C, 0x48, 0xFC, 0x120, 0xA2, + 0x12, 0xC6, 0x156, 0x6C, 0x48, 0xFC, 0x120, 0xA2, 0x12, 0xC6, 0x156, 0x6C, 0x48, + 0xFC, 0x120, 0xA2, 0x12, 0xC6, 0x156, 0x6C, 0x48, 0xFC, 0x120, 0xA1, 0x13, 0xC7, + 0x155, 0x6D, 0x47, 0xFB, 0x121, 0xA1, 0x13, 0xC7, 0x155, 0x6D, 0x47, 0xFB, 0x121, + 0xA1, 0x13, 0xC7, 0x155, 0x6D, 0x47, 0xFB, 0x121, 0xA1, 0x13, 0xC7, 0x155, 0x6D, + 0x47, 0xFB, 0x121, 0xA1, 0x13, 0xC7, 0x155, 0x6D, 0x47, 0xFB, 0x121, 0xA0, 0x14, + 0xC8, 0x154, 0x6E, 0x46, 0xFA, 0x122, 0xA0, 0x14, 0xC8, 0x154, 0x6E, 0x46, 0xFA, + 0x122, 0xA0, 0x14, 0xC8, 0x154, 0x6E, 0x46, 0xFA, 0x122, 0xA0, 0x14, 0xC8, 0x154, + 0x6E, 0x46, 0xFA, 0x122, 0xA0, 0x14, 0xC8, 0x154, 0x6E, 0x46, 0xFA, 0x122, 0x9F, + 0x15, 0xC9, 0x153, 0x6F, 0x45, 0xF9, 0x123, 0x9F, 0x15, 0xC9, 0x153, 0x6F, 0x45, + 0xF9, 0x123, 0x9F, 0x15, 0xC9, 0x153, 0x6F, 0x45, 0xF9, 0x123, 0x9F, 0x15, 0xC9, + 0x153, 0x6F, 0x45, 0xF9, 0x123, 0x9F, 0x15, 0xC9, 0x153, 0x6F, 0x45, 0xF9, 0x123, + 0x9E, 0x16, 0xCA, 0x152, 0x70, 0x44, 0xF8, 0x124, 0x9E, 0x16, 0xCA, 0x152, 0x70, + 0x44, 0xF8, 0x124, 0x9E, 0x16, 0xCA, 0x152, 0x70, 0x44, 0xF8, 0x124, 0x9E, 0x16, + 0xCA, 0x152, 0x70, 0x44, 0xF8, 0x124, 0x9E, 0x16, 0xCA, 0x152, 0x70, 0x44, 0xF8, + 0x124, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, + 0x71, 0x43, 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9D, + 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, + 0xF7, 0x125, 0x9D, 0x17, 0xCB, 0x151, 0x71, 0x43, 0xF7, 0x125, 0x9C, 0x18, 0xCC, + 0x150, 0x72, 0x42, 0xF6, 0x126, 0x9C, 0x18, 0xCC, 0x150, 0x72, 0x42, 0xF6, 0x126, + 0x9C, 0x18, 0xCC, 0x150, 0x72, 0x42, 0xF6, 0x126, 0x9C, 0x18, 0xCC, 0x150, 0x72, + 0x42, 0xF6, 0x126, 0x9C, 0x18, 0xCC, 0x150, 0x72, 0x42, 0xF6, 0x126, 0x9B, 0x19, + 0xCD, 0x14F, 0x73, 0x41, 0xF5, 0x127, 0x9B, 0x19, 0xCD, 0x14F, 0x73, 0x41, 0xF5, + 0x127, 0x9B, 0x19, 0xCD, 0x14F, 0x73, 0x41, 0xF5, 0x127, 0x9B, 0x19, 0xCD, 0x14F, + 0x73, 0x41, 0xF5, 0x127, 0x9B, 0x19, 0xCD, 0x14F, 0x73, 0x41, 0xF5, 0x127, 0x9A, + 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, + 0xF4, 0x128, 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x9A, 0x1A, 0xCE, + 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, + 0x9A, 0x1A, 0xCE, 0x14E, 0x74, 0x40, 0xF4, 0x128, 0x99, 0x1B, 0xCF, 0x14D, 0x75, + 0x3F, 0xF3, 0x129, 0x99, 0x1B, 0xCF, 0x14D, 0x75, 0x3F, 0xF3, 0x129, 0x99, 0x1B, + 0xCF, 0x14D, 0x75, 0x3F, 0xF3, 0x129, 0x99, 0x1B, 0xCF, 0x14D, 0x75, 0x3F, 0xF3, + 0x129, 0x99, 0x1B, 0xCF, 0x14D, 0x75, 0x3F, 0xF3, 0x129, 0x99, 0x1B, 0xCF, 0x14D, + 0x75, 0x3F, 0xF3, 0x129, 0x98, 0x1C, 0xD0, 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x98, + 0x1C, 0xD0, 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x98, 0x1C, 0xD0, 0x14C, 0x76, 0x3E, + 0xF2, 0x12A, 0x98, 0x1C, 0xD0, 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x98, 0x1C, 0xD0, + 0x14C, 0x76, 0x3E, 0xF2, 0x12A, 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, + 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, 0x97, 0x1D, 0xD1, 0x14B, 0x77, + 0x3D, 0xF1, 0x12B, 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, 0x97, 0x1D, + 0xD1, 0x14B, 0x77, 0x3D, 0xF1, 0x12B, 0x97, 0x1D, 0xD1, 0x14B, 0x77, 0x3D, 0xF1, + 0x12B, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, + 0x78, 0x3C, 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x96, + 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, + 0xF0, 0x12C, 0x96, 0x1E, 0xD2, 0x14A, 0x78, 0x3C, 0xF0, 0x12C, 0x95, 0x1F, 0xD3, + 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x95, 0x1F, 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, + 0x95, 0x1F, 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x95, 0x1F, 0xD3, 0x149, 0x79, + 0x3B, 0xEF, 0x12D, 0x95, 0x1F, 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x95, 0x1F, + 0xD3, 0x149, 0x79, 0x3B, 0xEF, 0x12D, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, + 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, + 0x7A, 0x3A, 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x94, + 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, + 0xEE, 0x12E, 0x94, 0x20, 0xD4, 0x148, 0x7A, 0x3A, 0xEE, 0x12E, 0x93, 0x21, 0xD5, + 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x93, 0x21, 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, + 0x93, 0x21, 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x93, 0x21, 0xD5, 0x147, 0x7B, + 0x39, 0xED, 0x12F, 0x93, 0x21, 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x93, 0x21, + 0xD5, 0x147, 0x7B, 0x39, 0xED, 0x12F, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, + 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, + 0x7C, 0x38, 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x92, + 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, + 0xEC, 0x130, 0x92, 0x22, 0xD6, 0x146, 0x7C, 0x38, 0xEC, 0x130, 0x91, 0x23, 0xD7, + 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, + 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, + 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, + 0xD7, 0x145, 0x7D, 0x37, 0xEB, 0x131, 0x91, 0x23, 0xD7, 0x145, 0x7D, 0x37, 0xEB, + 0x131, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, + 0x7E, 0x36, 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x90, + 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, + 0xEA, 0x132, 0x90, 0x24, 0xD8, 0x144, 0x7E, 0x36, 0xEA, 0x132, 0x8F, 0x25, 0xD9, + 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, + 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, + 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, + 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, + 0x133, 0x8F, 0x25, 0xD9, 0x143, 0x7F, 0x35, 0xE9, 0x133, 0x8E, 0x26, 0xDA, 0x142, + 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, + 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, + 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, + 0x142, 0x80, 0x34, 0xE8, 0x134, 0x8E, 0x26, 0xDA, 0x142, 0x80, 0x34, 0xE8, 0x134, + 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, + 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, + 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, + 0x135, 0x8D, 0x27, 0xDB, 0x141, 0x81, 0x33, 0xE7, 0x135, 0x8D, 0x27, 0xDB, 0x141, + 0x81, 0x33, 0xE7, 0x135, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, + 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, + 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, + 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, + 0x8C, 0x28, 0xDC, 0x140, 0x82, 0x32, 0xE6, 0x136, 0x8C, 0x28, 0xDC, 0x140, 0x82, + 0x32, 0xE6, 0x136, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, + 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, + 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, + 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, + 0x29, 0xDD, 0x13F, 0x83, 0x31, 0xE5, 0x137, 0x8B, 0x29, 0xDD, 0x13F, 0x83, 0x31, + 0xE5, 0x137, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, + 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, + 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, + 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, + 0xDE, 0x13E, 0x84, 0x30, 0xE4, 0x138, 0x8A, 0x2A, 0xDE, 0x13E, 0x84, 0x30, 0xE4, + 0x138, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, + 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, + 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, + 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, + 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, + 0x89, 0x2B, 0xDF, 0x13D, 0x85, 0x2F, 0xE3, 0x139, 0x88, 0x2C, 0xE0, 0x13C, 0x86, + 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, + 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, + 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, + 0x86, 0x2E, 0xE2, 0x13A, 0x88, 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x88, + 0x2C, 0xE0, 0x13C, 0x86, 0x2E, 0xE2, 0x13A, 0x87, 0x2D, 0xE1, 0x13B, 0x87, 0x2D, + 0xE1, 0x13B, +}; + +s32 CalculateAngle(s32* trans, u32 fo_curve, u32 param_3) { + u32 uVar2; + int iVar3; + u32 uVar4; + u32 uVar5; + int iVar6; + int iVar7; + u32 uVar8; + ASSERT(fo_curve < 0x29); + WaitSema(g_EarTransSema); + if (unktable[fo_curve] != 0) { + if (unktable[fo_curve + 0x2c] == 0) { + if (unktable[fo_curve + 0x58] != 0) { + trans = gEarTrans + 3; + } + iVar6 = trans[1]; + iVar3 = gCamTrans[0] - *trans; + iVar7 = gCamTrans[2] - trans[2]; + } else { + iVar7 = gCamForward[0] - gCamTrans[0]; + iVar3 = gCamTrans[2] - gCamForward[2]; + iVar6 = gCamForward[1]; + } + iVar6 = gCamTrans[1] - iVar6; + if (((iVar3 + 0x200168U | iVar6 + 0x200168U | iVar7 + 0x200168U) & 0xffc00000) != 0) { + if (iVar3 < 0) { + iVar3 = iVar3 + 0x3ff; + } + iVar3 = iVar3 >> 10; + if (iVar6 < 0) { + iVar6 = iVar6 + 0x3ff; + } + iVar6 = iVar6 >> 10; + if (iVar7 < 0) { + iVar7 = iVar7 + 0x3ff; + } + iVar7 = iVar7 >> 10; + } + uVar8 = iVar3 * gCamLeft[0] + iVar6 * gCamLeft[1] + iVar7 * gCamLeft[2]; + uVar5 = uVar8; + if ((int)uVar8 < 0) { + uVar5 = -uVar8; + } + uVar2 = iVar3 * gCamForward[0] + iVar6 * gCamForward[1] + iVar7 * gCamForward[2]; + uVar4 = uVar2; + if ((int)uVar2 < 0) { + uVar4 = -uVar2; + } + if ((0x1ffff < (int)uVar5) || (0x1ffff < (int)uVar4)) { + uVar4 = (int)uVar4 >> 8; + uVar5 = (int)uVar5 >> 8; + } + if ((uVar4 != 0) || (uVar5 != 0)) { + uVar8 = (uVar8 & 0x80000000) >> 0x1e | uVar2 >> 0x1f; + if ((int)uVar4 < (int)uVar5) { + if (uVar5 == 0) { + ASSERT_NOT_REACHED(); + } + uVar8 = uVar8 | (int)(uVar4 << 8) / (int)uVar5 << 3 | 4; + } else { + if (uVar4 == 0) { + ASSERT_NOT_REACHED(); + } + uVar8 = uVar8 | (int)(uVar5 << 8) / (int)uVar4 << 3; + } + ASSERT(uVar8 < 2056); + iVar3 = (int)(short)unk_table_2[uVar8]; + iVar6 = iVar3; + if (((param_3 != 0) && (iVar6 = iVar3, g_CameraInvert != 0)) && (iVar6 = 0, iVar3 != 0)) { + iVar6 = 0x168 - iVar3; + } + SignalSema(g_EarTransSema); + return iVar6; + } + } + SignalSema(g_EarTransSema); + return 0; +} + +s32 GetVolume(SoundInfo* sound) { + return CalculateFalloffVolume(sound->params.trans, sound->params.volume, sound->params.fo_curve, + sound->params.fo_min, sound->params.fo_max, nullptr, nullptr); +} + +s32 GetPan(SoundInfo* sound) { + return CalculateAngle(sound->params.trans, sound->params.fo_curve, 1); +} + +void UpdateLocation(SoundInfo* sound) { + auto handle = snd_SoundIsStillPlaying(sound->sound_handle); + sound->sound_handle = handle; + if (handle == 0) { + sound->id = 0; + } else { + auto vol = GetVolume(sound); + if (vol == 0 && unktable[(int)(sound->params).fo_curve + 0xb0] == 0) { + snd_StopSound(sound->sound_handle); + } else { + auto pan = GetPan(sound); + // ovrld_log(LogCategory::WARN, "HACK: falling back to old version of setting vol/pan"); + snd_SetSoundVolPan(handle, vol, pan); + + // FUN_00013e0c(handle,4,0,pan,0,0); + // if ((short)(sound->params).mask < 0) { + // FUN_00013d6c(handle,vol,0x40); + // } + // else { + // snd_SetSoundVolPan(handle,vol,0xfffffffe,4); + // } + } + } +} + +void UpdateAutoVol(SoundInfo* snd, int time) { + bool bVar1; + auto iVar6 = snd->auto_time; + auto iVar4 = snd->new_volume; + if (time < iVar6) { + auto iVar5 = iVar4; + if (iVar4 == -4) { + iVar5 = 0; + } + auto vol = (snd->params).volume; + int new_vol; + if (iVar6 == 0) { + ASSERT_NOT_REACHED(); + } + iVar5 = ((iVar5 - vol) * time) / iVar6; + if (iVar5 < 0) { + new_vol = vol + iVar5; + bVar1 = new_vol < iVar4; + } else { + new_vol = vol + iVar5; + if (iVar5 < 1) { + new_vol = vol + 1; + } + bVar1 = iVar4 < new_vol; + } + (snd->params).volume = new_vol; + if (bVar1) { + (snd->params).volume = iVar4; + } + snd->auto_time = iVar6 - time; + } else { + if (iVar4 == -4) { + snd_StopSound(snd->sound_handle); + snd->id = 0; + } else { + (snd->params).volume = iVar4; + } + snd->auto_time = 0; + } +} + +void UpdateVolume(SoundInfo* sound) { + auto handle = snd_SoundIsStillPlaying(sound->sound_handle); + sound->sound_handle = handle; + if (handle == 0) { + sound->id = 0; + } else { + if ((s16)(sound->params).mask < 0) { + // idk + snd_SetSoundVolPan(handle, GetVolume(sound), -2); + + // FUN_00013d6c(handle, GetVolume(sound), 0x40, 4); + } else { + snd_SetSoundVolPan(handle, GetVolume(sound), -2); + } + } +} + +void SetEarTrans(const s32* ear_trans0, + const s32* ear_trans1, + const s32* cam_trans, + const s32* cam_fwd, + const s32* cam_left, + s32 cam_scale, + bool cam_inverted) { + auto tick = snd_GetTick(); + auto time = tick - gLastTick; + gLastTick = tick; + WaitSema(g_EarTransSema); + gEarTrans[0] = *ear_trans0; + g_CameraInvert = cam_inverted; + gEarTrans[1] = ear_trans0[1]; + gEarTrans[2] = ear_trans0[2]; + gEarTrans[3] = *ear_trans1; + gEarTrans[4] = ear_trans1[1]; + gEarTrans[5] = ear_trans1[2]; + gCamTrans[0] = *cam_trans; + gCamTrans[1] = cam_trans[1]; + gCamTrans[2] = cam_trans[2]; + gCamForward[0] = *cam_fwd; + gCamForward[1] = cam_fwd[1]; + gCamForward[2] = cam_fwd[2]; + gCamLeft[0] = *cam_left; + gCamLeft[1] = cam_left[1]; + gCamLeft[2] = cam_left[2]; + gCamScale = cam_scale; + SignalSema(g_EarTransSema); + + for (auto& sound : gSounds) { + if (sound.id) { + if (sound.auto_time) { + UpdateAutoVol(&sound, time); + } + UpdateLocation(&sound); + } + } + + auto* cmd = g_aVagCmds; + s32 iVar2 = 5; + do { + if ((cmd->music_flag == 0) && (cmd->maybe_sound_handler != 0)) { + if ((cmd->flags.scanned == 0) || (cmd->flags.bit8 != 0)) { + if (cmd->flags.bit20 == 0) { + if ((u32)cmd->play_volume < 0x11) { + cmd->play_volume = 0; + } else { + cmd->play_volume = cmd->play_volume - 0x10; + } + SetVAGVol(cmd); + if (cmd->play_volume != 0) + goto LAB_0000db94; + } + LAB_0000db78: + StopVagStream(cmd); + } else { + time = snd_SoundIsStillPlaying(cmd->id); + if (time != 0) + goto LAB_0000db88; + if (cmd->flags.bit20 != 0) + goto LAB_0000db78; + // CpuSuspendIntr(local_28); + cmd->flags.bit8 = 1; + // CpuResumeIntr(local_28[0]); + } + } else { + LAB_0000db88: + SetVAGVol(cmd); + } + LAB_0000db94: + iVar2 = iVar2 + -1; + cmd = cmd + 1; + if (iVar2 < 0) { + return; + } + } while (true); +} + +void SetCurve(int param_1, + u32 param_2, + u32 param_3, + uint8_t param_4, + uint8_t param_5, + uint8_t param_6, + uint8_t param_7, + uint8_t param_8) { + gCurves[param_1].c = (param_3 - param_2) + -0x1000; + gCurves[param_1].d = 0x1000; + unktable[param_1 + 0xb0] = param_8; + gCurves[param_1].b = param_2 + param_3 * -3; + unktable[param_1] = param_4; + unktable[param_1 + 0x2c] = param_5; + unktable[param_1 + 0x58] = param_6; + unktable[param_1 + 0x84] = param_7; + gCurves[param_1].a = param_3 * 2; +} + +void SetPlaybackMode(s32 mode) { + g_nPlaybackMode = mode; + snd_SetPlayBackMode(mode); +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/ssound.h b/game/overlord/jak3/ssound.h new file mode 100644 index 0000000000..420fd65ac2 --- /dev/null +++ b/game/overlord/jak3/ssound.h @@ -0,0 +1,50 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/overlord/jak3/rpc_interface.h" + +namespace jak3 { +void jak3_overlord_init_globals_ssound(); +void InitSound(); + +extern s32 g_n989Semaphore; +extern bool g_bSoundEnable; + +struct SoundInfo { + SoundName name; + s32 id; + s32 sound_handle; + s32 new_volume; + s32 auto_time; + SoundPlayParams params; +}; + +struct VolumePair { + s16 left; + s16 right; +}; + +SoundInfo* LookupSound(s32 id); +SoundInfo* AllocateSound(); +int GetFalloffCurve(int fo_curve); +s32 GetVolume(SoundInfo* sound); +s32 GetPan(SoundInfo* sound); +void UpdateVolume(SoundInfo* sound); +void KillSoundsInGroup(u32 group); +void SetEarTrans(const s32* ear_trans0, + const s32* ear_trans1, + const s32* cam_trans, + const s32* cam_forward, + const s32* cam_left, + s32 cam_scale, + bool cam_inverted); +void SetPlaybackMode(s32 mode); +void SetCurve(int curve_idx, u32, u32, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t); +u32 CalculateFalloffVolume(s32* trans, u32 vol, u32 fo_curve, u32 fo_min, u32 fo_max, u32*, u32*); +s32 CalculateAngle(s32* trans, u32 fo_curve, u32); + +extern u32 g_anStreamVoice[6]; +extern VolumePair g_aPanTable[361]; +extern bool g_CameraInvert; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/stream.cpp b/game/overlord/jak3/stream.cpp new file mode 100644 index 0000000000..ef9ebde05a --- /dev/null +++ b/game/overlord/jak3/stream.cpp @@ -0,0 +1,282 @@ +#include "stream.h" + +#include "common/util/Assert.h" +#include "common/util/FileUtil.h" + +#include "game/overlord/jak3/iso_api.h" +#include "game/overlord/jak3/iso_cd.h" +#include "game/overlord/jak3/overlord.h" +#include "game/overlord/jak3/rpc_interface.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" + +namespace jak3 { + +using namespace iop; + +constexpr int kStrBufSize = sizeof(RPC_Str_Cmd); +static RPC_Str_Cmd sSTRBuf; + +constexpr int kNumPlayCmds = 4; +constexpr int kRpcBuf2Size = sizeof(RPC_Play_Cmd) * kNumPlayCmds; +static RPC_Play_Cmd sRPCBuf2[kNumPlayCmds]; + +constexpr int SECTOR_TABLE_SIZE = 512; + +struct StrFileHeader { + u32 sectors[SECTOR_TABLE_SIZE]; // start of chunk, in sectors. including this sector. + u32 sizes[SECTOR_TABLE_SIZE]; // size of chunk, in bytes. always an integer number of sectors +}; + +static_assert(sizeof(StrFileHeader) == 0x1000, "Sector header size"); + +struct CacheEntry { + ISOFileDef* filedef = nullptr; + s32 countdown = 0; + StrFileHeader header; +}; + +constexpr int STR_INDEX_CACHE_SIZE = 4; +CacheEntry sCache[STR_INDEX_CACHE_SIZE]; + +void jak3_overlord_init_globals_stream() {} + +u32 STRThread() { + sceSifQueueData dq; + sceSifServeData serve; + + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, RpcId::STR, RPC_STR, &sSTRBuf, kStrBufSize, nullptr, nullptr, &dq); + + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} + +u32 PLAYThread() { + sceSifQueueData dq; + sceSifServeData serve; + + CpuDisableIntr(); + sceSifInitRpc(0); + sceSifSetRpcQueue(&dq, GetThreadId()); + sceSifRegisterRpc(&serve, RpcId::PLAY, RPC_PLAY, &sRPCBuf2, kRpcBuf2Size, nullptr, nullptr, &dq); + + CpuEnableIntr(); + sceSifRpcLoop(&dq); + return 0; +} + +void* RPC_STR(unsigned int, void* msg_in, int size) { + auto* msg = (RPC_Str_Cmd*)msg_in; + ASSERT(size == sizeof(RPC_Str_Cmd)); + + if (msg->section < 0) { + ovrld_log(LogCategory::STR_RPC, "RPC_STR loading full file {}", msg->basename); + // not a stream file - treat it like a normal load + auto* filedef = get_file_system()->Find(msg->basename); + if (filedef) { + msg->maxlen = LoadISOFileToEE(filedef, msg->address, msg->maxlen); + if (msg->maxlen) { + msg->result = 0; + return msg; + } else { + ovrld_log(LogCategory::WARN, "Failed to LoadISOFileToEE in RPC_STR for {}", msg->basename); + } + } else { + ovrld_log(LogCategory::WARN, "Failed to open {} for RPC STR", msg->basename); + } + } else { + // this is an animation load. Convert name: + ISOName animation_iso_name; + file_util::ISONameFromAnimationName(animation_iso_name.data, msg->basename); + auto* filedef = get_file_system()->FindIN(&animation_iso_name); + ovrld_log(LogCategory::STR_RPC, "STR_RPC for {} chunk {}", msg->basename, msg->section); + + if (filedef) { + // found it! See if we've cached this animation's header. + int cache_entry = 0; + int oldest = INT32_MAX; + int oldest_idx = -1; + while (cache_entry < STR_INDEX_CACHE_SIZE && sCache[cache_entry].filedef != filedef) { + sCache[cache_entry].countdown--; + if (sCache[cache_entry].countdown < oldest) { + oldest_idx = cache_entry; + oldest = sCache[cache_entry].countdown; + } + cache_entry++; + } + + if (cache_entry == STR_INDEX_CACHE_SIZE) { + // cache miss, we need to load the header to the header cache on the IOP + ovrld_log(LogCategory::STR_RPC, + "STR_RPC header cache miss - loading .str file header now."); + cache_entry = oldest_idx; + sCache[oldest_idx].filedef = filedef; + sCache[oldest_idx].countdown = INT32_MAX - 1; + if (!LoadISOFileToIOP(filedef, (u8*)&sCache[oldest_idx].header, sizeof(StrFileHeader))) { + ovrld_log(LogCategory::WARN, "STR_RPC failed to load .str file header for {}", + msg->basename); + msg->result = 1; + return msg; + } + } + + // load data, using the cached header to find the location of the chunk. + if (!LoadISOFileChunkToEE(filedef, msg->address, + sCache[cache_entry].header.sizes[msg->section], + sCache[cache_entry].header.sectors[msg->section])) { + ovrld_log(LogCategory::WARN, "STR_RPC failed to load .str file chunk {} for {}", + msg->section, msg->basename); + msg->result = 1; + } else { + // successful load! + msg->maxlen = sCache[cache_entry].header.sizes[msg->section]; + msg->result = 0; + return msg; + } + } + } + msg->result = 1; + return msg; +} + +void* RPC_PLAY(unsigned int, void* msg_in, int size) { + static_assert(sizeof(RPC_Play_Cmd) == 256); + + if (size <= 0) { + return msg_in; + } + + auto* msg_array = (RPC_Play_Cmd*)msg_in; + + for (u32 msg_idx = 0; msg_idx < size / sizeof(RPC_Play_Cmd); msg_idx++) { + auto* msg = &msg_array[msg_idx]; + + // the operation is stashed in the "result" field of the message + switch (msg->result) { + case 1: { + // remove vag streams by name + for (int s = 0; s < 4; s++) { + VagStreamData vsd; + if (msg->names[s].chars[0] != 0) { + // lg::warn("RPC PLAY remove {}", msg->names[s].chars); + strncpy(vsd.name, msg->names[s].chars, 0x30); + vsd.id = msg->id[s]; + WaitSema(g_EEStreamsList.sema); + RemoveVagStreamFromList(&vsd, &g_EEStreamsList); + SignalSema(g_EEStreamsList.sema); + WaitSema(g_EEPlayList.sema); + RemoveVagStreamFromList(&vsd, &g_EEPlayList); + SignalSema(g_EEPlayList.sema); + } + } + } break; + case 2: { + // completely redefine the set of vag streams to queue up. + WaitSema(g_EEStreamsList.sema); // lock stream list + EmptyVagStreamList(&g_EEStreamsList); // clear all existing streams + + // the first stream has the highest priority. + int priority = 9; + for (int s = 0; s < 4; s++) { + if (msg->names[s].chars[0] && msg->id[s]) { + // lg::warn("RPC PLAY queue {}", msg->names[s].chars); + + // set up list entry for this stream + VagStreamData vsd; + strncpy(vsd.name, msg->names[s].chars, 0x30); + vsd.id = msg->id[s]; + vsd.art_load = msg->address & 1 << (s & 0x1f) & 0xf; + vsd.movie_art_load = msg->address & 0x10 << (s & 0x1f) & 0xf0; + vsd.sound_handler = 0; + vsd.priority = priority; + + // if we have an existing one, make sure it has the appropriate flags + auto* existing_vag = FindThisVagStream(vsd.name, vsd.id); + if (existing_vag) { + existing_vag->art_flag = (u32)(vsd.art_load != 0); + existing_vag->music_flag = 0; + existing_vag->movie_flag = (u32)(vsd.movie_art_load != 0); + if (vsd.art_load != 0) { + existing_vag->flags.art = 1; + } + if (existing_vag->movie_flag != 0) { + existing_vag->flags.movie = 1; + } + } + + // add to list + InsertVagStreamInList(&vsd, &g_EEStreamsList); + } + + if (priority == 8) { + priority = 2; + } else { + if (0 < priority) { + priority = priority + -1; + } + } + s = s + 1; + } + SignalSema(g_EEStreamsList.sema); + } break; + case 0: { + int priority = 9; + for (int s = 0; s < 4; s++) { + if (msg->names[s].chars[0] && msg->id[s]) { + // lg::warn("RPC PLAY play {}", msg->names[s].chars); + + VagStreamData vsd; + strncpy(vsd.name, msg->names[s].chars, 0x30); + vsd.id = msg->id[s]; + vsd.volume2 = msg->section; + vsd.group = msg->maxlen; + vsd.plugin_id = 0; + vsd.sound_handler = 0; + vsd.maybe_volume_3 = 0; + vsd.priority = priority; + auto* existing_vag = FindThisVagStream(msg->names[s].chars, vsd.id); + if (existing_vag != (ISO_VAGCommand*)0x0) { + existing_vag->play_volume = vsd.volume2; + existing_vag->play_group = vsd.group; + if (existing_vag->flags.running != 0) + goto LAB_000092a4; + } + WaitSema(g_EEPlayList.sema); + auto* already_playing = FindVagStreamInList(&vsd, &g_EEPlayList); + if (!already_playing) { + already_playing = InsertVagStreamInList(&vsd, &g_EEPlayList); + strncpy(already_playing->name, vsd.name, 0x30); + already_playing->id = vsd.id; + already_playing->priority = vsd.priority; + already_playing->sound_handler = vsd.sound_handler; + already_playing->plugin_id = vsd.plugin_id; + already_playing->unk1 = 0; + already_playing->art_load = 0; + already_playing->movie_art_load = 0; + } + SignalSema(g_EEPlayList.sema); + } else { + // lg::warn("RPC PLAY play (NONE)"); + } + LAB_000092a4: + if (priority == 8) { + priority = 2; + } else { + if (0 < priority) { + priority = priority + -1; + } + } + } + + } break; + } + } + + return msg_in; +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/stream.h b/game/overlord/jak3/stream.h new file mode 100644 index 0000000000..fe2beff37e --- /dev/null +++ b/game/overlord/jak3/stream.h @@ -0,0 +1,13 @@ +#pragma once + +#include "common/common_types.h" + +namespace jak3 { +void jak3_overlord_init_globals_stream(); + +u32 PLAYThread(); +u32 STRThread(); +void* RPC_STR(unsigned int fno, void* msg, int size); +void* RPC_PLAY(unsigned int fno, void* msg, int size); + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/streamlist.cpp b/game/overlord/jak3/streamlist.cpp new file mode 100644 index 0000000000..156b4a6c43 --- /dev/null +++ b/game/overlord/jak3/streamlist.cpp @@ -0,0 +1,339 @@ +#include "streamlist.h" + +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" + +namespace jak3 { +using namespace iop; +List g_RequestedStreamsList; +List g_NewStreamsList; +List g_EEStreamsList; +List g_EEPlayList; +void jak3_overlord_init_globals_streamlist() { + g_RequestedStreamsList = {}; + g_NewStreamsList = {}; + g_EEStreamsList = {}; + g_EEPlayList = {}; +} + +void InitVagStreamList(List* list, int size, const char* name) { + strncpy(list->name, name, 8); + + InitList(list, size, sizeof(VagStreamData)); + + auto* iter = list->next; + if (0 < size) { + do { + iter->in_use = 0; + strncpy(iter->name, "free", 0x30); + iter->group = 2; + iter->id = 0; + iter->sound_handler = 0; + iter->priority = 0; + iter->art_load = 0; + iter->movie_art_load = 0; + iter->unk2 = 0; + iter->unk1 = 0; + iter->volume2 = 0; + iter->maybe_volume_3 = 0; + iter = iter + 1; + size = size + -1; + } while (size != 0); + } + + ASSERT(list->buffer); +} + +VagStreamData* FindVagStreamInList(VagStreamData* stream, List* list) { + int iVar1; + VagStreamData* iter; + u32 max_idx; + u32 idx; + u32 uVar2; + VagStreamData* ret; + VagStreamData* pVVar3; + + max_idx = list->count; + iter = list->next; + ret = nullptr; + idx = 0; + if (max_idx != 0) { + do { + uVar2 = idx; + pVVar3 = ret; + if ((iter->id != stream->id) || (iVar1 = strncmp(iter->name, stream->name, 0x30), + uVar2 = max_idx, pVVar3 = iter, iVar1 == 0)) { + ret = pVVar3; + idx = uVar2; + } + idx = idx + 1; + iter = iter->next; + } while (idx < max_idx); + } + return ret; +} + +VagStreamData* GetVagStreamInList(u32 idx, List* list) { + VagStreamData* iter = nullptr; + if ((idx < (u32)list->count) && (iter = list->next, idx != 0)) { + do { + idx = idx - 1; + iter = iter->next; + } while (idx != 0); + } + return iter; +} + +void EmptyVagStreamList(List* list) { + VagStreamData* elt; + u32 i; + u32 cnt; + + cnt = list->count; + elt = (VagStreamData*)list->buffer; + i = 0; + if (cnt != 0) { + do { + i = i + 1; + strncpy(elt->name, "free", 0x30); + elt->group = 2; + elt->id = 0; + elt->sound_handler = 0; + elt->priority = 0; + elt->art_load = 0; + elt->movie_art_load = 0; + elt->unk2 = 0; + elt->unk1 = 0; + elt->volume2 = 0; + elt->maybe_volume_3 = 0; + elt->in_use = 0; + elt = elt + 1; + } while (i < cnt); + } + list->unk_flag = 1; +} + +void RemoveVagStreamFromList(VagStreamData* stream, List* list) { + VagStreamData* elt = FindVagStreamInList(stream, list); + if (elt) { + elt->in_use = 0; + strncpy(elt->name, "free", 0x30); + elt->group = 2; + elt->id = 0; + elt->priority = 0; + elt->art_load = 0; + elt->movie_art_load = 0; + elt->unk1 = 0; + elt->volume2 = 0; + elt->maybe_volume_3 = 0; + elt->sound_handler = 0; + list->unk_flag = 1; + elt->unk2 = 0; + } +} + +VagStreamData* InsertVagStreamInList(VagStreamData* user_stream, List* list) { + u32 uVar1; + VagStreamData* pVVar10; + VagStreamData* pVVar11; + VagStreamData* pVVar12; + + u32 count = list->count; + VagStreamData* free_elt = nullptr; + u32 free_elt_idx = 0; + VagStreamData* iter = list->next; + if (count != 0) { + do { + if (iter->id == 0) { + free_elt = iter; + free_elt_idx = count; + } + free_elt_idx = free_elt_idx + 1; + iter = iter->next; + } while (free_elt_idx < count); + } + if ((free_elt != (VagStreamData*)0x0) && + (uVar1 = 0, pVVar11 = list->next, pVVar12 = nullptr, count != 0)) { + do { + pVVar10 = pVVar11; + uVar1 = uVar1 + 1; + if (pVVar10->priority < user_stream->priority) { + list->unk_flag = 1; + free_elt->in_use = 1; + strncpy(free_elt->name, user_stream->name, 0x30); + free_elt->id = user_stream->id; + free_elt->plugin_id = user_stream->plugin_id; + free_elt->art_load = user_stream->art_load; + free_elt->movie_art_load = user_stream->movie_art_load; + free_elt->priority = user_stream->priority; + free_elt->sound_handler = user_stream->sound_handler; + free_elt->volume2 = user_stream->volume2; + free_elt->maybe_volume_3 = user_stream->maybe_volume_3; + free_elt->group = user_stream->group; + free_elt->unk1 = 0; + if (pVVar12 == (VagStreamData*)0x0) { + if (free_elt == pVVar10) { + return free_elt; + } + auto* prev = free_elt->next; + auto* next = free_elt->prev; + list->next = free_elt; + prev->prev = next; + pVVar11 = free_elt->prev; + pVVar10->prev = free_elt; + pVVar11->next = prev; + free_elt->prev = nullptr; + free_elt->next = pVVar10; + return free_elt; + } + if (free_elt == pVVar10) { + return free_elt; + } + auto* pVVar13 = free_elt->prev; + pVVar13->next = free_elt->next; + pVVar11 = pVVar12->next; + free_elt->next->prev = pVVar13; + free_elt->next = pVVar11; + pVVar10->prev = free_elt; + pVVar12->next = free_elt; + free_elt->prev = pVVar12; + return free_elt; + } + pVVar11 = pVVar10->next; + pVVar12 = pVVar10; + } while (uVar1 < count); + } + return free_elt; +} + +void MergeVagStreamLists(List* list_a, List* list_b) { + VagStreamData* stream; + VagStreamData* pVVar1; + u32 uVar2; + u32 idx; + + idx = 0; + uVar2 = 0; +LAB_0000fde8: + do { + stream = GetVagStreamInList(idx, list_a); + idx = idx + 1; + if (stream != (VagStreamData*)0x0) { + if (stream->id == 0) + goto LAB_0000fde8; + pVVar1 = FindVagStreamInList(stream, list_b); + if (pVVar1 == (VagStreamData*)0x0) { + InsertVagStreamInList(stream, list_b); + } + } + uVar2 = uVar2 + 1; + if (3 < uVar2) { + return; + } + } while (true); +} + +void QueueNewStreamsFromList(List* list) { + VagStreamData* stream; + ISO_VAGCommand* pIVar1; + u32 uVar2; + u32 idx; + + SetVagStreamsNotScanned(); + idx = 0; + EmptyVagStreamList(&g_NewStreamsList); + g_NewStreamsList.unk_flag = 0; + uVar2 = 0; +LAB_0000fe94: + do { + stream = GetVagStreamInList(idx, list); + idx = idx + 1; + if (stream == (VagStreamData*)0x0) { + uVar2 = 4; + } else { + if (stream->id == 0) + goto LAB_0000fe94; + pIVar1 = FindThisVagStream(stream->name, stream->id); + if (pIVar1 == (ISO_VAGCommand*)0x0) { + pIVar1 = FindThisVagStream(stream->name, stream->id); + if (pIVar1 == (ISO_VAGCommand*)0x0) { + InsertVagStreamInList(stream, &g_NewStreamsList); + } + } else { + pIVar1->flags.scanned = 1; + if (pIVar1->stereo_sibling != (ISO_VAGCommand*)0x0) { + pIVar1->stereo_sibling->flags.scanned = 1; + } + if (stream->priority != pIVar1->priority_pq) { + SetNewVagCmdPri(pIVar1, stream->priority); + } + } + } + uVar2 = uVar2 + 1; + if (3 < uVar2) { + return; + } + } while (true); +} + +void CheckPlayList(List* list) { + int count; + ISO_VAGCommand* cmd; + VagStreamData* iter; + + count = list->count; + iter = list->next; +joined_r0x0000ff80: + do { + while (true) { + if (count == 0) { + return; + } + count = count + -1; + if (iter->id != 0) + break; + iter = iter->next; + } + cmd = FindThisVagStream(iter->name, iter->id); + } while (cmd == (ISO_VAGCommand*)0x0); + if (cmd->flags.running == 0) + goto code_r0x0000ffc4; + goto LAB_00010004; +code_r0x0000ffc4: + if (((cmd->flags.saw_chunks1 != 0) || (cmd->flags.file_disappeared != 0)) && + (cmd->flags.nostart == 0)) { + IsoPlayVagStream(cmd); + LAB_00010004: + RemoveVagStreamFromList(iter, list); + } + goto joined_r0x0000ff80; +} + +void StreamListThread() { + if (g_RequestedStreamsList.pending_data == 0) { + WaitSema(g_RequestedStreamsList.sema); + EmptyVagStreamList(&g_RequestedStreamsList); + g_RequestedStreamsList.unk_flag = 0; + // WaitSema(DAT_00015dd0); + // MergeVagStreamLists((List*)&g_PluginStreamsList, &g_RequestedStreamsList); + // SignalSema(DAT_00015dd0); + WaitSema(g_EEStreamsList.sema); + MergeVagStreamLists(&g_EEStreamsList, &g_RequestedStreamsList); + SignalSema(g_EEStreamsList.sema); + g_RequestedStreamsList.pending_data = 1; + SignalSema(g_RequestedStreamsList.sema); + WaitSema(g_EEPlayList.sema); + CheckPlayList(&g_EEPlayList); + SignalSema(g_EEPlayList.sema); + // WaitSema(DAT_0001e31c); + // CheckLfoList(&g_LfoStreamsList); + // SignalSema(DAT_0001e31c); + } +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/streamlist.h b/game/overlord/jak3/streamlist.h new file mode 100644 index 0000000000..6acddb3b0c --- /dev/null +++ b/game/overlord/jak3/streamlist.h @@ -0,0 +1,22 @@ +#pragma once + +#include "game/overlord/jak3/list.h" + +namespace jak3 { +void jak3_overlord_init_globals_streamlist(); + +struct ISO_VAGCommand; + +extern List g_RequestedStreamsList; +extern List g_NewStreamsList; +extern List g_EEStreamsList; +extern List g_EEPlayList; + +void QueueNewStreamsFromList(List* list); +void RemoveVagStreamFromList(VagStreamData* entry, List* list); +void EmptyVagStreamList(List* list); +VagStreamData* InsertVagStreamInList(VagStreamData* entry, List* list); +VagStreamData* FindVagStreamInList(VagStreamData* entry, List* list); +void InitVagStreamList(List* list, int size, const char* name); +void StreamListThread(); +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/todo.txt b/game/overlord/jak3/todo.txt new file mode 100644 index 0000000000..68871518bb --- /dev/null +++ b/game/overlord/jak3/todo.txt @@ -0,0 +1,6 @@ +- pan stuff is wrong - it's now using a table from 989snd +- Check ssound.cpp for most of these issues, and bottom of vag.cpp +- in some cases, we're hitting different playback modes? see comment dolby crap, we hit this when going outside freedom hq +- changing file size +- UpdateVolume +- goal src update diff --git a/game/overlord/jak3/vag.cpp b/game/overlord/jak3/vag.cpp new file mode 100644 index 0000000000..6bcaeb2ba4 --- /dev/null +++ b/game/overlord/jak3/vag.cpp @@ -0,0 +1,1042 @@ +#include "vag.h" + +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/basefile.h" +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/iso_queue.h" +#include "game/overlord/jak3/spustreams.h" +#include "game/overlord/jak3/srpc.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/sce/iop.h" +#include "game/sound/sdshim.h" + +#define VOICE_BIT(voice) (1 << ((voice) >> 1)) + +namespace jak3 { +using namespace iop; + +bool g_bExtPause = false; +bool g_bExtResume = false; +ISO_VAGCommand g_aVagCmds[6]; +int g_anMasterVolume[32]; +bool voice_key_flags[0x30]; +u32 voice_key_times[0x30]; +u32 g_nTimeOfLastVoiceKey = 0; +bool g_bRecentlyKeyedVoice = false; +u32 g_nActiveVagStreams = 0; +u32 g_nPlaybackMode = 2; + +struct VagCmdPriListEntry { + ISO_VAGCommand* cmds[6]; +}; + +VagCmdPriListEntry g_aapVagCmdsPriList[10]; +u32 g_anVagCmdPriCounter[10]; + +void jak3_overlord_init_globals_vag() { + g_bExtPause = false; + g_bExtResume = false; + for (auto& c : g_aVagCmds) { + c = {}; + } + for (auto& x : g_anMasterVolume) { + x = 0x400; + } + g_nActiveVagStreams = 0; + g_nPlaybackMode = 2; +} + +void ISO_VAGCommand::set_all_flags_zero() { + flags.bit0 = 0; + flags.saw_chunks1 = 0; + flags.paused = 0; + flags.bit3 = 0; + + flags.running = 0; + flags.clocks_set = 0; + flags.file_disappeared = 0; + flags.scanned = 0; + + flags.bit8 = 0; + flags.stop = 0; + flags.art = 0; + flags.stereo_secondary = 0; + flags.bit12 = 0; + flags.bit13 = 0; + flags.bit14 = 0; + flags.bit15 = 0; + flags.bit16 = 0; + flags.bit17 = 0; + flags.dma_complete_even_chunk_count = 0; + flags.dma_complete_odd_chunk_count = 0; + flags.bit20 = 0; + flags.bit21 = 0; + flags.bit22 = 0; + flags.nostart = 0; + flags.movie = 0; + flags.bit25 = 0; +} + +u32 ISO_VAGCommand::pack_flags() { + u32 ret = 0; + u8* ptr = &flags.bit0; + u32 mask = 1; + for (; ptr <= &flags.bit25; ptr++) { + if (*ptr) { + ret |= mask; + } + mask <<= 1; + } + return ret; +} + +void InitVAGCmd(ISO_VAGCommand* cmd, int paused) { + set_active_a(cmd, 0); + set_active_b(cmd, 0); + set_active_c(cmd, 0); + cmd->set_all_flags_zero(); + cmd->unk_gvsp_state2 = 0; + cmd->lfo_callback = nullptr; + if (paused == 0) { + cmd->flags.paused = 0; + } else { + cmd->flags.paused = 1; + } + cmd->safe_to_modify_dma = 1; + cmd->unk_spu_mem_offset = 0x4000; + cmd->callback = NullCallback; + cmd->dma_chan = -1; + cmd->fo_min = 5; + cmd->fo_max = 0x1e; + cmd->unk_gvsp_len = 0; + cmd->position_for_ee = 0; + cmd->unk_gvsp_cntr = 0; + cmd->clocka = 0; + cmd->clockb = 0; + cmd->clockc = 0; + cmd->clockd = 0; + cmd->flags.clocks_set = 0; + cmd->flags.file_disappeared = 0; + cmd->num_isobuffered_chunks = 0; + cmd->xfer_size = 0; + cmd->vag_file_rate = 0; + cmd->error = 0; + cmd->unk_gvsp_flag = 0; + cmd->m_pBaseFile = nullptr; + cmd->stereo_sibling = nullptr; + cmd->dma_iop_mem_ptr = nullptr; + cmd->pitch_cmd = 0; + cmd->updated_trans = 0; + cmd->trans[0] = 0; + cmd->trans[1] = 0; + cmd->trans[2] = 0; + cmd->fo_curve = GetFalloffCurve(1); + cmd->play_group = 2; + set_active_a(cmd, 1); + set_active_b(cmd, 1); +} + +void InitVagCmds() { + auto now = GetSystemTimeLow(); + for (auto& x : voice_key_flags) { + x = false; + } + for (auto& x : voice_key_times) { + x = now; + } + g_nTimeOfLastVoiceKey = 0; + g_bRecentlyKeyedVoice = false; + + for (int i = 0; i < 6; i++) { + auto* cmd = &g_aVagCmds[i]; + InitVAGCmd(cmd, 1); + cmd->info_idx = i; + cmd->stream_sram = g_auStrmSRAM[i]; + cmd->trap_sram = g_auTrapSRAM[i]; + cmd->voice = g_anStreamVoice[i]; + cmd->music_flag = i >= 4; + cmd->file_def = nullptr; + cmd->vag_file_def = nullptr; + cmd->vag_dir_entry = nullptr; + cmd->flags.saw_chunks1 = 0; + cmd->play_volume = 0; + cmd->pitch_cmd = 0; + cmd->id = 0; + cmd->plugin_id = 0; + cmd->maybe_sound_handler = 0; + cmd->oog = 0; + cmd->dolby_pan_angle = 0; + cmd->priority_pq = 0; + cmd->art_flag = 0; + cmd->movie_flag = 0; + } + + for (auto& level : g_aapVagCmdsPriList) { + for (auto& cmd : level.cmds) { + cmd = nullptr; + } + } + for (auto& x : g_anVagCmdPriCounter) { + x = 0; + } + // oops - wrong value from jak 2 maybe? + g_anVagCmdPriCounter[0] = 4; +} + +void RemoveVagCmd(ISO_VAGCommand* cmd) { + ASSERT(cmd); + ASSERT(!cmd->music_flag); + ASSERT(cmd->info_idx < 4); + + g_aapVagCmdsPriList[cmd->priority_pq].cmds[cmd->info_idx] = nullptr; + if (g_anVagCmdPriCounter[cmd->priority_pq]) { + g_anVagCmdPriCounter[cmd->priority_pq]--; + } else { + ASSERT_NOT_REACHED(); + } + g_anVagCmdPriCounter[0]++; +} + +ISO_VAGCommand* FindFreeVagCmd() { + for (int i = 0; i < 4; i++) { + if (g_aVagCmds[i].id == 0) { + return &g_aVagCmds[i]; + } + } + return nullptr; +} + +void SetNewVagCmdPri(ISO_VAGCommand* cmd, int pri) { + ASSERT(cmd); + ASSERT(!cmd->music_flag); + ASSERT(cmd->info_idx < 4); + auto old_pri = cmd->priority_pq; + g_aapVagCmdsPriList[old_pri].cmds[cmd->info_idx] = nullptr; + if (0 < g_anVagCmdPriCounter[old_pri]) { + g_anVagCmdPriCounter[old_pri]--; + } else { + ASSERT_NOT_REACHED(); + } + g_anVagCmdPriCounter[pri]++; + cmd->priority_pq = pri; + g_aapVagCmdsPriList[pri].cmds[cmd->info_idx] = cmd; +} + +int HowManyBelowThisPriority(int max_pri) { + int count = 0; + for (int i = 0; i < max_pri; i++) { + count += g_anVagCmdPriCounter[i]; + } + return count; +} + +/*! + * The "smart"ness of this function is less than advertised. + */ +ISO_VAGCommand* SmartAllocMusicVagCommand(const ISO_VAGCommand* user_command, int flag) { + ASSERT(user_command); + int idx = flag ? 5 : 4; + auto* cmd = &g_aVagCmds[idx]; + if (cmd->id == 0) { + g_nActiveMusicStreams++; + } + return cmd; +} + +ISO_VAGCommand* SmartAllocVagCmd(ISO_VAGCommand* user_cmd) { + ASSERT(user_cmd); + ISO_VAGCommand* cmd = FindFreeVagCmd(); + if (!cmd && (cmd = FindNotQueuedVagCmd(), !cmd)) { + u32 pri = 0; + if ((user_cmd->priority_pq != 0) && (pri = 0, user_cmd->priority_pq != 0)) { + do { + u32 cmd_idx = 0; + do { + auto* pIVar1 = g_aapVagCmdsPriList[pri].cmds[cmd_idx]; + if (pIVar1 && (cmd = pIVar1, pIVar1->flags.stereo_secondary == 0)) { + pri = user_cmd->priority_pq; + cmd_idx = 4; + cmd = pIVar1; + } + cmd_idx = cmd_idx + 1; + } while (cmd_idx < 4); + pri = pri + 1; + } while (pri < (u32)user_cmd->priority_pq); + } + if (!cmd) { + return nullptr; + } + } + g_nActiveVagStreams = g_nActiveVagStreams + 1; + return cmd; +} + +void FreeVagCmd(ISO_VAGCommand* cmd) { + set_active_a(cmd, 0); + set_active_b(cmd, 0); + set_active_c(cmd, 0); + cmd->set_all_flags_zero(); + + cmd->flags.paused = 1; + cmd->flags.saw_chunks1 = 0; + cmd->flags.scanned = 0; + cmd->clocka = 0; + cmd->clockb = 0; + cmd->clockc = 0; + cmd->clockd = 0; + SetVagStreamName(cmd, 0); + cmd->safe_to_modify_dma = 1; + cmd->unk_spu_mem_offset = 0x4000; + cmd->callback = NullCallback; + cmd->dma_chan = -1; + cmd->lfo_callback = 0; + cmd->pitch1 = 0; + cmd->pitch1_file = 0; + cmd->file_def = nullptr; + cmd->vag_file_def = nullptr; + cmd->vag_dir_entry = nullptr; + cmd->unk_gvsp_len = 0; + cmd->position_for_ee = 0; + cmd->unk_gvsp_cntr = 0; + cmd->name[0] = 0; + cmd->num_isobuffered_chunks = 0; + cmd->xfer_size = 0; + cmd->vag_file_rate = 0; + cmd->error = 0; + cmd->unk_gvsp_flag = 0; + cmd->play_volume = 0; + cmd->pitch_cmd = 0; + cmd->id = 0; + cmd->plugin_id = 0; + cmd->maybe_sound_handler = 0; + cmd->priority_pq = 0; + cmd->art_flag = 0; + cmd->movie_flag = 0; + cmd->updated_trans = 0; + cmd->m_pBaseFile = nullptr; + cmd->dma_iop_mem_ptr = nullptr; + cmd->unk_gvsp_state2 = 0; + if (!cmd->music_flag) { + if (0 < g_nActiveVagStreams) { + g_nActiveVagStreams = g_nActiveVagStreams + -1; + } else { + ASSERT_NOT_REACHED(); + } + } else { + if (0 < g_nActiveMusicStreams) { + g_nActiveMusicStreams = g_nActiveMusicStreams + -1; + } else { + ASSERT_NOT_REACHED(); + } + } + // CpuResumeIntr(local_10[0]); +} + +void SetVagStreamsNotScanned() { + for (int i = 0; i < 4; i++) { + g_aVagCmds[i].flags.scanned = false; + } +} + +void SetVagStreamsNoStart(int value) { + for (int i = 0; i < 4; i++) { + g_aVagCmds[i].flags.nostart = value; + } +} + +ISO_VAGCommand* FindNotQueuedVagCmd() { + for (int i = 0; i < 4; i++) { + auto* cmd = &g_aVagCmds[i]; + if (!cmd->flags.scanned && !cmd->flags.stereo_secondary && !cmd->flags.running) { + return cmd; + } + } + return nullptr; +} + +int AnyVagRunning() { + int count = 0; + for (int i = 0; i < 4; i++) { + if (g_aVagCmds[i].flags.running) { + count++; + } + } + return count; +} + +ISO_VAGCommand* FindVagStreamPluginId(int id) { + if (id == 0) { + return nullptr; + } + for (int i = 0; i < 4; i++) { + auto* cmd = &g_aVagCmds[i]; + if (cmd->plugin_id == id) { + return cmd; + } + } + return nullptr; +} + +ISO_VAGCommand* FindVagStreamId(int id) { + if (id == 0) { + return nullptr; + } + for (int i = 0; i < 4; i++) { + auto* cmd = &g_aVagCmds[i]; + if (cmd->id == id) { + return cmd; + } + } + return nullptr; +} + +ISO_VAGCommand* FindVagStreamName(const char* name) { + for (int i = 0; i < 4; i++) { + auto* cmd = &g_aVagCmds[i]; + if (strcmp(cmd->name, name) == 0) { + return cmd; + } + } + return nullptr; +} + +ISO_VAGCommand* FindThisVagStream(const char* name, int id) { + for (int i = 0; i < 4; i++) { + auto* cmd = &g_aVagCmds[i]; + if (strcmp(cmd->name, name) == 0 && cmd->id == id) { + return cmd; + } + } + return nullptr; +} + +ISO_VAGCommand* FindMusicStreamName(const char* name) { + for (int i = 4; i < 6; i++) { + auto* cmd = &g_aVagCmds[i]; + if (strcmp(cmd->name, name) == 0) { + return cmd; + } + } + return nullptr; +} + +ISO_VAGCommand* FindThisMusicStream(const char* name, int id) { + for (int i = 4; i < 6; i++) { + auto* cmd = &g_aVagCmds[i]; + if (strcmp(cmd->name, name) == 0 && cmd->id == id) { + return cmd; + } + } + return nullptr; +} + +/*! + * Immediately stop the audio playback of a command, without the ability to resume. + * This will key off voices. + */ +void StopVAG(ISO_VAGCommand* cmd) { + // mark buffer inactive, so we don't stream more data + set_active_a(cmd, 0); + set_active_b(cmd, 0); + + // safely pause playback + PauseVAG(cmd); + + // deal with SPU voices: + if (cmd->flags.clocks_set) { + // do individual voice blocks + u32 voice_mask = VOICE_BIT(cmd->voice); + BlockUntilVoiceSafe(cmd->voice, 0x900); + auto* sibling = cmd->stereo_sibling; + if (sibling) { + voice_mask |= VOICE_BIT(sibling->voice); + BlockUntilVoiceSafe(sibling->voice, 0x900); + } + BlockUntilAllVoicesSafe(); + + sceSdSetSwitch((cmd->voice & 1) | SD_S_KOFF, voice_mask); + + auto now = GetSystemTimeLow(); + MarkVoiceKeyedOnOff(cmd->voice, now); + if (sibling) { + MarkVoiceKeyedOnOff(sibling->voice, now); + } + } + + // clear out command + cmd->set_all_flags_zero(); + cmd->callback = NullCallback; + cmd->clockd = 0; + cmd->play_volume = 0; + cmd->pitch_cmd = 0; + cmd->id = 0; + cmd->plugin_id = 0; + cmd->maybe_sound_handler = 0; + cmd->lfo_callback = 0; + cmd->pitch1 = 0; + cmd->pitch1_file = 0; + cmd->clocka = 0; + cmd->clockb = 0; + cmd->clockc = 0; +} + +/*! + * Stop a VAG, also remove it from lists of active commands. + */ +void TerminateVAG(ISO_VAGCommand* in_cmd) { + // ISO_VAGCommand *sibling; + // VagStreamData vsd; + // undefined auStack64 [36]; + // int local_1c; + // int local_18; + // bool not_music; + ASSERT(in_cmd); + bool not_music = !in_cmd->music_flag; + VagStreamData vsd; + strncpy(vsd.name, in_cmd->name, 0x30); + vsd.id = in_cmd->id; + StopVAG(in_cmd); + ReleaseMessage(in_cmd); + if (not_music) { + RemoveVagCmd(in_cmd); + } + FreeVagCmd(in_cmd); + auto* sibling = in_cmd->stereo_sibling; + if (sibling) { + sibling->flags.scanned = 0; + if (not_music) { + RemoveVagCmd(sibling); + } + FreeVagCmd(sibling); + } + if (not_music) { + if (in_cmd->maybe_sound_handler != 0) { + // TODO LFO support + // RemoveVagStreamFromList(&vsd, &g_PluginStreamsList); + // RemoveLfoStreamFromList(auStack64, &g_LfoStreamsList); + ASSERT_NOT_REACHED(); + } + RemoveVagStreamFromList(&vsd, &g_EEPlayList); + } +} + +/*! + * Pause a vag stream by setting the volume to 0, the pitch to 0, then keying off the voice. + */ +void PauseVAG(ISO_VAGCommand* cmd) { + if (cmd->flags.paused == 0) { + // CpuSuspendIntr(local_18); + if (cmd->flags.stereo_secondary == 0) { + auto* sibling = cmd->stereo_sibling; + if (!sibling) { + if (cmd->flags.saw_chunks1) { + // this means we enabled volume before, so disable it. + static_assert(SD_VP_VOLL == 0); + static_assert(SD_VP_VOLR == 0x100); + static_assert(SD_VP_PITCH == 0x200); + sceSdSetParam(cmd->voice | SD_VP_VOLL, 0); + sceSdSetParam(cmd->voice | SD_VP_VOLR, 0); + } + + // in either case, set pitch to 0 to stop playback. + sceSdSetParam(cmd->voice | SD_VP_PITCH, 0); + + auto voice = cmd->voice; + BlockUntilVoiceSafe(voice, 0x900); + BlockUntilAllVoicesSafe(); + // key off + sceSdSetSwitch((cmd->voice & 1) | SD_S_KOFF, VOICE_BIT(voice)); + MarkVoiceKeyedOnOff(cmd->voice, GetSystemTimeLow()); + + // remember where to resume from + if (cmd->flags.clocks_set == 0) { + cmd->current_spu_address = 0; + } else { + cmd->current_spu_address = GetSpuRamAddress(cmd); + } + cmd->flags.paused = 1; + } else { + if (cmd->flags.saw_chunks1 != 0) { + sceSdSetParam(cmd->voice | SD_VP_VOLL, 0); + sceSdSetParam(cmd->voice | SD_VP_VOLR, 0); + sceSdSetParam(sibling->voice | SD_VP_VOLL, 0); + sceSdSetParam(sibling->voice | SD_VP_VOLR, 0); + } + sceSdSetParam(sibling->voice | SD_VP_PITCH, 0); + sceSdSetParam(cmd->voice | SD_VP_PITCH, 0); + auto sibling_voice = sibling->voice; + auto voice = cmd->voice; + BlockUntilVoiceSafe(voice, 0x900); + BlockUntilVoiceSafe(sibling->voice, 0x900); + BlockUntilAllVoicesSafe(); + sceSdSetSwitch((cmd->voice & 1) | SD_S_KOFF, + (VOICE_BIT(voice)) | (VOICE_BIT(sibling_voice))); + auto now = GetSystemTimeLow(); + MarkVoiceKeyedOnOff(cmd->voice, now); + MarkVoiceKeyedOnOff(sibling->voice, now); + if (cmd->flags.clocks_set == 0) { + cmd->current_spu_address = 0; + sibling->current_spu_address = 0; + } else { + u32 spu_ram_addr = GetSpuRamAddress(cmd); + cmd->current_spu_address = spu_ram_addr & 0xfffffff8; + sibling->current_spu_address = + ((spu_ram_addr & 0xfffffff8) - cmd->stream_sram) + sibling->stream_sram; + } + cmd->flags.paused = 1; + sibling->flags.paused = 1; + } + } + // CpuResumeIntr(local_18[0]); + } +} + +namespace { +u32 read_rate_calc(u32 pitch) { + u64 pitch1 = (pitch >> 3); + u64 mult_result = pitch1 * 0x2492'4925ull; + return mult_result >> 32; +} +} // namespace +/*! + * Start up a VAG stream after it was paused. Also, used to start a vag stream for the first time. + */ +void UnPauseVAG(ISO_VAGCommand* cmd) { + if (cmd->flags.paused) { + // CpuSuspendIntr(&local_28); + if (cmd->flags.stereo_secondary == 0) { + auto* sibling = cmd->stereo_sibling; + auto pitch = CalculateVAGPitch(cmd->pitch1, cmd->pitch_cmd); + + // calculate read rate + if (cmd->m_pBaseFile) { + auto p2 = CalculateVAGPitch(cmd->pitch1_file, cmd->pitch_cmd); + u32 rate; + if (sibling == (ISO_VAGCommand*)0x0) { + rate = p2 * 0x177; + } else { + rate = p2 * 0x2ee; + } + cmd->m_pBaseFile->m_ReadRate = read_rate_calc(rate); + } + + int lvol, rvol; + CalculateVAGVolumes(cmd, &lvol, &rvol); + if (!sibling) { + // only update volume here if we've actually started the stream. + // otherwise, the stream is still getting set up, and the unpause will occur in + // the SPU interrupt handler. + if (cmd->flags.saw_chunks1) { + BlockUntilVoiceSafe(cmd->voice, 0x900); + if (cmd->current_spu_address != 0) { + sceSdSetAddr(cmd->voice | SD_VA_SSA, cmd->current_spu_address); + } + sceSdSetParam(cmd->voice | SD_VP_PITCH, pitch & 0xffff); + BlockUntilAllVoicesSafe(); + // lg::error("keying on 1!"); + sceSdSetSwitch((cmd->voice & 1) | SD_S_KON, VOICE_BIT(cmd->voice)); + MarkVoiceKeyedOnOff(cmd->voice, GetSystemTimeLow()); + sceSdSetParam(cmd->voice | SD_VP_VOLL, lvol); + sceSdSetParam(cmd->voice | SD_VP_VOLR, rvol); + } + cmd->flags.paused = 0; + } else { + if (cmd->flags.saw_chunks1) { + BlockUntilVoiceSafe(cmd->voice, 0x900); + BlockUntilVoiceSafe(sibling->voice, 0x900); + sceSdSetParam(cmd->voice | SD_VP_PITCH, pitch & 0xffff); + sceSdSetParam(sibling->voice | SD_VP_PITCH, pitch & 0xffff); + if (cmd->current_spu_address != 0) { + sceSdSetAddr(cmd->voice | SD_VA_SSA, cmd->current_spu_address); + sceSdSetAddr(sibling->voice | SD_VA_SSA, sibling->current_spu_address); + } + BlockUntilAllVoicesSafe(); + sceSdSetSwitch((cmd->voice & 1) | SD_S_KON, + (VOICE_BIT(cmd->voice)) | (VOICE_BIT(sibling->voice))); + auto now = GetSystemTimeLow(); + MarkVoiceKeyedOnOff(cmd->voice, now); + MarkVoiceKeyedOnOff(sibling->voice, now); + sceSdSetParam(cmd->voice | SD_VP_VOLL, lvol); + sceSdSetParam(sibling->voice | SD_VP_VOLL, 0); + sceSdSetParam(cmd->voice | SD_VP_VOLR, 0); + sceSdSetParam(sibling->voice | SD_VP_VOLR, rvol); + } + cmd->flags.paused = 0; + sibling->flags.paused = 0; + } + } + // CpuResumeIntr(local_28); + } +} + +// TODO: needs some cleanup +void RestartVag(ISO_VAGCommand* cmd, int p) { + (void)p; + // CpuSuspendIntr(&local_28); + int lvol, rvol; + CalculateVAGVolumes(cmd, &lvol, &rvol); + if (cmd->flags.stereo_secondary == 0) { + auto* sibling = cmd->stereo_sibling; + u32 voice_mask = VOICE_BIT(cmd->voice); + BlockUntilVoiceSafe(cmd->voice, 0x900); + if (sibling) { + voice_mask |= VOICE_BIT(sibling->voice); + BlockUntilVoiceSafe(sibling->voice, 0x900); + } + BlockUntilAllVoicesSafe(); + sceSdSetSwitch((cmd->voice & 1) | SD_S_KOFF, voice_mask); + sceSdSetParam(cmd->voice | SD_VP_VOLL, 0); + sceSdSetParam(cmd->voice | SD_VP_VOLR, 0); + // wtf is this crap + // CpuResumeIntr(local_28); + DelayThread(100); + // CpuSuspendIntr(&local_28); + u32 bVar1; + u32 uVar4; + if (sibling == (ISO_VAGCommand*)0x0) { + bVar1 = cmd->voice; + uVar4 = cmd->stream_sram; + } else { + sceSdSetParam(sibling->voice, 0); + sceSdSetParam(sibling->voice | 0x100, 0); + sceSdSetAddr(cmd->voice | 0x2040, cmd->stream_sram); + bVar1 = sibling->voice; + uVar4 = sibling->stream_sram; + } + sceSdSetAddr(bVar1 | 0x2040, uVar4); + BlockUntilAllVoicesSafe(); + sceSdSetSwitch((cmd->voice & 1) | 0x1500, voice_mask); + auto uVar3 = GetSystemTimeLow(); + MarkVoiceKeyedOnOff(cmd->voice, uVar3); + u32 uVar2; + if (!sibling) { + sceSdSetParam(cmd->voice, lvol); + uVar2 = cmd->voice; + } else { + MarkVoiceKeyedOnOff(sibling->voice, uVar3); + sceSdSetParam(cmd->voice, lvol); + sceSdSetParam(sibling->voice, 0); + sceSdSetParam(cmd->voice | 0x100, 0); + uVar2 = sibling->voice; + } + sceSdSetParam(uVar2 | 0x100, rvol); + } + // CpuResumeIntr(local_28); +} + +void PauseVagStreams(bool music) { + if (music != 0) { + WaitSema(g_nMusicSemaphore); + g_bAnotherMusicPauseFlag = true; + g_bMusicIsPaused = true; + } + + for (int i = 0; i < (music ? 6 : 4); i++) { + auto* cmd = &g_aVagCmds[i]; + if (cmd->flags.running && !cmd->flags.paused) { + PauseVAG(cmd); + } + } + + if (music != 0) { + SignalSema(g_nMusicSemaphore); + } +} + +void UnPauseVagStreams(bool music) { + int iVar1; + ISO_VAGCommand* cmd; + + if (music != 0) { + WaitSema(g_nMusicSemaphore); + } + cmd = g_aVagCmds; + iVar1 = 4; + if ((music != 0) && (iVar1 = 6, g_bMusicPause != 0)) { + iVar1 = 4; + } + while (iVar1 != 0) { + iVar1 = iVar1 + -1; + if ((cmd->flags.running != 0) && (cmd->flags.paused != 0)) { + UnPauseVAG(cmd); + } + cmd = cmd + 1; + } + if (music != 0) { + g_bAnotherMusicPauseFlag = false; + if (g_bMusicPause == 0) { + g_bMusicIsPaused = false; + } + SignalSema(g_nMusicSemaphore); + } +} + +int CalculateDolbyPanAngle(ISO_VAGCommand* cmd, u32 angle) { + int iVar1; + int iVar2; + + iVar1 = 0; + if (cmd != (ISO_VAGCommand*)0x0) { + if (0x167 < angle) { + // TODO: sus 64-bit multiply + angle = angle + (u32)((u64)(angle >> 3) * 0x16c16c17 >> 0x22) * -0x168; + } + iVar1 = cmd->dolby_pan_angle; + iVar2 = iVar1; + if (0x167 < iVar1) { + iVar2 = iVar1 + -0x168; + } + iVar2 = angle - iVar2; + if (iVar2 < -0xb4) { + iVar2 = iVar2 + 0x168; + } else { + if (0xb4 < iVar2) { + iVar2 = iVar2 + -0x168; + } + } + iVar1 = iVar1 + iVar2; + if (iVar1 < 0) { + iVar1 = iVar1 + 0x2d0; + } + if (0x2cf < iVar1) { + iVar1 = iVar1 + -0x2d0; + } + cmd->dolby_pan_angle = iVar1; + } + return iVar1; +} + +void CalculateVAGVolumes(ISO_VAGCommand* cmd, int* lvol, int* rvol) { + u32 angle; + int iVar3; + int iVar4; + int iVar5; + u32 uVar7; + u32 uVar8; + int iVar9; + + ASSERT(cmd && lvol && rvol); + + iVar9 = g_nPlaybackMode; + iVar3 = 0x3fff; + iVar4 = 0x3fff; + uVar8 = 0; + if (cmd->music_flag == 0) { + iVar5 = cmd->maybe_sound_handler; + if (iVar5 == 0) { + if (cmd->updated_trans == 0) { + uVar7 = (u32)(cmd->play_volume * g_anMasterVolume[cmd->play_group]) >> 6; + goto LAB_0000bb58; + } + uVar7 = CalculateFalloffVolume(cmd->trans, + (cmd->play_volume * g_anMasterVolume[cmd->play_group]) >> 10, + cmd->fo_curve, cmd->fo_min, cmd->fo_max, 0, 0); + iVar3 = CalculateAngle(cmd->trans, cmd->fo_curve, 0); + angle = iVar3 + 0x5aU + (u32)((u64)((iVar3 + 0x5aU) >> 3) * 0x16c16c17 >> 0x22) * -0x168; + if (0x400 < uVar7) { + uVar7 = 0x400; + } + uVar7 = uVar7 << 4 | uVar7 >> 6; + } else { + ASSERT_NOT_REACHED(); + } + + if (iVar9 == 1) { + iVar3 = 0x2d41; + iVar4 = 0x2d41; + } else { + if (iVar9 < 2) { + iVar3 = 0x2d41; + if (iVar9 == 0) { + // ASSERT_NOT_REACHED(); // dolby crap + // TODO: + iVar4 = 0x2d41; + // uVar8 = CalculateDolbyPanAngle(cmd, angle); + // iVar9 = (uint)((u64)(uVar8 >> 1) * 0xb60b60b7 >> 0x25) * 4; + // if (0x167 < uVar8) { + // uVar8 = uVar8 - 0x168; + // } + // if (uVar8 < 0xb4) { + // psVar6 = (short*)(g_aPanTable + uVar8 * 4); + // sVar1 = psVar6[1]; + // sVar2 = *psVar6; + // } else { + // iVar3 = g_aPanTable + uVar8 * 4; + // sVar1 = *(short*)(iVar3 + -0x2d0); + // sVar2 = *(short*)(iVar3 + -0x2ce); + // } + // iVar4 = (int)sVar1; + // iVar3 = (int)sVar2; + // if ((int)((uint) * (ushort*)(iVar9 + 0x15800) << 0x10) < 0) { + // iVar3 = -iVar3; + // } + // if (*(short*)(iVar9 + 0x15802) < 0) { + // iVar4 = -iVar4; + // } + // uVar8 = 0xffffc001; + } else { + iVar4 = 0x2d41; + } + } else { + iVar3 = 0x2d41; + if (iVar9 == 2) { + cmd->dolby_pan_angle = angle; + if (angle < 0xb4) { + auto pte = g_aPanTable[angle]; + // psVar6 = (short*)(g_aPanTable + angle * 4); + iVar4 = pte.right; + iVar3 = pte.left; + } else { + // iVar3 = g_aPanTable + angle * 4; + u32 angle2 = (angle - 180); + ASSERT(angle2 < 360); + auto pte = g_aPanTable[angle2]; + iVar4 = pte.left; + iVar3 = pte.right; + } + } else { + iVar4 = 0x2d41; + } + } + } + } else { + uVar7 = ((u32)(g_nMusicFade * g_anMasterVolume[1]) >> 0xc) * g_nMusicTweak >> 7; + } + +LAB_0000bb58: + iVar9 = iVar4; + if (g_CameraInvert != 0) { + iVar9 = iVar3; + iVar3 = iVar4; + } + iVar3 = iVar3 * uVar7; + iVar9 = iVar9 * uVar7; + if (iVar3 < 0) { + iVar3 = iVar3 + 0x3fff; + } + uVar7 = iVar3 >> 0xe; + if (iVar9 < 0) { + iVar9 = iVar9 + 0x3fff; + } + angle = iVar9 >> 0xe; + if (0x3fff < (int)uVar7) { + uVar7 = 0x3fff; + } + if ((int)uVar7 < (int)uVar8) { + uVar7 = uVar8; + } + *lvol = uVar7 & 0x7fff; + if (0x3fff < (int)angle) { + angle = 0x3fff; + } + if ((int)angle < (int)uVar8) { + angle = uVar8; + } + *rvol = angle & 0x7fff; +} + +s32 CalculateVAGPitch(int param_1, int param_2) { + if (param_2) { + if (param_2 <= 0) { + return 0x5f4 * param_1 / (0x5f4 - param_2); + } else { + return param_1 * (param_2 + 0x5f4) / 0x5f4; + } + } + + return param_1; + // if (b != 0) { + // if (0 < b) { + // return (a * (b + 0x5f4)) / 0x5f4; + // } + // a = (a * 0x5f4) / (0x5f4U - b); + // } + // return a; +} + +void SetVAGVol(ISO_VAGCommand* cmd) { + if (cmd && cmd->flags.running && !cmd->flags.paused && !cmd->flags.stereo_secondary) { + auto* sibling = cmd->stereo_sibling; + int lvol, rvol; + CalculateVAGVolumes(cmd, &lvol, &rvol); + auto* file = cmd->m_pBaseFile; + if (file) { + u32 rate = CalculateVAGPitch(cmd->pitch1_file, cmd->pitch_cmd); + if (!sibling) { + rate = rate * 0x177; + } else { + rate = rate * 0x2ee; + } + file->m_ReadRate = read_rate_calc(rate); + } + + // like jak2, rewritten to not use sceSdProcBatch + if (!sibling) { + sceSdSetParam(SD_VP_VOLL | cmd->voice, lvol); + sceSdSetParam(SD_VP_VOLR | cmd->voice, rvol); + sceSdSetParam(SD_VP_PITCH | cmd->voice, CalculateVAGPitch(cmd->pitch1, cmd->pitch_cmd)); + } else { + // left channel, left vol + if (g_CameraInvert) { + sceSdSetParam(SD_VP_VOLL | cmd->voice, 0); + } else { + sceSdSetParam(SD_VP_VOLL | cmd->voice, lvol); + } + + // right channel, left vol + if (g_CameraInvert) { + sceSdSetParam(SD_VP_VOLL | sibling->voice, rvol); + } else { + sceSdSetParam(SD_VP_VOLL | sibling->voice, 0); + } + + if (g_CameraInvert) { + sceSdSetParam(SD_VP_VOLR | cmd->voice, lvol); + } else { + sceSdSetParam(SD_VP_VOLR | cmd->voice, 0); + } + + if (g_CameraInvert) { + sceSdSetParam(SD_VP_VOLR | sibling->voice, 0); + } else { + sceSdSetParam(SD_VP_VOLR | sibling->voice, rvol); + } + + sceSdSetParam(SD_VP_PITCH | cmd->voice, CalculateVAGPitch(cmd->pitch1, cmd->pitch_cmd)); + } + // CpuSuspendIntr(local_20); + // sceSdProcBatch(batch, 0, count); + // CpuResumeIntr(local_20[0]); + } +} + +void SetAllVagsVol(int x) { + if (x < 0) { + for (int i = 0; i < 4; i++) { + SetVAGVol(&g_aVagCmds[i]); + } + } else { + for (int i = 0; i < 4; i++) { + if (g_aVagCmds[i].maybe_sound_handler) { + ASSERT_NOT_REACHED(); + // there's more check before this... + SetVAGVol(&g_aVagCmds[i]); + } + } + } +} + +void VAG_MarkLoopStart(uint8_t* data) { + data[0x11] = 2; + data[1] = 6; +} + +void VAG_MarkLoopEnd(uint8_t* data, int offset) { + data[offset + -0xf] = 3; +} +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/vag.h b/game/overlord/jak3/vag.h new file mode 100644 index 0000000000..1ce2179cf5 --- /dev/null +++ b/game/overlord/jak3/vag.h @@ -0,0 +1,173 @@ +#pragma once + +#include "game/overlord/jak3/isocommon.h" +namespace jak3 { +void jak3_overlord_init_globals_vag(); + +extern bool g_bExtPause; +extern bool g_bExtResume; + +struct ISO_VAGCommand : ISO_Hdr { + ISOFileDef* vag_file_def = nullptr; // 44 (actually INT file def?) + VagDirEntry* vag_dir_entry = nullptr; // 48 + ISO_VAGCommand* stereo_sibling = nullptr; // 52 + + // pointer to IOP memory to DMA to SPU. Points to the data for the next new transfer. + const u8* dma_iop_mem_ptr = nullptr; // 56 + + // the DMA channel to upload to for sceCdVoiceTrans + int dma_chan = 0; // 60 + + // if not set, a pending dma interrupt will want to modify this command. + int safe_to_modify_dma = 0; // 64 + + u32 current_spu_address = 0; // 68 + + char name[48]; // 72 + char overflow[16]; + + // SPU address of the next chunk to fill + // for stereo mode, there's a 0x2000 offset between the left and right audio. This stream_sram + // doesn't include that offset. + u32 stream_sram; // 124 + u32 trap_sram; // 138 + + // spu voice for playback + int voice; // 132 + int info_idx; // 136 + int maybe_sound_handler = 0; // 140 + void* lfo_callback = nullptr; // 144 + + int oog = 0; // 180 + int dolby_pan_angle = 0; // 184 + int clocka = 0; // 188 + int clockb = 0; // 192 + int clockc = 0; // 196 + int clockd = 0; // 200 + int unk_gvsp_len; // 204 + int position_for_ee; // 208 + int unk_gvsp_cntr; // 212 + + struct { + u8 bit0 = 0; // 216 + u8 saw_chunks1 = 0; // 217 + // will start the voice, but with a pitch of 0. + u8 paused = 0; // 218 + u8 bit3 = 0; // 219 + u8 running = 0; // 220 + u8 clocks_set = 0; // 221, set by SetVagClock if it succeeds. + u8 file_disappeared = 0; // 222, set if SetVagClock notices that bBaseFile is gone! + u8 scanned = 0; // 223, set shortly after internal command is created. + u8 bit8 = 0; + u8 stop = 0; // 225, set if this is a non-plugin stream, and was stopped by StopVagStream. + u8 art = 0; // 226, set if this has art_flag set + // set if we are the non-main stereo command. + u8 stereo_secondary = 0; // 227 + u8 bit12 = 0; // 228 + u8 bit13 = 0; // 229 + u8 bit14 = 0; // 230 + u8 bit15 = 0; // 231 + u8 bit16 = 0; // 232 + u8 bit17 = 0; // 233 + // set if SPU DMA has completed for an even or odd number of chunks of non-stereo audio. + u8 dma_complete_even_chunk_count = 0; // 234 + u8 dma_complete_odd_chunk_count = 0; // 235 + u8 bit20 = 0; + u8 bit21 = 0; + u8 bit22 = 0; + u8 nostart = 0; + u8 movie = 0; + u8 bit25 = 0; + } flags; + + u32 pack_flags(); + + void set_all_flags_zero(); + int unk_gvsp_state2 = 0; // 244 + int num_isobuffered_chunks = 0; // 248 + + // if we need to do a second SPU DMA for stereo's second channel, the size of that transfer + int xfer_size; // 252 + int vag_file_rate; // 256 + + int pitch1; // 260 pitch to use for playback, possibly overwritten + int pitch1_file; // 264 pitch to use for playback, from the file itself (sample rate) + int pitch_cmd; // 268 pitch mod command (?) + int error; // 272 + int unk_spu_mem_offset; // 276 + int unk_gvsp_flag; // 280 + int play_volume = 0; // 284 + int id = 0; // 288 + int plugin_id = 0; // 292 + int priority_pq = 0; // 296 + int art_flag = 0; // 300 + int music_flag = 0; // 304 + int updated_trans = 0; // 308 + int trans[3]; // 312 + int fo_min; // 324 + int fo_max; // 328 + int fo_curve; // 332 + int play_group = 0; // 336 + int movie_flag = 0; // 340 +}; + +struct VagStreamData { + VagStreamData* next = nullptr; + VagStreamData* prev = nullptr; + int in_use; + char name[0x30]; + int id; + int plugin_id; + int sound_handler; + int art_load; + int movie_art_load; + int priority; + int unk2; + int unk1; + int volume2; + int maybe_volume_3; + int group; +}; + +extern ISO_VAGCommand g_aVagCmds[6]; +extern int g_anMasterVolume[32]; +extern bool voice_key_flags[0x30]; +extern u32 voice_key_times[0x30]; +extern u32 g_nTimeOfLastVoiceKey; +extern bool g_bRecentlyKeyedVoice; + +int CalculateVAGPitch(int a, int b); +void BlockUntilVoiceSafe(int, u32); +void BlockUntilAllVoicesSafe(); +void CheckVagStreamsProgress(); +void MarkVoiceKeyedOnOff(int voice, u32 systime); +void UnPauseVAG(ISO_VAGCommand* cmd); +ISO_VAGCommand* FindMusicStreamName(const char* name); +ISO_VAGCommand* SmartAllocMusicVagCommand(const ISO_VAGCommand* user_command, int flag); +void InitVAGCmd(ISO_VAGCommand* cmd, int paused); +int HowManyBelowThisPriority(int pri); +ISO_VAGCommand* FindThisVagStream(const char* name, int id); +ISO_VAGCommand* SmartAllocVagCmd(ISO_VAGCommand* user_command); +void RemoveVagCmd(ISO_VAGCommand* cmd); +void SetNewVagCmdPri(ISO_VAGCommand* cmd, int pri); +void TerminateVAG(ISO_VAGCommand* cmd); +ISO_VAGCommand* FindThisMusicStream(const char* name, int id); +ISO_VAGCommand* FindVagStreamName(const char* name); +int AnyVagRunning(); +void PauseVAG(ISO_VAGCommand* cmd); +void InitVagCmds(); +void PauseVagStreams(bool music); +void UnPauseVagStreams(bool music); +void SetVagStreamsNoStart(int value); +ISO_VAGCommand* FindVagStreamId(int); +void SetVAGVol(ISO_VAGCommand* cmd); +void SetAllVagsVol(int); +void PauseVAGStreams(); +ISO_VAGCommand* FindNotQueuedVagCmd(); +void CalculateVAGVolumes(ISO_VAGCommand* cmd, int* l, int* r); +void SetVagStreamsNotScanned(); +void VAG_MarkLoopEnd(uint8_t* data, int offset); +void VAG_MarkLoopStart(uint8_t* data); +void RestartVag(ISO_VAGCommand* cmd, int p); +extern u32 g_nPlaybackMode; +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/vblank_handler.cpp b/game/overlord/jak3/vblank_handler.cpp new file mode 100644 index 0000000000..c9e1eed906 --- /dev/null +++ b/game/overlord/jak3/vblank_handler.cpp @@ -0,0 +1,201 @@ +#include "vblank_handler.h" + +#include + +#include "common/log/log.h" +#include "common/util/Assert.h" + +#include "game/overlord/jak3/dma.h" +#include "game/overlord/jak3/iso.h" +#include "game/overlord/jak3/sbank.h" +#include "game/overlord/jak3/srpc.h" +#include "game/overlord/jak3/ssound.h" +#include "game/overlord/jak3/streamlist.h" +#include "game/overlord/jak3/vag.h" +#include "game/sce/iop.h" + +namespace jak3 { +using namespace iop; +u32 g_nInfoEE = 0; +SoundIOPInfo g_SRPCSoundIOPInfo; +bool g_bVBlankInitialized = false; +s32 g_nVBlankThreadID = -1; +s32 g_nVBlankSemaphoreID = -1; +bool g_bVBlankRegistered = false; +u32 g_nIopTicks = 0; +u32 g_nFrameNum = 0; +void jak3_overlord_init_globals_vblank_handler() { + g_nInfoEE = 0; + g_SRPCSoundIOPInfo = {}; + g_bVBlankInitialized = false; + g_nVBlankThreadID = -1; + g_nVBlankSemaphoreID = -1; + g_bVBlankRegistered = false; + g_nIopTicks = 0; + g_nFrameNum = 0; +} + +int VBlankHandler(void*); +u32 VBlankThread(); + +void VBlank_Initialize() { + ThreadParam thread_param; + SemaParam sema_param; + + if (g_bVBlankInitialized == 0) { + thread_param.attr = 0x2000000; + thread_param.stackSize = 0x800; + thread_param.initPriority = 0x34; + thread_param.option = 0; + thread_param.entry = VBlankThread; + strcpy(thread_param.name, "vblank"); + g_nVBlankThreadID = CreateThread(&thread_param); + ASSERT(g_nVBlankThreadID >= 0); + sema_param.max_count = 200; // hack + sema_param.attr = 0; + sema_param.init_count = 0; + sema_param.option = 0; + g_nVBlankSemaphoreID = CreateSema(&sema_param); + ASSERT(g_nVBlankSemaphoreID >= 0); + int ret = StartThread(g_nVBlankThreadID, 0); + ASSERT(ret == 0); + RegisterVblankHandler(0, 0x40, VBlankHandler, 0); + g_bVBlankInitialized = true; + g_bVBlankRegistered = true; + } +} + +int VBlankHandler(void*) { + if ((g_bVBlankInitialized != 0) && (-1 < g_nVBlankSemaphoreID)) { + SignalSema(g_nVBlankSemaphoreID); // was iSignalSema + } + return 1; +} + +u32 VBlankThread() { + // char *pcVar1; + // int iVar2; + // uint uVar3; + // SoundBankInfo *pSVar4; + // ISO_VAGCommand *cmd; + // SoundBankInfo **ppSVar5; + // uint *puVar6; + // uint uVar7; + // int iVar8; + // int iVar9; + // SoundIOPInfo *local_30; + // void *local_2c; + // undefined4 local_28; + // undefined4 local_24; + // undefined4 local_20 [2]; + + do { + while ((g_bVBlankInitialized == 0 || (g_nVBlankSemaphoreID < 0))) { + DelayThread(1000000); + } + WaitSema(g_nVBlankSemaphoreID); + g_nIopTicks = g_nIopTicks + 1; + if (g_bSoundEnable != 0) { + CheckVagStreamsProgress(); + if ((g_nIopTicks & 1U) != 0) { + StreamListThread(); + } + if (g_nMusicFadeDir < 0) { + g_nMusicFade = g_nMusicFade + -0x200; + if (g_nMusicFade < 0) { + g_nMusicFade = 0; + LAB_00011f60: + g_nMusicFadeDir = 0; + } + } else { + if ((0 < g_nMusicFadeDir) && + (g_nMusicFade = g_nMusicFade + 0x400, 0x10000 < g_nMusicFade)) { + g_nMusicFade = 0x10000; + goto LAB_00011f60; + } + } + if (g_nInfoEE) { + g_nFrameNum = g_nFrameNum + 1; + // puVar6 = g_SRPCSoundIOPInfo.stream_status; + for (int i = 0; i < 4; i++) { + auto* cmd = &g_aVagCmds[i]; + u32 stream_status = cmd->pack_flags(); + + if ((cmd->flags.file_disappeared != 0) && (cmd->flags.paused == 0)) { + auto uVar3 = CalculateVAGPitch(0x400, cmd->pitch_cmd); + if (g_nFPS == 0) { + ASSERT_NOT_REACHED(); + } + cmd->clockd = cmd->clockd + uVar3 / g_nFPS; + } + + if ((cmd->flags.saw_chunks1 == 0) && (cmd->flags.clocks_set != 0)) { + g_SRPCSoundIOPInfo.stream_status[i] = stream_status; + g_SRPCSoundIOPInfo.stream_id[i] = cmd->id; + g_SRPCSoundIOPInfo.stream_position[i] = 0; + } else { + g_SRPCSoundIOPInfo.stream_status[i] = stream_status; + g_SRPCSoundIOPInfo.stream_id[i] = cmd->id; + g_SRPCSoundIOPInfo.stream_position[i] = cmd->position_for_ee; + } + } + // CpuSuspendIntr(local_20); + + // CpuResumeIntr(local_20[0]); + g_SRPCSoundIOPInfo.iop_ticks = g_nIopTicks; + g_SRPCSoundIOPInfo.freemem = 12345; // hack + g_SRPCSoundIOPInfo.frame = g_nFrameNum; + g_SRPCSoundIOPInfo.freemem2 = QueryTotalFreeMemSize(); + g_SRPCSoundIOPInfo.nocd = 0; // hack + g_SRPCSoundIOPInfo.dirtycd = 0; // hack + g_SRPCSoundIOPInfo.dupseg = -1; + g_SRPCSoundIOPInfo.diskspeed[0] = 0; + g_SRPCSoundIOPInfo.diskspeed[1] = 0; + g_SRPCSoundIOPInfo.lastspeed = 0; + + memset(&g_SRPCSoundIOPInfo.sound_bank0[0], 0, 8 * 16); + + if (gBanks[0]->in_use && gBanks[0]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank0, gBanks[0]->m_name1); + } + if (gBanks[1]->in_use && gBanks[1]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank1, gBanks[1]->m_name1); + } + if (gBanks[2]->in_use && gBanks[2]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank2, gBanks[2]->m_name1); + } + if (gBanks[3]->in_use && gBanks[3]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank3, gBanks[3]->m_name1); + } + if (gBanks[4]->in_use && gBanks[4]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank4, gBanks[4]->m_name1); + } + if (gBanks[5]->in_use && gBanks[5]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank5, gBanks[5]->m_name1); + } + if (gBanks[6]->in_use && gBanks[6]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank6, gBanks[6]->m_name1); + } + if (gBanks[7]->in_use && gBanks[7]->loaded) { + strcpy(g_SRPCSoundIOPInfo.sound_bank7, gBanks[7]->m_name1); + } + + for (int i = 0; i < 48; i++) { + g_SRPCSoundIOPInfo.chinfo[i] = (snd_GetVoiceStatus(i) != 1) - 1; + } + + sceSifDmaData dma; + dma.data = &g_SRPCSoundIOPInfo; + dma.addr = (void*)(u64)g_nInfoEE; + dma.size = sizeof(g_SRPCSoundIOPInfo); + static_assert(sizeof(g_SRPCSoundIOPInfo) == 0x2d0); + dma.mode = 0; + /*dmaid =*/sceSifSetDma(&dma, 1); + } + } + RunDeferredVoiceTrans(); + // Poll(&g_DvdDriver); + } while (true); +} + +} // namespace jak3 \ No newline at end of file diff --git a/game/overlord/jak3/vblank_handler.h b/game/overlord/jak3/vblank_handler.h new file mode 100644 index 0000000000..5c343e7a2b --- /dev/null +++ b/game/overlord/jak3/vblank_handler.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common/common_types.h" + +#include "game/overlord/jak3/rpc_interface.h" + +namespace jak3 { +void jak3_overlord_init_globals_vblank_handler(); +void VBlank_Initialize(); +extern u32 g_nInfoEE; +extern SoundIOPInfo g_SRPCSoundIOPInfo; +} // namespace jak3 \ No newline at end of file diff --git a/game/runtime.cpp b/game/runtime.cpp index b39317b604..3f41177a2b 100644 --- a/game/runtime.cpp +++ b/game/runtime.cpp @@ -78,6 +78,8 @@ #include "game/overlord/jak2/stream.h" #include "game/overlord/jak2/streamlist.h" #include "game/overlord/jak2/vag.h" +#include "game/overlord/jak3/init.h" +#include "game/overlord/jak3/overlord.h" #include "game/system/Deci2Server.h" #include "game/system/iop_thread.h" #include "sce/deci2.h" @@ -269,33 +271,36 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) { iop::LIBRARY_register(&iop); Gfx::register_vsync_callback([&iop]() { iop.kernel.signal_vblank(); }); - jak1::dma_init_globals(); - jak2::dma_init_globals(); + if (version != GameVersion::Jak3) { + jak1::dma_init_globals(); + jak2::dma_init_globals(); - iso_init_globals(); - jak1::iso_init_globals(); - jak2::iso_init_globals(); + iso_init_globals(); + jak1::iso_init_globals(); + jak2::iso_init_globals(); - fake_iso_init_globals(); - jak1::fake_iso_init_globals(); - jak2::iso_cd_init_globals(); + fake_iso_init_globals(); + jak1::fake_iso_init_globals(); + jak2::iso_cd_init_globals(); - jak1::iso_queue_init_globals(); - jak2::iso_queue_init_globals(); + jak1::iso_queue_init_globals(); + jak2::iso_queue_init_globals(); - jak2::spusstreams_init_globals(); - jak1::ramdisk_init_globals(); - sbank_init_globals(); + jak2::spusstreams_init_globals(); + jak1::ramdisk_init_globals(); + sbank_init_globals(); - // soundcommon - jak1::srpc_init_globals(); - jak2::srpc_init_globals(); - srpc_init_globals(); - ssound_init_globals(); - jak2::ssound_init_globals(); + // soundcommon + jak1::srpc_init_globals(); + jak2::srpc_init_globals(); + srpc_init_globals(); + ssound_init_globals(); + jak2::ssound_init_globals(); + + jak1::stream_init_globals(); + jak2::stream_init_globals(); + } - jak1::stream_init_globals(); - jak2::stream_init_globals(); prof().end_event(); iface.initialization_complete(); @@ -323,9 +328,11 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) { jak1::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete); break; case GameVersion::Jak2: - case GameVersion::Jak3: // TODO: jak3 using jak2's overlord. jak2::start_overlord_wrapper(iop.overlord_argc, iop.overlord_argv, &complete); break; + case GameVersion::Jak3: + jak3::start_overlord_wrapper(&complete); + break; default: ASSERT_NOT_REACHED(); } @@ -334,7 +341,6 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) { { auto p = scoped_prof("overlord-wait-for-init"); while (complete == false) { - prof().root_event(); iop.kernel.dispatch(); } } @@ -344,7 +350,7 @@ void iop_runner(SystemThreadInterface& iface, GameVersion version) { // IOP Kernel loop while (!iface.get_want_exit() && !iop.want_exit) { - prof().root_event(); + // prof().root_event(); // The IOP scheduler informs us of how many microseconds are left until it has something to do. // So we can wait for that long or until something else needs it to wake up. auto wait_duration = iop.kernel.dispatch(); diff --git a/game/sce/iop.cpp b/game/sce/iop.cpp index 2784c9fa42..6756fa1513 100644 --- a/game/sce/iop.cpp +++ b/game/sce/iop.cpp @@ -174,6 +174,10 @@ void DelayThread(u32 usec) { iop->kernel.DelayThread(usec); } +void YieldThread() { + iop->kernel.YieldThread(); +} + int sceCdBreak() { return 1; } @@ -199,16 +203,20 @@ s32 PollMbx(MsgPacket** recvmsg, int mbxid) { return iop->kernel.PollMbx((void**)recvmsg, mbxid); } +s32 ReceiveMbx(MsgPacket** recvmsg, int mbxid) { + return iop->kernel.ReceiveMbx((void**)recvmsg, mbxid); +} + s32 PeekMbx(s32 mbx) { return iop->kernel.PeekMbx(mbx); } -static int now = 0; +s32 MbxSize(s32 mbx) { + return iop->kernel.MbxSize(mbx); +} -void GetSystemTime(SysClock* time) { - time->lo = 0; - time->hi = now; - now += 10; +u32 GetSystemTimeLow() { + return iop->kernel.GetSystemTimeLow(); } void SleepThread() { @@ -216,7 +224,7 @@ void SleepThread() { } s32 CreateSema(SemaParam* param) { - return iop->kernel.CreateSema(param->attr, param->option, param->max_count, param->init_count); + return iop->kernel.CreateSema(param->attr, param->option, param->init_count, param->max_count); } s32 WaitSema(s32 sema) { @@ -231,6 +239,22 @@ s32 PollSema(s32 sema) { return iop->kernel.PollSema(sema); } +s32 CreateEventFlag(const EventFlagParam* param) { + return iop->kernel.CreateEventFlag(param->attr, param->option, param->init_pattern); +} + +s32 ClearEventFlag(s32 flag, u32 pattern) { + return iop->kernel.ClearEventFlag(flag, pattern); +} + +s32 WaitEventFlag(s32 flag, u32 pattern, u32 mode) { + return iop->kernel.WaitEventFlag(flag, pattern, mode); +} + +s32 SetEventFlag(s32 flag, u32 pattern) { + return iop->kernel.SetEventFlag(flag, pattern); +} + s32 WakeupThread(s32 thid) { iop->kernel.WakeupThread(thid); return 0; diff --git a/game/sce/iop.h b/game/sce/iop.h index 50a06088c1..62fa6549ac 100644 --- a/game/sce/iop.h +++ b/game/sce/iop.h @@ -30,6 +30,10 @@ #define SA_THFIFO 0 #define SA_THPRI 1 +#define EW_AND 0 +#define EW_OR 1 +#define EW_CLEAR 0x10 + class IOP; namespace iop { @@ -56,7 +60,7 @@ struct sceCdRMode { }; struct sceSifDmaData { - void* data; + const void* data; void* addr; unsigned int size; unsigned int mode; @@ -85,7 +89,7 @@ struct ThreadParam { int initPriority; // added! - char name[64]; + char name[64] = ""; }; struct SemaParam { @@ -95,6 +99,12 @@ struct SemaParam { int32_t max_count; }; +struct EventFlagParam { + u32 attr; + u32 option; + u32 init_pattern; +}; + // void PS2_RegisterIOP(IOP *iop); int QueryTotalFreeMemSize(); void* AllocSysMemory(int type, unsigned long size, void* addr); @@ -105,6 +115,7 @@ void CpuDisableIntr(); void CpuEnableIntr(); void SleepThread(); void DelayThread(u32 usec); +void YieldThread(); s32 CreateThread(ThreadParam* param); s32 ExitThread(); s32 StartThread(s32 thid, u32 arg); @@ -135,16 +146,23 @@ u32 sceSifSetDma(sceSifDmaData* sdd, int len); s32 SendMbx(int mbxid, void* sendmsg); s32 PollMbx(MsgPacket** recvmsg, int mbxid); +s32 ReceiveMbx(MsgPacket** recvmsg, int mbxid); s32 PeekMbx(s32 mbx); +s32 MbxSize(s32 mbx); s32 CreateMbx(MbxParam* param); -void GetSystemTime(SysClock* time); +u32 GetSystemTimeLow(); s32 CreateSema(SemaParam* param); s32 WaitSema(s32 sema); s32 SignalSema(s32 sema); s32 PollSema(s32 sema); +s32 CreateEventFlag(const EventFlagParam* param); +s32 ClearEventFlag(s32 flag, u32 pattern); +s32 SetEventFlag(s32 flag, u32 pattern); +s32 WaitEventFlag(s32 flag, u32 pattern, u32 mode); + s32 RegisterVblankHandler(int edge, int priority, int (*handler)(void*), void* userdata); void FlushDcache(); diff --git a/game/sound/989snd/blocksound_handler.cpp b/game/sound/989snd/blocksound_handler.cpp index 4429b472ce..f1e189d5a0 100644 --- a/game/sound/989snd/blocksound_handler.cpp +++ b/game/sound/989snd/blocksound_handler.cpp @@ -15,8 +15,9 @@ BlockSoundHandler::BlockSoundHandler(SoundBank& bank, VoiceManager& vm, s32 sfx_vol, s32 sfx_pan, - SndPlayParams& params) - : m_group(sfx.VolGroup), m_sfx(sfx), m_vm(vm), m_bank(bank) { + SndPlayParams& params, + u32 sound_id) + : m_group(sfx.VolGroup), m_sfx(sfx), m_vm(vm), m_bank(bank), m_sound_id(sound_id) { s32 vol, pan, pitch_mod, pitch_bend; if (sfx_vol == -1) { sfx_vol = sfx.Vol; diff --git a/game/sound/989snd/blocksound_handler.h b/game/sound/989snd/blocksound_handler.h index 52920b3650..f1b30a80fa 100644 --- a/game/sound/989snd/blocksound_handler.h +++ b/game/sound/989snd/blocksound_handler.h @@ -25,7 +25,8 @@ class BlockSoundHandler : public SoundHandler { VoiceManager& vm, s32 sfx_vol, s32 sfx_pan, - SndPlayParams& params); + SndPlayParams& params, + u32 sound_id); ~BlockSoundHandler() override; bool Tick() override; @@ -39,6 +40,7 @@ class BlockSoundHandler : public SoundHandler { void SetPMod(s32 mod) override; void SetRegister(u8 reg, u8 value) override { m_registers.at(reg) = value; }; void SetPBend(s32 bend) override; + u32 SoundID() const override { return m_sound_id; } void DoGrain(); @@ -86,5 +88,7 @@ class BlockSoundHandler : public SoundHandler { s32 m_countdown{0}; u32 m_next_grain{0}; + + u32 m_sound_id{0}; }; } // namespace snd diff --git a/game/sound/989snd/player.cpp b/game/sound/989snd/player.cpp index be5821928f..da8c7e6b1f 100644 --- a/game/sound/989snd/player.cpp +++ b/game/sound/989snd/player.cpp @@ -193,6 +193,14 @@ void Player::StopSound(u32 sound_id) { // m_handlers.erase(sound_id); } +u32 Player::GetSoundID(u32 sound_handle) { + std::scoped_lock lock(mTickLock); + auto handler = mHandlers.find(sound_handle); + if (handler == mHandlers.end()) + return -1; + return handler->second->SoundID(); +} + void Player::SetSoundReg(u32 sound_id, u8 reg, u8 value) { std::scoped_lock lock(mTickLock); if (mHandlers.find(sound_id) == mHandlers.end()) { diff --git a/game/sound/989snd/player.h b/game/sound/989snd/player.h index c6c18e3f76..f03498c20a 100644 --- a/game/sound/989snd/player.h +++ b/game/sound/989snd/player.h @@ -48,6 +48,7 @@ class Player { void SetMasterVolume(u32 group, s32 volume); void UnloadBank(BankHandle bank_handle); void StopSound(u32 sound_handle); + u32 GetSoundID(u32 sound_handle); void SetPanTable(VolPair* pantable); void SetPlaybackMode(s32 mode); void PauseSound(s32 sound_handle); diff --git a/game/sound/989snd/sfxblock.cpp b/game/sound/989snd/sfxblock.cpp index 9de0dac857..9fc9920ec1 100644 --- a/game/sound/989snd/sfxblock.cpp +++ b/game/sound/989snd/sfxblock.cpp @@ -18,7 +18,7 @@ std::optional> SFXBlock::MakeHandler(VoiceManager& return std::nullopt; } - auto handler = std::make_unique(*this, SFX, vm, vol, pan, params); + auto handler = std::make_unique(*this, SFX, vm, vol, pan, params, sound_id); return handler; } diff --git a/game/sound/989snd/sound_handler.h b/game/sound/989snd/sound_handler.h index 45594f41a8..85ef182134 100644 --- a/game/sound/989snd/sound_handler.h +++ b/game/sound/989snd/sound_handler.h @@ -24,5 +24,6 @@ class SoundHandler { virtual void SetPMod(s32 mod) = 0; virtual void SetPBend(s32 /*mod*/){}; virtual void SetRegister(u8 /*reg*/, u8 /*value*/) {} + virtual u32 SoundID() const { return -1; } }; } // namespace snd diff --git a/game/sound/sdshim.cpp b/game/sound/sdshim.cpp index 12247d2628..251c9fb00e 100644 --- a/game/sound/sdshim.cpp +++ b/game/sound/sdshim.cpp @@ -9,7 +9,7 @@ #include "fmt/core.h" -std::shared_ptr voices[4]; +std::shared_ptr voices[kNVoices]; u8 spu_memory[0x15160 * 10]; static sceSdTransIntrHandler trans_handler[2] = {nullptr, nullptr}; @@ -21,7 +21,7 @@ u32 sceSdGetSwitch(u32 entry) { } snd::Voice* voice_from_entry(u32 entry) { - u32 it = entry & 3; + u32 it = entry % kNVoices; return voices[it].get(); } @@ -66,7 +66,6 @@ void sceSdSetSwitch(u32 entry, u32 value) { void sceSdSetAddr(u32 entry, u32 value) { [[maybe_unused]] u32 core = entry & 1; [[maybe_unused]] u32 voice_id = (entry >> 1) & 0x1f; - auto* voice = voice_from_entry(voice_id); if (!voice) { return; @@ -123,10 +122,10 @@ void sceSdSetTransIntrHandler(s32 channel, sceSdTransIntrHandler handler, void* userdata[channel] = data; } -u32 sceSdVoiceTrans(s32 channel, s32 mode, void* iop_addr, u32 spu_addr, u32 size) { +u32 sceSdVoiceTrans(s32 channel, s32 mode, const void* iop_addr, u32 spu_addr, u32 size) { memcpy(&spu_memory[spu_addr], iop_addr, size); if (trans_handler[channel] != nullptr) { - trans_handler[channel](channel, userdata); + trans_handler[channel](channel, userdata[channel]); } return size; } diff --git a/game/sound/sdshim.h b/game/sound/sdshim.h index fd2eb4dc52..fc0a9dfe58 100644 --- a/game/sound/sdshim.h +++ b/game/sound/sdshim.h @@ -19,7 +19,8 @@ #define SD_S_KOFF (0x16 << 8) #define SD_VOICE(_core, _v) ((_core) | ((_v) << 1)) -extern std::shared_ptr voices[4]; +constexpr int kNVoices = 8; +extern std::shared_ptr voices[kNVoices]; extern u8 spu_memory[0x15160 * 10]; using sceSdTransIntrHandler = int (*)(int, void*); @@ -30,4 +31,4 @@ void sceSdSetSwitch(u32 entry, u32 value); void sceSdSetAddr(u32 entry, u32 value); void sceSdSetParam(u32 entry, u32 value); void sceSdSetTransIntrHandler(s32 channel, sceSdTransIntrHandler, void* data); -u32 sceSdVoiceTrans(s32 channel, s32 mode, void* iop_addr, u32 spu_addr, u32 size); +u32 sceSdVoiceTrans(s32 channel, s32 mode, const void* iop_addr, u32 spu_addr, u32 size); diff --git a/game/sound/sndshim.cpp b/game/sound/sndshim.cpp index 5831010805..c52f2e2f83 100644 --- a/game/sound/sndshim.cpp +++ b/game/sound/sndshim.cpp @@ -105,6 +105,14 @@ void snd_StopSound(s32 sound_handle) { } } +u32 snd_GetSoundID(s32 sound_handle) { + if (player) { + return player->GetSoundID(sound_handle); + } else { + return -1; + } +} + void snd_SetSoundVolPan(s32 sound_handle, s32 vol, s32 pan) { if (player) { player->SetSoundVolPan(sound_handle, vol, pan); @@ -220,6 +228,26 @@ snd::BankHandle snd_BankLoadEx(const char* filename, } } +namespace { +bool started = false; +std::vector sbk_data; +} // namespace + +void snd_BankLoadFromIOPPartialEx_Start() { + started = true; + sbk_data.clear(); +} + +void snd_BankLoadFromIOPPartialEx(const u8* data, u32 length, u32 spu_mem_loc, u32 spu_mem_size) { + sbk_data.insert(sbk_data.end(), data, data + length); +} +void snd_BankLoadFromIOPPartialEx_Completion() { + ASSERT(started); + started = false; + player->LoadBank(std::span(sbk_data)); + sbk_data.clear(); +} + s32 snd_GetVoiceStatus(s32 voice) { // hacky thincg to say that voice 0 is uses allocated if (voice == 0) { diff --git a/game/sound/sndshim.h b/game/sound/sndshim.h index 998029803c..bb0f07e5f1 100644 --- a/game/sound/sndshim.h +++ b/game/sound/sndshim.h @@ -35,6 +35,7 @@ void snd_SetPanTable(s16* table); void snd_SetPlayBackMode(s32 mode); s32 snd_SoundIsStillPlaying(s32 sound_handle); void snd_StopSound(s32 sound_handle); +u32 snd_GetSoundID(s32 sound_handle); void snd_SetSoundVolPan(s32 sound_handle, s32 vol, s32 pan); void snd_SetMasterVolume(s32 which, s32 volume); void snd_UnloadBank(snd::BankHandle bank_handle); @@ -69,6 +70,11 @@ snd::BankHandle snd_BankLoadEx(const char* filepath, s32 data_offset, u32 spu_mem_loc, u32 spu_mem_size); + +void snd_BankLoadFromIOPPartialEx_Start(); +void snd_BankLoadFromIOPPartialEx(const u8* data, u32 length, u32 spu_mem_loc, u32 spu_mem_size); +void snd_BankLoadFromIOPPartialEx_Completion(); + s32 snd_GetVoiceStatus(s32 voice); s32 snd_GetFreeSPUDMA(); void snd_FreeSPUDMA(s32 channel); diff --git a/game/system/IOP_Kernel.cpp b/game/system/IOP_Kernel.cpp index 10ce6e88fa..56b8d663ff 100644 --- a/game/system/IOP_Kernel.cpp +++ b/game/system/IOP_Kernel.cpp @@ -2,6 +2,7 @@ #include +#include "common/global_profiler/GlobalProfiler.h" #include "common/log/log.h" #include "common/util/Assert.h" #include "common/util/FileUtil.h" @@ -27,12 +28,27 @@ void IopThread::functionWrapper() { } } +IOP_Kernel::IOP_Kernel() { + // this ugly hack + threads.reserve(16); + CreateThread("null-thread", nullptr, 0); + CreateMbx(); + CreateSema(0, 0, 0, 0); + kernel_thread = co_active(); + m_start_time = time_point_cast(steady_clock::now()); +} + /* ** ----------------------------------------------------------------------------- ** Functions callable by threads ** ----------------------------------------------------------------------------- */ +u32 IOP_Kernel::GetSystemTimeLow() { + auto delta_time = time_point_cast(steady_clock::now()) - m_start_time; + return delta_time.count() * 36.864; +} + /*! * Create a new thread. Will not run the thread. */ @@ -89,6 +105,12 @@ void IOP_Kernel::SleepThread() { leaveThread(); } +void IOP_Kernel::YieldThread() { + ASSERT(_currentThread); + _currentThread->state = IopThread::State::Ready; + leaveThread(); +} + /*! * Wake up a thread. Doesn't run it immediately though. */ @@ -118,6 +140,105 @@ s32 IOP_Kernel::WaitSema(s32 id) { return KE_OK; } +s32 IOP_Kernel::ClearEventFlag(s32 id, u32 pattern) { + auto& ef = event_flags.at(id); + // yes, this seems backward, but the manual says this is how it works. + ef.value &= pattern; + return 0; +} + +namespace { +bool event_flag_check(u32 pattern, u32 check_pattern, u32 mode) { + if (mode & 1) { + // or + return (pattern & check_pattern); + } else { + // and + return (pattern & check_pattern) == check_pattern; + } +} +} // namespace + +s32 IOP_Kernel::WaitEventFlag(s32 flag, u32 pattern, u32 mode) { + auto& ef = event_flags.at(flag); + // check to see if we already match + if (event_flag_check(ef.value, pattern, mode)) { + if (mode & 0x10) { + ef.value = 0; + } + return KE_OK; + } else { + if (!ef.multiple_waiters_allowed && !ef.wait_list.empty()) { + lg::die("Multiple thread trying to wait on an event flag, but this option was not enabled."); + } + + auto& wait_entry = ef.wait_list.emplace_back(); + wait_entry.pattern = pattern; + wait_entry.mode = mode; + wait_entry.thread = _currentThread; + + _currentThread->state = IopThread::State::Wait; + _currentThread->waitType = IopThread::Wait::EventFlag; + leaveThread(); + + return KE_OK; + } +} + +s32 IOP_Kernel::SetEventFlag(s32 flag, u32 pattern) { + auto& ef = event_flags.at(flag); + ef.value |= pattern; + + for (auto it = ef.wait_list.begin(); it != ef.wait_list.end();) { + if (event_flag_check(ef.value, it->pattern, it->mode)) { + if (it->mode & 0x10) { + ef.value = 0; + } + it->thread->waitType = IopThread::Wait::None; + it->thread->state = IopThread::State::Ready; + it = ef.wait_list.erase(it); + } else { + ++it; + } + } + + return KE_OK; +} + +s32 IOP_Kernel::ReceiveMbx(void** msg, s32 id) { + auto& box = mbxs.at(id); + if (!box.messages.empty()) { + auto ret = PollMbx(msg, id); + ASSERT(ret == KE_OK); + return KE_OK; + } + + ASSERT(!box.wait_thread); // don't know how to deal with this, hopefully doesn't come up. + + box.wait_thread = _currentThread; + _currentThread->state = IopThread::State::Wait; + _currentThread->waitType = IopThread::Wait::Messagebox; + leaveThread(); + + auto ret = PollMbx(msg, id); + ASSERT(ret == KE_OK); + return KE_OK; +} + +s32 IOP_Kernel::SendMbx(s32 mbx, void* value) { + ASSERT(mbx < (s32)mbxs.size()); + auto& box = mbxs[mbx]; + box.messages.push(value); + auto* to_run = box.wait_thread; + + if (to_run) { + box.wait_thread = nullptr; + to_run->waitType = IopThread::Wait::None; + to_run->state = IopThread::State::Ready; + } + return 0; +} + s32 IOP_Kernel::SignalSema(s32 id) { auto& sema = semas.at(id); @@ -265,6 +386,9 @@ std::optional IOP_Kernel::dispatch() { // Run until all threads are idle IopThread* next = schedNext(); + if (next) { + prof().root_event(); + } while (next != nullptr) { // Check vblank interrupt if (vblank_handler != nullptr && vblank_recieved) { @@ -272,6 +396,7 @@ std::optional IOP_Kernel::dispatch() { vblank_recieved = false; } // printf("[IOP Kernel] Dispatch %s (%d)\n", next->name.c_str(), next->thID); + auto p = scoped_prof(next->name.c_str()); runThread(next); updateDelay(); processWakeups(); diff --git a/game/system/IOP_Kernel.h b/game/system/IOP_Kernel.h index b8a5b51389..3409d639a9 100644 --- a/game/system/IOP_Kernel.h +++ b/game/system/IOP_Kernel.h @@ -53,14 +53,10 @@ struct IopThread { Dormant, }; - enum class Wait { - None, - Semaphore, - Delay, - }; + enum class Wait { None, Semaphore, Delay, Messagebox, EventFlag }; - IopThread(std::string n, void (*f)(), s32 ID, u32 priority) - : name(std::move(n)), function(f), priority(priority), thID(ID) { + IopThread(std::string n, void (*f)(), s32 ID, u32 pri) + : name(std::move(n)), function(f), priority(pri), thID(ID) { thread = co_create(0x300000, functionWrapper); } @@ -79,8 +75,12 @@ struct IopThread { struct Semaphore { enum class attribute { fifo, prio }; - Semaphore(attribute attr, s32 option, s32 init_count, s32 max_count) - : attr(attr), option(option), count(init_count), initCount(init_count), maxCount(max_count) {} + Semaphore(attribute _attr, s32 _option, s32 init_count, s32 max_count) + : attr(_attr), + option(_option), + count(init_count), + initCount(init_count), + maxCount(max_count) {} attribute attr{attribute::fifo}; u32 option{0}; @@ -91,17 +91,26 @@ struct Semaphore { std::list wait_list; }; +struct EventFlagWaiter { + IopThread* thread = nullptr; + u32 pattern = 0; + u32 mode = 0; +}; + +struct EventFlag { + bool multiple_waiters_allowed = false; + u32 value = 0; + std::list wait_list; +}; + +struct Messagebox { + std::queue messages; + IopThread* wait_thread = nullptr; +}; + class IOP_Kernel { public: - IOP_Kernel() { - // this ugly hack - threads.reserve(16); - CreateThread("null-thread", nullptr, 0); - CreateMbx(); - CreateSema(0, 0, 0, 0); - kernel_thread = co_active(); - } - + IOP_Kernel(); s32 CreateThread(std::string n, void (*f)(), u32 priority); s32 ExitThread(); void StartThread(s32 id); @@ -109,6 +118,7 @@ class IOP_Kernel { void SleepThread(); void WakeupThread(s32 id); void iWakeupThread(s32 id); + void YieldThread(); std::optional dispatch(); void set_rpc_queue(iop::sceSifQueueData* qd, u32 thread); void rpc_loop(iop::sceSifQueueData* qd); @@ -137,30 +147,29 @@ class IOP_Kernel { */ s32 PollMbx(void** msg, s32 mbx) { ASSERT(mbx < (s32)mbxs.size()); - s32 gotSomething = mbxs[mbx].empty() ? 0 : 1; + s32 gotSomething = mbxs[mbx].messages.empty() ? 0 : 1; if (gotSomething) { - void* thing = mbxs[mbx].front(); + void* thing = mbxs[mbx].messages.front(); if (msg) { *msg = thing; } - mbxs[mbx].pop(); + mbxs[mbx].messages.pop(); } return gotSomething ? KE_OK : KE_MBOX_NOMSG; } - s32 PeekMbx(s32 mbx) { return !mbxs[mbx].empty(); } + s32 PeekMbx(s32 mbx) { return !mbxs[mbx].messages.empty(); } + s32 MbxSize(s32 mbx) { return mbxs[mbx].messages.size(); } + + s32 ReceiveMbx(void** msg, s32 id); /*! * Push something into a mbx */ - s32 SendMbx(s32 mbx, void* value) { - ASSERT(mbx < (s32)mbxs.size()); - mbxs[mbx].push(value); - return 0; - } + s32 SendMbx(s32 mbx, void* value); s32 CreateSema(s32 attr, s32 option, s32 init_count, s32 max_count) { s32 id = semas.size(); @@ -172,11 +181,27 @@ class IOP_Kernel { s32 SignalSema(s32 id); s32 PollSema(s32 id); + s32 CreateEventFlag(s32 attr, s32 option, u32 init_pattern) { + ASSERT(option == 0); + s32 id = event_flags.size(); + auto& flag = event_flags.emplace_back(); + flag.value = init_pattern; + flag.multiple_waiters_allowed = attr == 2; + return id; + } + + s32 WaitEventFlag(s32 flag, u32 pattern, u32 mode); + s32 SetEventFlag(s32 flag, u32 pattern); + + s32 ClearEventFlag(s32 id, u32 pattern); + s32 RegisterVblankHandler(int (*handler)(void*)) { vblank_handler = handler; return 0; } + u32 GetSystemTimeLow(); + void signal_vblank() { vblank_recieved = true; }; bool sif_busy(u32 id); @@ -198,17 +223,20 @@ class IOP_Kernel { IopThread* schedNext(); std::optional nextWakeup(); - s32 (*vblank_handler)(void*); + s32 (*vblank_handler)(void*) = nullptr; std::atomic_bool vblank_recieved = false; cothread_t kernel_thread; s32 _nextThID = 0; IopThread* _currentThread = nullptr; std::vector threads; - std::vector> mbxs; + std::vector mbxs; std::vector sif_records; std::vector semas; + std::vector event_flags; std::queue wakeup_queue; bool mainThreadSleep = false; std::mutex sif_mtx, wakeup_mtx; + + time_stamp m_start_time; }; diff --git a/game/system/iop_thread.cpp b/game/system/iop_thread.cpp index 0ac74120d3..910a29da79 100644 --- a/game/system/iop_thread.cpp +++ b/game/system/iop_thread.cpp @@ -1,5 +1,7 @@ #include "iop_thread.h" +#include "common/global_profiler/GlobalProfiler.h" + #ifdef __linux__ #include #elif _WIN32 @@ -57,6 +59,7 @@ void* IOP::iop_alloc(int size) { void IOP::wait_run_iop( std::chrono::time_point wakeup) { + auto p = scoped_prof("krnlw"); std::unique_lock lk(run_cv_mutex); iop_run_cv.wait_until(lk, wakeup); } diff --git a/goal_src/jak3/engine/scene/scene.gc b/goal_src/jak3/engine/scene/scene.gc index 4f3437b253..b61d1ec1cf 100644 --- a/goal_src/jak3/engine/scene/scene.gc +++ b/goal_src/jak3/engine/scene/scene.gc @@ -905,8 +905,6 @@ (defstate wait (scene-player) :virtual #t :enter (behavior ((arg0 symbol)) - (format 0 "scene-player: skipping scene~%") - (set! (-> self aborted?) #t) (set-time! (-> self state-time)) (if (= (-> *game-info* demo-state) 101) (set-setting! 'audio-language #f 0.0 5) @@ -1158,7 +1156,6 @@ ) ) ) - (not (-> self aborted?)) ) (set-blackout-frames (seconds 0.1)) (suspend) @@ -1549,7 +1546,6 @@ (logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons triangle)) #t ) - (none) ) false-func ) @@ -1577,10 +1573,6 @@ ) ) ) - (#when PC_PORT ;; og:preserve-this until overlord2 is done - (backup-load-state-and-set-cmds *load-state* (-> self anim command-list)) - (restore-load-state-and-cleanup *load-state*) - ) (if (and (-> self wait) *target* (focus-test? *target* grabbed)) (go-virtual release) ) diff --git a/goal_src/jak3/game.gp b/goal_src/jak3/game.gp index 7ab31948d1..766c047422 100644 --- a/goal_src/jak3/game.gp +++ b/goal_src/jak3/game.gp @@ -492,8 +492,9 @@ "WASSTAD4" "WASSTAD5" "WASSTAD6" "WASTOAD" "WASTURT") ;; Jak 3 has no MUS files -;; (copy-mus-files "" "TWEAKVAL") - +(defstep :in "$ISO/RES/TWEAKVAL.MUS" + :tool 'copy + :out '("$OUT/iso/TWEAKVAL.MUS")) ;;;;;;;;;;;;;;;;;;;;; ;; Text ;;;;;;;;;;;;;;;;;;;;; @@ -531,6 +532,7 @@ "$OUT/iso/7COMMON.TXT" "$OUT/iso/0SUBTI2.TXT" "$OUT/iso/VAGDIR.AYB" + "$OUT/iso/TWEAKVAL.MUS" ,@(reverse *all-vis*) ,@(reverse *all-str*) ,@(reverse *all-sbk*) diff --git a/test/decompiler/reference/jak3/engine/scene/scene_REF.gc b/test/decompiler/reference/jak3/engine/scene/scene_REF.gc index c3a0065b18..b11968a00d 100644 --- a/test/decompiler/reference/jak3/engine/scene/scene_REF.gc +++ b/test/decompiler/reference/jak3/engine/scene/scene_REF.gc @@ -1707,16 +1707,13 @@ (the-as (function process-drawable symbol) (if (logtest? (-> self scene scene-flags) (scene-flags scf1)) - (lambda :behavior scene-player - () - (when (cpad-pressed? 0 triangle) - (set! (-> self aborted?) #t) - (logclear! (-> *cpad-list* cpads 0 button0-abs 0) (pad-buttons triangle)) - (logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons triangle)) - #t - ) - (none) - ) + (lambda :behavior scene-player () (when (cpad-pressed? 0 triangle) + (set! (-> self aborted?) #t) + (logclear! (-> *cpad-list* cpads 0 button0-abs 0) (pad-buttons triangle)) + (logclear! (-> *cpad-list* cpads 0 button0-rel 0) (pad-buttons triangle)) + #t + ) + ) false-func ) ) @@ -2169,7 +2166,3 @@ ) this ) - - - -