jak-project/game/overlord/iso.cpp
Ziemas 766b328c97
Overlord sound player (#1239)
* Accept player RPC commands in overlord

* Remove the .projectile file

I use emacs for everything so I don't want it to only look at the goal code.

* Fill out most of the unique player structs

* Decompile most of ssound.c

* Silence some spam

* Comment out WaitSema instance

* Add a file with definitions for snd_ functions

Makes it compile without commenting them out.

Maybe it'd be nice to maintain the original API usage in overlord for
similarity and shim them to whatever API the player uses.

* Make SoundBank statically sized again.

Didn't realise this was used in an array. MSVC should be happy again.

Not sure what the actual size of these should be.

* Fix logic issue

* Finish RPC Loader

* More RPC_Player

* Play RPC command

* All the RPC commands added

* Call Music/Bank loaders

* audio: add almost all `.MUS` and `.SBK` files to build process

* Include TWEAKVAL in build output

* Load banks and music tweaks

* Comment out spam

* Sound struct unk -> is music

* Also test if empty1.sbk was found

For the sake of tests.

* Get rid of PC_DEBUG_SOUND_ENABLE

Co-authored-by: Tyler Wilding <xtvaser@gmail.com>
2022-03-22 18:53:36 -04:00

977 lines
30 KiB
C++

/*!
* @file iso.cpp
* CD/DVD Reading.
* This is a huge mess
*/
#include "common/log/log.h"
#include <cstring>
#include <cstdio>
#include "iso.h"
#include "iso_cd.h"
#include "iso_queue.h"
#include "iso_api.h"
#include "game/sce/iop.h"
#include "stream.h"
#include "dma.h"
#include "fake_iso.h"
#include "game/common/dgo_rpc_types.h"
#include "common/util/Assert.h"
using namespace iop;
u32 ISOThread();
u32 DGOThread();
u32 ProcessVAGData(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 CopyDataToEE(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 CopyDataToIOP(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
u32 NullCallback(IsoMessage* _cmd, IsoBufferHeader* buffer_header);
constexpr int VAGDIR_SIZE = 0x28b4;
constexpr int LOADING_SCREEN_SIZE = 0x800000;
constexpr u32 LOADING_SCREEN_DEST_ADDR = 0x1000000;
IsoFs* isofs;
u32 iso_init_flag;
s32 sync_mbx;
s32 iso_mbx;
s32 dgo_mbx;
s32 iso_thread;
s32 dgo_thread;
s32 str_thread;
s32 play_thread;
u8 gVagDir[VAGDIR_SIZE];
u32 gPlayPos;
static RPC_Dgo_Cmd sRPCBuff[1]; // todo move...
DgoCommand scmd;
void iso_init_globals() {
isofs = nullptr;
iso_init_flag = 0;
sync_mbx = 0;
iso_mbx = 0;
dgo_mbx = 0;
iso_thread = 0;
dgo_thread = 0;
str_thread = 0;
play_thread = 0;
memset(gVagDir, 0, sizeof(gVagDir));
gPlayPos = 0;
memset(sRPCBuff, 0, sizeof(sRPCBuff));
memset(&scmd, 0, sizeof(DgoCommand));
}
/*!
* Initialize the ISO Driver.
* Requires a buffer large enough to hold 3 sector (or 4 if you have DUP files)
*/
void InitDriver(u8* buffer) {
MsgPacket msg_packet;
if (!isofs->init(buffer)) {
// succesful init!
iso_init_flag = 0;
}
// you idiots, you're giving the kernel a pointer to a stack variable!
// (this is fixed in Jak 1 Japan and NTSC Greatest Hits)
SendMbx(sync_mbx, &msg_packet);
}
/*!
* Does the messagebox have a message in it?
*/
u32 LookMbx(s32 mbx) {
MsgPacket* msg_packet;
return PollMbx((&msg_packet), mbx) != KE_MBOX_NOMSG;
}
/*!
* Wait for a messagebox to have a message. This is inefficient and polls with a 100 us wait.
* This is stupid because the IOP does have much better syncronization primitives so you don't have
* to do this.
*/
void WaitMbx(s32 mbx) {
while (!LookMbx(mbx)) {
DelayThread(100);
}
}
/*!
* Initialize the ISO FileSystem system.
* Returns 0 on success.
*/
u32 InitISOFS(const char* fs_mode, const char* loading_screen) {
// in retail:
// isofs = &iso_cd;
// ADDED
if (!strcmp(fs_mode, "iso_cd")) {
isofs = &iso_cd_;
} else if (!strcmp(fs_mode, "fakeiso")) {
isofs = &fake_iso;
} else {
printf("[OVERLORD ISO] ISOFS has unknown fs_mode %s\n", fs_mode);
}
// END ADDED
// mark us as NOT initialized.
iso_init_flag = 1;
// TODO ADD
// while(!DMA_SendToSPUAndSync(&VAG_SilentLoop, 0x30, gTrapSRAM)) {
// DelayThread(1000);
// }
// INITIALIZE MESSAGE BOXES
MbxParam mbx_param;
mbx_param.attr = 0;
mbx_param.option = 0;
iso_mbx = CreateMbx(&mbx_param);
if (iso_mbx <= 0) {
return 1;
}
mbx_param.attr = 0;
mbx_param.option = 0;
dgo_mbx = CreateMbx(&mbx_param);
if (dgo_mbx <= 0) {
return 1;
}
mbx_param.attr = 0;
mbx_param.option = 0;
sync_mbx = CreateMbx(&mbx_param);
if (sync_mbx <= 0) {
return 1;
}
// INITIALIZE THREADS
ThreadParam thread_param;
thread_param.attr = TH_C;
thread_param.initPriority = 100;
thread_param.stackSize = 0x1000;
thread_param.option = 0;
thread_param.entry = (void*)ISOThread;
strcpy(thread_param.name, "ISOThread");
iso_thread = CreateThread(&thread_param);
if (iso_thread <= 0) {
return 1;
}
thread_param.attr = TH_C;
thread_param.initPriority = 98;
thread_param.stackSize = 0x800;
thread_param.option = 0;
thread_param.entry = (void*)DGOThread;
strcpy(thread_param.name, "DGOThread");
dgo_thread = CreateThread(&thread_param);
if (dgo_thread <= 0) {
return 1;
}
thread_param.attr = TH_C;
thread_param.initPriority = 97;
thread_param.stackSize = 0x800;
thread_param.option = 0;
thread_param.entry = (void*)STRThread;
strcpy(thread_param.name, "STRThread");
str_thread = CreateThread(&thread_param);
if (str_thread <= 0) {
return 1;
}
thread_param.attr = TH_C;
thread_param.initPriority = 97;
thread_param.stackSize = 0x800;
thread_param.option = 0;
thread_param.entry = (void*)PLAYThread;
strcpy(thread_param.name, "PLAYThread");
play_thread = CreateThread(&thread_param);
if (play_thread <= 0) {
return 1;
}
// Start the threads!
StartThread(iso_thread, 0);
StartThread(dgo_thread, 0);
StartThread(str_thread, 0);
StartThread(play_thread, 0);
// wait for ISO Thread to initialize
WaitMbx(sync_mbx);
// LOAD VAGDIR file
FileRecord* vagdir_file = FindISOFile("VAGDIR.AYB");
if (vagdir_file) {
LoadISOFileToIOP(vagdir_file, gVagDir, VAGDIR_SIZE);
}
FileRecord* loading_screen_file = FindISOFile(loading_screen);
if (loading_screen_file) {
LoadISOFileToEE(loading_screen_file, LOADING_SCREEN_DEST_ADDR, LOADING_SCREEN_SIZE);
}
// should be set by ISOThread to 0 before the WaitMbx(sync_mbx);
return iso_init_flag;
}
/*!
* Find a file by name. Return nullptr if it fails.
*/
FileRecord* FindISOFile(const char* name) {
return isofs->find(name);
}
/*!
* Get the length of an ISO File by FileRecord
*/
u32 GetISOFileLength(FileRecord* f) {
return isofs->get_length(f);
}
struct VagDirEntry {
union {
char name[8];
s32 name_as_s32s[2];
};
u32 unknown;
};
static_assert(sizeof(VagDirEntry) == 12, "bad size of VagDirEntry");
/*!
* Find VAG file by "name", where name is 8 bytes (chars with spaces at the end, treated as two
* s32's). Returns pointer to name in the VAGDIR file data.
*/
VagDirEntry* FindVAGFile(s32* name) {
// First 4 bytes of VAGDIR file are the number of entries.
// Next is a list of entries.
VagDirEntry* entry = (VagDirEntry*)(gVagDir + 4);
// loop over entries
for (s32 idx = 0; idx < *(s32*)gVagDir; idx++) {
// check if matching name
if (entry->name_as_s32s[0] == name[0] && entry->name_as_s32s[1] == name[1]) {
return entry;
}
entry++;
}
return nullptr;
}
/*!
* The CD/DVD Reading Thread. This is a mess.
*/
u32 ISOThread() {
// Initialize!
InitBuffers();
auto temp_buffer = AllocateBuffer(BUFFER_PAGE_SIZE);
InitDriver(temp_buffer->get_data()); // unblocks InitISOFS's WaitMbx
FreeBuffer(temp_buffer);
VagCommand* in_progress_vag_command = nullptr;
// main CD/DVD read loop
for (;;) {
/////////////////////////////////////
// Receive Messages and Add to Queue
/////////////////////////////////////
// receive a message
IsoMessage* msg_from_mbx;
IsoCommandLoadSingle* load_single_cmd;
s32 mbx_status = PollMbx((MsgPacket**)(&msg_from_mbx), iso_mbx);
load_single_cmd = (IsoCommandLoadSingle*)msg_from_mbx;
if (mbx_status == 0) {
// we got a new message!
// initialize fields of the message
msg_from_mbx->callback_buffer = nullptr;
msg_from_mbx->ready_for_data = 1;
msg_from_mbx->callback_function = NullCallback;
msg_from_mbx->fd = nullptr;
if (msg_from_mbx->cmd_id == LOAD_TO_EE_CMD_ID || msg_from_mbx->cmd_id == LOAD_TO_IOP_CMD_ID ||
msg_from_mbx->cmd_id == LOAD_TO_EE_OFFSET_CMD_ID) {
// A Simple File Load, add it to the queue
if (QueueMessage(msg_from_mbx, 2, "LoadSingle")) {
// if queued successfully, start by opening the file:
if (load_single_cmd->cmd_id == LOAD_TO_EE_OFFSET_CMD_ID) {
load_single_cmd->fd =
isofs->open(load_single_cmd->file_record, load_single_cmd->offset);
} else {
// open takes -1 as "no offset", same as 0.
load_single_cmd->fd = isofs->open(load_single_cmd->file_record, -1);
}
// Check to see if it opened correctly:
if (!load_single_cmd->fd) {
// nope, set the status to indicate we failed
load_single_cmd->status = CMD_STATUS_FAILED_TO_OPEN;
// remove us from the queue...
UnqueueMessage(load_single_cmd);
// and wake up whoever requested this.
ReturnMessage(load_single_cmd);
} else {
// yep, opened correctly. Set up the pointers/sizes
load_single_cmd->dst_ptr = load_single_cmd->dest_addr;
load_single_cmd->bytes_done = 0;
// by default, copy size is the full file.
load_single_cmd->length_to_copy = isofs->get_length(load_single_cmd->file_record);
if (load_single_cmd->length_to_copy == 0) {
// if we get zero for some reason, use the commanded length.
ASSERT(false);
load_single_cmd->length_to_copy = load_single_cmd->length;
} else if (load_single_cmd->length < load_single_cmd->length_to_copy) {
// if we ask for less than the full length, use the smaller value.
load_single_cmd->length_to_copy = load_single_cmd->length;
}
// set status and callback function.
load_single_cmd->status = CMD_STATUS_IN_PROGRESS;
switch (msg_from_mbx->cmd_id) {
case LOAD_TO_EE_CMD_ID:
case LOAD_TO_EE_OFFSET_CMD_ID:
msg_from_mbx->callback_function = CopyDataToEE;
break;
case LOAD_TO_IOP_CMD_ID:
msg_from_mbx->callback_function = CopyDataToIOP;
break;
}
}
}
} else if (msg_from_mbx->cmd_id == LOAD_DGO_CMD_ID) {
// Got a DGO command. There is one LoadDGO command for the entire DGO.
if (QueueMessage(msg_from_mbx, 0, "LoadDGO")) {
// queued successfully, open the file.
load_single_cmd->fd = isofs->open(load_single_cmd->file_record, -1);
if (!load_single_cmd->fd) {
// failed to open, return error
load_single_cmd->status = CMD_STATUS_FAILED_TO_OPEN;
UnqueueMessage(load_single_cmd);
ReturnMessage(load_single_cmd);
} else {
// init DGO state machine and register as the callback.
load_single_cmd->status = CMD_STATUS_IN_PROGRESS;
((DgoCommand*)load_single_cmd)->dgo_state = DgoState::Init;
load_single_cmd->callback_function = RunDGOStateMachine;
}
}
} else if (msg_from_mbx->cmd_id == LOAD_SOUND_BANK) {
// if there's an in progress vag command, try again.
if (!in_progress_vag_command || !in_progress_vag_command->field_0x3c) {
auto buff = TryAllocateBuffer(BUFFER_PAGE_SIZE);
if (!buff) {
// no buffers, try again.
SendMbx(iso_mbx, msg_from_mbx);
} else {
auto* cmd = (SoundBankLoadCommand*)msg_from_mbx;
isofs->load_sound_bank(cmd->bank_name, cmd->bank);
FreeBuffer(buff);
ReturnMessage(msg_from_mbx);
}
} else {
// just try again...
SendMbx(iso_mbx, msg_from_mbx);
}
} else if (msg_from_mbx->cmd_id == LOAD_MUSIC) {
// if there's an in progress vag command, try again.
if (!in_progress_vag_command || !in_progress_vag_command->field_0x3c) {
auto buff = TryAllocateBuffer(BUFFER_PAGE_SIZE);
if (!buff) {
// no buffers, try again.
SendMbx(iso_mbx, msg_from_mbx);
} else {
auto* cmd = (MusicLoadCommand*)msg_from_mbx;
isofs->load_music(cmd->music_name, cmd->music_handle);
FreeBuffer(buff);
ReturnMessage(msg_from_mbx);
}
} else {
// just try again...
SendMbx(iso_mbx, msg_from_mbx);
}
}
else {
printf("[OVERLORD] Unknown ISOThread message id 0x%x\n", msg_from_mbx->cmd_id);
}
// TODO magic number
} else if (mbx_status == -0x1a9) {
return 0;
}
////////////////////////////
// Handle Sound (TODO)
////////////////////////////
////////////////////////////
// Begin a read
////////////////////////////
IsoBufferHeader* read_buffer = nullptr;
IsoMessage* cmd_to_process = GetMessage();
if (cmd_to_process) { // okay, there's a command queued that we should process
// prep for a read !! DANGER !! - this read _may_ complete after the command is done.
// At this point we don't know if the command actually needs another read or not!
if (cmd_to_process->callback_function == ProcessVAGData) {
read_buffer = AllocateBuffer(STR_BUFFER_DATA_SIZE);
} else {
read_buffer = AllocateBuffer(BUFFER_PAGE_SIZE);
}
if (!read_buffer) {
// there aren't enough buffers. give up on this command for now.
cmd_to_process = nullptr;
} else {
// kick off read
if (cmd_to_process->callback_function == ProcessVAGData) {
cmd_to_process->status =
isofs->begin_read(cmd_to_process->fd, read_buffer->get_data(), STR_BUFFER_DATA_SIZE);
} else {
cmd_to_process->status =
isofs->begin_read(cmd_to_process->fd, read_buffer->get_data(), BUFFER_PAGE_SIZE);
}
// if we have bad status, kill read buffer
if (cmd_to_process->status != CMD_STATUS_IN_PROGRESS) {
FreeBuffer(read_buffer);
read_buffer = nullptr;
cmd_to_process = nullptr;
}
}
}
if (!cmd_to_process) {
// drive is doing nothing, make sure the DVD is still in there.
isofs->poll_drive();
}
// Deal with completed reads. NOTE - this can close files and terminate return commands!
ProcessMessageData();
if (!read_buffer) {
// didn't actually start a read, just delay for a bit I guess.
DelayThread(100);
} else {
// attempt to sync read. If we closed the file mid-read in ProcessMessageData, this returns
// an error code.
u32 read_status = isofs->sync_read();
if (read_status == CMD_STATUS_READ_ERR) {
// closed file mid-read, or the read failed. Either way we can't give this read buffer to
// anybody, so we should just free it.
FreeBuffer(read_buffer);
} else {
// read is good!
cmd_to_process->status = read_status;
// setup the buffer for the callback.
if (cmd_to_process->callback_function == ProcessVAGData) {
read_buffer->data = read_buffer->get_data();
read_buffer->data_size = STR_BUFFER_DATA_SIZE;
} else {
read_buffer->data = read_buffer->get_data();
read_buffer->data_size = BUFFER_PAGE_SIZE;
}
// add buffer to linked list of buffers.
if (!cmd_to_process->callback_buffer) {
cmd_to_process->callback_buffer = read_buffer;
} else {
auto* bh = cmd_to_process->callback_buffer;
while (bh->next) {
bh = (IsoBufferHeader*)bh->next;
}
bh->next = read_buffer;
}
}
}
} // for
return 0;
}
/*!
* Handler for DGO data buffers.
*/
u32 RunDGOStateMachine(IsoMessage* _cmd, IsoBufferHeader* buffer) {
auto* cmd = (DgoCommand*)_cmd;
u32 return_value = CMD_STATUS_IN_PROGRESS;
u8* unprocessed_data = (u8*)buffer->data;
u32 bytes_left = buffer->data_size;
// loop until we've read all the data
while (bytes_left) {
// printf("run DGO in state %d (%s) with %d unprocessed buffered bytes\n", cmd->dgoState,
// names[cmd->dgoState], buffer->data_size);
switch (cmd->dgo_state) {
case DgoState::Init: // init
cmd->bytes_processed = 0;
// start by reading header.
cmd->dgo_state = DgoState::Read_Header;
cmd->finished_first_obj = 0;
cmd->want_abort = 0;
break;
case DgoState::Read_Header: // read dgo header. If we are unlucky this crosses a boundary
// and we have to do this in two chunks
{
u32 bytes_to_read = sizeof(DgoHeader) - cmd->bytes_processed;
if (bytes_to_read > bytes_left) {
bytes_to_read = bytes_left;
}
// copy to our local storage
memcpy((u8*)&cmd->dgo_header + cmd->bytes_processed, unprocessed_data, bytes_to_read);
unprocessed_data += bytes_to_read;
bytes_left -= bytes_to_read;
cmd->bytes_processed += bytes_to_read;
// if we are done with header
if (cmd->bytes_processed == sizeof(DgoHeader)) {
// printf("[Overlord DGO] Got DGO file header for %s with %d objects\n",
// cmd->dgo_header.name,
// cmd->dgo_header.object_count); // added
lg::info("[Overlord DGO] Got DGO file header for {} 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 there's only one object, load to top immediately
cmd->buffer_toggle = 0;
cmd->ee_destination_buffer = cmd->buffer_heaptop;
cmd->dgo_state = DgoState::Read_Obj_Header;
} else {
// otherwise load to buffer1 first.
cmd->buffer_toggle = 1;
cmd->ee_destination_buffer = cmd->buffer1;
cmd->dgo_state = DgoState::Read_Obj_Header;
}
}
} break;
case DgoState::Finish_Obj: // we have reached the end of an object file!
{
// EE synchronization occurs here.
// we skip this if we're loading the first object so we can double buffer the
// linking/loading process and have two in flight at a time (one loading, other linking)
if (cmd->finished_first_obj) {
s32 isSync = LookMbx(sync_mbx); // did we get a "sync" message?
if (isSync) {
// if so, this means we got a CancelDGO or NextDGO
if (cmd->want_abort) {
// we got a CancelDGO.
cmd->dgo_state = DgoState::Finish_Dgo;
break;
}
} else {
// nope, ee isn't ready. bail and wait for next run.
goto cleanup_and_return;
}
}
cmd->finished_first_obj = 1;
cmd->status = CMD_STATUS_IN_PROGRESS;
// select a buffer for next time.
if (cmd->buffer_toggle == 1) {
cmd->selectedBuffer = cmd->buffer1;
} else {
cmd->selectedBuffer = cmd->buffer2;
}
// we've processed the command, go wake up the DGO RPC thread.
// doesn't terminate the command (ReleaseMessage does this, ReturnMessage just
// wakes up the caller while keeping the command alive).
ReturnMessage(cmd);
// toggle buffer
if (cmd->buffer_toggle == 1) {
cmd->ee_destination_buffer = cmd->buffer2;
cmd->buffer_toggle = 2;
} else {
cmd->ee_destination_buffer = cmd->buffer1;
cmd->buffer_toggle = 1;
}
// setup for next run
if (cmd->objects_loaded + 1 == cmd->dgo_header.object_count) {
cmd->dgo_state = DgoState::Read_Last_Obj;
} else {
cmd->dgo_state = DgoState::Read_Obj_Header;
}
break;
}
case DgoState::Read_Last_Obj: // setup load last
{
// extra sync here
s32 sync = LookMbx(sync_mbx);
if (sync) {
if (cmd->want_abort) {
cmd->dgo_state = DgoState::Finish_Dgo;
} else {
// EE ready, no abort. Nothing in flight, so we are safe to do a top load!
cmd->ee_destination_buffer = cmd->buffer_heaptop;
cmd->buffer_toggle = 0;
cmd->dgo_state = DgoState::Read_Obj_Header;
}
} else {
goto cleanup_and_return;
}
} break;
case DgoState::Read_Obj_Header: // read object file header
{
u32 bytesToRead = sizeof(ObjectHeader) - cmd->bytes_processed;
if (bytes_left < bytesToRead) {
bytesToRead = bytes_left;
}
// for now, buffer locally
memcpy((u8*)&cmd->objHeader + cmd->bytes_processed, unprocessed_data, bytesToRead);
unprocessed_data += bytesToRead;
bytes_left -= bytesToRead;
cmd->bytes_processed += bytesToRead;
// once we're done, send the header to the EE, and start reading object data
if (cmd->bytes_processed == sizeof(ObjectHeader)) {
// printf("[Overlord DGO] Got object header for %s, object size 0x%x bytes (sent
// to 0x%p)\n",
// cmd->objHeader.name, cmd->objHeader.size, cmd->ee_destination_buffer);
DMA_SendToEE(&cmd->objHeader, sizeof(ObjectHeader), cmd->ee_destination_buffer);
DMA_Sync();
cmd->ee_destination_buffer += sizeof(ObjectHeader);
cmd->objHeader.size = (cmd->objHeader.size + 0xf) & 0xfffffff0;
cmd->dgo_state = DgoState::Read_Obj_data;
cmd->bytes_processed = 0;
}
} break;
case DgoState::Read_Obj_data: // read object file data
{
u32 bytesToRead = cmd->objHeader.size - cmd->bytes_processed;
if (bytes_left < bytesToRead) {
bytesToRead = bytes_left;
}
// send contents directly to EE
DMA_SendToEE(unprocessed_data, bytesToRead, cmd->ee_destination_buffer);
DMA_Sync();
unprocessed_data += bytesToRead;
bytes_left -= bytesToRead;
cmd->ee_destination_buffer += bytesToRead;
cmd->bytes_processed += bytesToRead;
if (cmd->bytes_processed == cmd->objHeader.size) {
cmd->objects_loaded++;
if (cmd->objects_loaded == cmd->dgo_header.object_count) {
cmd->dgo_state = DgoState::Finish_Dgo;
} else {
cmd->dgo_state = DgoState::Finish_Obj;
cmd->bytes_processed = 0;
}
}
} break;
case DgoState::Finish_Dgo: {
// done with buffer, complete. Kill the ISO thread read.
return_value = CMD_STATUS_DONE;
goto cleanup_and_return;
}
default:
printf("unknown dgoState!\n");
}
}
// printf("[DGO State Machine Complete] Out of things to read!\n");
cleanup_and_return:
if (return_value == 0) {
buffer->data = nullptr;
buffer->data_size = 0;
} else {
if (!bytes_left) {
buffer->data = nullptr;
buffer->data_size = 0;
} else {
buffer->data = unprocessed_data;
buffer->data_size = bytes_left;
}
}
return return_value;
}
/*!
* Callback for sending to EE.
*/
u32 CopyDataToEE(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
auto* cmd = (IsoCommandLoadSingle*)_cmd;
s32 bytes_to_send = cmd->length_to_copy - cmd->bytes_done;
// make sure we don't copy too much (if the buffer does not have enough data)
if (buffer_header->data_size < (u32)bytes_to_send) {
bytes_to_send = (s32)buffer_header->data_size;
}
DMA_SendToEE(buffer_header->get_data(), bytes_to_send, cmd->dest_addr);
DMA_Sync();
cmd->dest_addr += bytes_to_send;
cmd->bytes_done += bytes_to_send;
buffer_header->data = nullptr;
buffer_header->data_size = 0;
if (cmd->bytes_done == cmd->length_to_copy) {
return CMD_STATUS_DONE;
} else {
return CMD_STATUS_IN_PROGRESS;
}
}
/*!
* Callback for loading to IOP buffer.
*/
u32 CopyDataToIOP(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
auto* cmd = (IsoCommandLoadSingle*)_cmd;
s32 bytes_to_send = cmd->length_to_copy - cmd->bytes_done;
// make sure we don't copy too much (if the buffer does not have enough data)
if (buffer_header->data_size < (u32)bytes_to_send) {
bytes_to_send = (s32)buffer_header->data_size;
}
memcpy(cmd->dst_ptr, buffer_header->get_data(), bytes_to_send);
cmd->dst_ptr += bytes_to_send;
cmd->bytes_done += bytes_to_send;
buffer_header->data = nullptr;
buffer_header->data_size = 0;
if (cmd->bytes_done == cmd->length_to_copy) {
return CMD_STATUS_DONE;
} else {
return CMD_STATUS_IN_PROGRESS;
}
}
/*!
* Callback which does nothing.
*/
u32 NullCallback(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
(void)_cmd;
buffer_header->data_size = 0;
return CMD_STATUS_NULL_CB;
}
/*!
* Initialize a VagCommand.
*/
void InitVAGCmd(VagCommand* cmd, u32 x) {
cmd->field_0x30 = 0;
cmd->field_0x34 = 0;
cmd->field_0x38 = 0;
cmd->field_0x3c = x;
cmd->field_0x40 = 0;
cmd->field_0x44 = 0;
cmd->field_0x48 = 0xffffffff;
gPlayPos = 0x30;
cmd->messagebox_to_reply = 0;
cmd->thread_id = 0;
}
/*!
* Byte-swap.
*/
u32 bswap(u32 in) {
return ((in >> 0x18) & 0xff) | ((in >> 8) & 0xff00) | ((in & 0xff00) << 8) | (in << 0x18);
}
/*!
* TODO - implement.
*/
u32 ProcessVAGData(IsoMessage* _cmd, IsoBufferHeader* buffer_header) {
(void)_cmd;
(void)buffer_header;
ASSERT(false);
return 0;
}
// TODO - StopVAG
// TODO - PauseVAG
// TODO - CalculateVAGVolumes
// TODO - UnpauseVAG
// TODO - SetVAGVol
// TODO - GetPlayPos
// TODO - UpdatePlayPos
// TODO - CheckVAGStreamProgress
void* RPC_DGO(unsigned int fno, void* _cmd, int y);
void LoadDGO(RPC_Dgo_Cmd* cmd);
void LoadNextDGO(RPC_Dgo_Cmd* cmd);
void CancelDGO(RPC_Dgo_Cmd* cmd);
/*!
* DGO RPC Thread.
*/
u32 DGOThread() {
sceSifQueueData dq;
sceSifServeData serve;
// setup RPC.
CpuDisableIntr();
sceSifInitRpc(0);
sceSifSetRpcQueue(&dq, GetThreadId());
sceSifRegisterRpc(&serve, DGO_RPC_ID, RPC_DGO, sRPCBuff, nullptr, nullptr, &dq);
CpuEnableIntr();
sceSifRpcLoop(&dq);
return 0;
}
/*!
* DGO RPC Handler.
*/
void* RPC_DGO(unsigned int fno, void* _cmd, int y) {
(void)y;
auto* cmd = (RPC_Dgo_Cmd*)_cmd;
// call appropriate handler.
switch (fno) {
case DGO_RPC_LOAD_FNO:
LoadDGO(cmd);
break;
case DGO_RPC_LOAD_NEXT_FNO:
LoadNextDGO(cmd);
break;
case DGO_RPC_CANCEL_FNO:
CancelDGO(cmd);
break;
default:
cmd->result = DGO_RPC_RESULT_ERROR;
}
return cmd;
}
/*!
* Begin loading a DGO. Returns when the first obj is loaded.
* Then will load the next obj into the second buffer.
* Then the DGO loader will block until LoadNextDGO is called.
* This approach keeps two loads in flight at a time to increase loading throughput.
* One load will be read from DVD / DMA'd to EE
* Another will be linked on the EE.
* The final load is done directly onto the heap, and isn't double buffered
* (otherwise the linking object could allocate on the heap where the final loading object is
* being copied). This avoids having to relocate the data from the temporary load buffer to the
* heap, and is the only way to make sure that the entire heap can be filled.
*/
void LoadDGO(RPC_Dgo_Cmd* cmd) {
// Find the file
FileRecord* fr = isofs->find(cmd->name);
if (!fr) {
cmd->result = DGO_RPC_RESULT_ERROR;
return;
}
// cancel an in progress command and wait for it to end.
// note - this doesn't handle a nullptr correctly, so if this actually ends up cancelling
// it will crash.
CancelDGO(nullptr);
// set up the ISO Command
scmd.cmd_id = LOAD_DGO_CMD_ID;
scmd.messagebox_to_reply = dgo_mbx;
scmd.thread_id = 0;
scmd.buffer1 = (u8*)(u64)(cmd->buffer1);
scmd.buffer2 = (u8*)(u64)(cmd->buffer2);
scmd.buffer_heaptop = (u8*)(u64)(cmd->buffer_heap_top);
scmd.fr = fr;
// send the command to ISO Thread
SendMbx(iso_mbx, &scmd);
// wait for the ReturnMessage in the DGO callback state machine.
// this happens when the first file is loaded
WaitMbx(dgo_mbx);
if (scmd.status == CMD_STATUS_IN_PROGRESS) {
// we got one, but there's more to load.
// we don't set cmd->buffer1 as it's already the correct buffer in this case -
// when there are >1 objs, we load into buffer1 first.
cmd->result = DGO_RPC_RESULT_MORE;
} else if (scmd.status == CMD_STATUS_DONE) {
// all done! make sure our reply says we loaded to the top.
cmd->result = DGO_RPC_RESULT_DONE;
cmd->buffer1 = cmd->buffer_heap_top;
scmd.cmd_id = 0;
} else {
// error.
cmd->result = DGO_RPC_RESULT_ERROR;
scmd.cmd_id = 0;
}
}
/*!
* Signal to the IOP it can keep loading and overwrite the oldest obj buffer.
* This will return when there's another loaded obj.
*/
void LoadNextDGO(RPC_Dgo_Cmd* cmd) {
if (scmd.cmd_id == 0) {
// something went wrong.
cmd->result = DGO_RPC_RESULT_ERROR;
} else {
// update heap location
scmd.buffer_heaptop = (u8*)(u64)cmd->buffer_heap_top;
// allow DGO state machine to advance
SendMbx(sync_mbx, nullptr);
// wait for another load to finish.
WaitMbx(dgo_mbx);
// another load finished, respond with the result.
if (scmd.status == CMD_STATUS_IN_PROGRESS) {
// more, use the selected buffer.
cmd->result = DGO_RPC_RESULT_MORE;
cmd->buffer1 = (u32)(u64)scmd.selectedBuffer;
} else if (scmd.status == CMD_STATUS_DONE) {
// last obj, always loaded to top.
cmd->result = DGO_RPC_RESULT_DONE;
cmd->buffer1 = cmd->buffer_heap_top;
scmd.cmd_id = 0;
} else {
cmd->result = DGO_RPC_RESULT_ERROR;
scmd.cmd_id = 0;
}
}
}
/*!
* Abort an in progress load.
*/
void CancelDGO(RPC_Dgo_Cmd* cmd) {
if (scmd.cmd_id) {
scmd.want_abort = 1;
// wake up DGO state machine with abort
SendMbx(sync_mbx, nullptr);
// wait for it to abort.
WaitMbx(dgo_mbx);
// this will cause a crash if we cancel because we try to load 2 dgos at the same time.
// this should succeed if it's an actual cancel because we changed which level we're trying to
// load.
// I don't understand how this works in the real game.
// maybe the IOP doesn't crash on writing to 0x0?
// or, we have some other bug.
if (cmd) {
printf("null pointer case in CancelDGO hit!\n");
cmd->result = DGO_RPC_RESULT_ABORTED;
}
scmd.cmd_id = 0;
}
}
// TODO - GetVAGStreamPos
// TODO - VAG_MarkLoopStart
// TODO - VAG_MarkLoopEnd
// TODO - VAG_MarkNonloopStart
// TODO - VAG_MarkNonloopEnd