mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 21:27:52 -04:00
4f537d4a71
This sets up the C Kernel for Jak 3, and makes it possible to build and load code built with `goalc --jak3`. There's not too much interesting here, other than they switched to a system where symbol IDs (unique numbers less than 2^14) are generated at compile time, and those get included in the object file itself. This is kind of annoying, since it means all tools that produce a GOAL object file need to work together to assign unique symbol IDs. And since the symbol IDs can't conflict, and are only a number between 0 and 2^14, you can't just hash and hope for no collisions. We work around this by ignoring the IDs and re-assigning our own. I think this is very similar to what the C Kernel did on early builds of Jak 3 which supported loading old format level files, which didn't have the IDs included. As far as I can tell, this shouldn't cause any problems. It defeats all of their fancy tricks to save memory by not storing the symbol string, but we don't care.
200 lines
7.3 KiB
C++
200 lines
7.3 KiB
C++
#include "kdgo.h"
|
|
|
|
#include "common/common_types.h"
|
|
#include "common/log/log.h"
|
|
|
|
#include "game/common/dgo_rpc_types.h"
|
|
#include "game/kernel/common/Ptr.h"
|
|
#include "game/kernel/common/fileio.h"
|
|
#include "game/kernel/common/kdgo.h"
|
|
#include "game/kernel/common/kmalloc.h"
|
|
#include "game/kernel/jak1/klink.h"
|
|
|
|
namespace jak1 {
|
|
|
|
RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP
|
|
RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers
|
|
|
|
void kdgo_init_globals() {
|
|
sLastMsg = nullptr;
|
|
memset(sMsg, 0, sizeof(sMsg));
|
|
}
|
|
|
|
/*!
|
|
* Send message to IOP to start loading a new DGO file
|
|
* Uses a double-buffered message buffer
|
|
* @param name: the name of the DGO file
|
|
* @param buffer1 : one of the two file loading buffers
|
|
* @param buffer2 : the other of the two file loading buffers
|
|
* @param currentHeap : the current heap (for loading directly into the heap).
|
|
*
|
|
* DONE,
|
|
* MODIFIED : Added print statement to indicate when DGO load starts.
|
|
*/
|
|
void BeginLoadingDGO(const char* name, Ptr<u8> buffer1, Ptr<u8> buffer2, Ptr<u8> currentHeap) {
|
|
u8 msgID = sMsgNum;
|
|
RPC_Dgo_Cmd* mess = sMsg + sMsgNum;
|
|
sMsgNum = sMsgNum ^ 1; // toggle message buffer.
|
|
RpcSync(DGO_RPC_CHANNEL); // make sure old RPC is finished
|
|
|
|
// put a dummy value here just to make sure the IOP overwrites it.
|
|
sMsg[msgID].result = DGO_RPC_RESULT_INIT; // !! this is 666
|
|
|
|
// inform IOP of buffers
|
|
sMsg[msgID].buffer1 = buffer1.offset;
|
|
sMsg[msgID].buffer2 = buffer2.offset;
|
|
|
|
// also give a heap pointer so it can load the last object file directly into the heap to save the
|
|
// precious time.
|
|
sMsg[msgID].buffer_heap_top = currentHeap.offset;
|
|
|
|
// file name
|
|
strcpy(sMsg[msgID].name, name);
|
|
lg::debug("[Begin Loading DGO RPC] {}, 0x{:x}, 0x{:x}, 0x{:x}", name, buffer1.offset,
|
|
buffer2.offset, currentHeap.offset);
|
|
// this RPC will return once we have loaded the first object file.
|
|
// but we call async, so we don't block here.
|
|
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_FNO, true, mess, sizeof(RPC_Dgo_Cmd), mess,
|
|
sizeof(RPC_Dgo_Cmd));
|
|
sLastMsg = mess;
|
|
}
|
|
|
|
/*!
|
|
* Get the next object in the DGO. Will block until something is loaded.
|
|
* @param lastObjectFlag: will get set to 1 if this is the last object.
|
|
*
|
|
* DONE,
|
|
* MODIFIED : added exception if the sLastMessage isn't set (game just returns null as buffer)
|
|
*/
|
|
Ptr<u8> GetNextDGO(u32* lastObjectFlag) {
|
|
*lastObjectFlag = 1;
|
|
// Wait for RPC function to respond. This will happen once the first object file is loaded.
|
|
RpcSync(DGO_RPC_CHANNEL);
|
|
Ptr<u8> buffer(0);
|
|
if (sLastMsg) {
|
|
// if we got a good result, get pointer to object
|
|
if ((sLastMsg->result == DGO_RPC_RESULT_MORE) || (sLastMsg->result == DGO_RPC_RESULT_DONE)) {
|
|
buffer.offset =
|
|
sLastMsg->buffer1; // buffer 1 always contains location of most recently loaded object.
|
|
}
|
|
|
|
// not the last one, so don't set the flag.
|
|
if (sLastMsg->result == DGO_RPC_RESULT_MORE) {
|
|
*lastObjectFlag = 0;
|
|
}
|
|
|
|
// no pending message.
|
|
sLastMsg = nullptr;
|
|
} else {
|
|
// I don't see how this case can happen unless there's a bug. The game does check for this and
|
|
// nothing in this case. (maybe from GOAL this can happen?)
|
|
printf("last message not set!\n");
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/*!
|
|
* Instruct the IOP to continue loading the next object.
|
|
* Only should be called once it is safe to overwrite the previous.
|
|
* @param heapPtr : pointer to heap so the IOP could try to load directly into a heap if it wants.
|
|
* This should be updated after each object file load to make sure the IOP knows the exact location
|
|
* of the end of the GOAL heap data.
|
|
* DONE,
|
|
* EXACT
|
|
*/
|
|
void ContinueLoadingDGO(Ptr<u8> heapPtr) {
|
|
u32 msgID = sMsgNum;
|
|
RPC_Dgo_Cmd* sendBuff = sMsg + sMsgNum;
|
|
sMsgNum = sMsgNum ^ 1;
|
|
sendBuff->result = DGO_RPC_RESULT_INIT;
|
|
sMsg[msgID].buffer1 = 0;
|
|
sMsg[msgID].buffer2 = 0;
|
|
sMsg[msgID].buffer_heap_top = heapPtr.offset;
|
|
// the IOP will wait for this RpcCall to continue the DGO state machine.
|
|
RpcCall(DGO_RPC_CHANNEL, DGO_RPC_LOAD_NEXT_FNO, true, sendBuff, sizeof(RPC_Dgo_Cmd), sendBuff,
|
|
sizeof(RPC_Dgo_Cmd));
|
|
// this async RPC call will complete when the next object is fully loaded.
|
|
sLastMsg = sendBuff;
|
|
}
|
|
|
|
/*!
|
|
* Load and link a DGO file.
|
|
* This does not use the mutli-threaded linker and will block until the entire file is done.
|
|
*/
|
|
void load_and_link_dgo(u64 name_gstr, u64 heap_info, u64 flag, u64 buffer_size) {
|
|
auto name = Ptr<char>(name_gstr + 4).c();
|
|
auto heap = Ptr<kheapinfo>(heap_info);
|
|
load_and_link_dgo_from_c(name, heap, flag, buffer_size, false);
|
|
}
|
|
|
|
/*!
|
|
* Load and link a DGO file.
|
|
* This does not use the mutli-threaded linker and will block until the entire file is done.e
|
|
*/
|
|
void load_and_link_dgo_from_c(const char* name,
|
|
Ptr<kheapinfo> heap,
|
|
u32 linkFlag,
|
|
s32 bufferSize,
|
|
bool jump_from_c_to_goal) {
|
|
lg::debug("[Load and Link DGO From C] {}", name);
|
|
u32 oldShowStall = sShowStallMsg;
|
|
|
|
// remember where the heap top point is so we can clear temporary allocations
|
|
auto oldHeapTop = heap->top;
|
|
|
|
// allocate temporary buffers from top of the given heap
|
|
// align 64 for IOP DMA
|
|
// note: both buffers named dgo-buffer-2
|
|
auto buffer2 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2");
|
|
auto buffer1 = kmalloc(heap, bufferSize, KMALLOC_TOP | KMALLOC_ALIGN_64, "dgo-buffer-2");
|
|
|
|
// build filename. If no extension is given, default to CGO.
|
|
char fileName[16];
|
|
kstrcpyup(fileName, name);
|
|
if (fileName[strlen(fileName) - 4] != '.') {
|
|
strcat(fileName, ".CGO");
|
|
}
|
|
|
|
// no stall messages, as this is a blocking load and when spending 100% CPU time on linking,
|
|
// the linker can beat the DVD drive.
|
|
sShowStallMsg = 0;
|
|
|
|
// start load on IOP.
|
|
BeginLoadingDGO(
|
|
fileName, buffer1, buffer2,
|
|
Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0)); // 64-byte aligned for IOP DMA
|
|
|
|
u32 lastObjectLoaded = 0;
|
|
while (!lastObjectLoaded) {
|
|
// check to see if next object is loaded (I believe it always is?)
|
|
auto dgoObj = GetNextDGO(&lastObjectLoaded);
|
|
if (!dgoObj.offset) {
|
|
continue;
|
|
}
|
|
|
|
// if we're on the last object, it is loaded at cheap->current. So we can safely reset the two
|
|
// dgo-buffer allocations. We do this _before_ we link! This way, the last file loaded has more
|
|
// heap available, which is important when we need to use the entire memory.
|
|
if (lastObjectLoaded) {
|
|
heap->top = oldHeapTop;
|
|
}
|
|
|
|
// determine the size and name of the object we got
|
|
auto obj = dgoObj + 0x40; // seek past dgo object header
|
|
u32 objSize = *(dgoObj.cast<u32>()); // size from object's link block
|
|
|
|
char objName[64];
|
|
strcpy(objName, (dgoObj + 4).cast<char>().c()); // name from dgo object header
|
|
lg::debug("[link and exec] {:18s} {} {:6d} heap-use {:8d} {:8d}: 0x{:x}", objName,
|
|
lastObjectLoaded, objSize, kheapused(kglobalheap),
|
|
kdebugheap.offset ? kheapused(kdebugheap) : 0, kglobalheap->current.offset);
|
|
link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now!
|
|
|
|
// inform IOP we are done
|
|
if (!lastObjectLoaded) {
|
|
ContinueLoadingDGO(Ptr<u8>((heap->current + 0x3f).offset & 0xffffffc0));
|
|
}
|
|
}
|
|
sShowStallMsg = oldShowStall;
|
|
}
|
|
} // namespace jak1
|