Files
jak-project/game/overlord/jak3/iso.cpp
T
2024-07-26 09:42:28 -04:00

1796 lines
61 KiB
C++

#include "iso.h"
#include <cstring>
#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