#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!! 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: {}", user_cmd->name); 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; internal_vag_cmd = FindVagStreamId(vag_cmd->id); if (internal_vag_cmd) { 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 {}", 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); SendMbx(g_nISOMbx, &sLoadDGO); WaitMbx(g_nDGOMbx); 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 {}", 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 {}", 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) { bank_info->snd_handle = 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