/*! * @file kdgo.cpp * Loading DGO Files. Also has some general SIF RPC stuff used for RPCs other than DGO loading. * DONE! */ #include #include "kdgo.h" #include "kprint.h" #include "kmalloc.h" #include "fileio.h" #include "klink.h" #include "game/sce/sif_ee.h" #include "game/common/dgo_rpc_types.h" #include "game/common/player_rpc_types.h" #include "game/common/ramdisk_rpc_types.h" #include "game/common/loader_rpc_types.h" #include "game/common/play_rpc_types.h" using namespace ee; sceSifClientData cd[6]; //! client data for each IOP Remove Procedure Call. u16 x[8]; //! stupid temporary for storing a message u32 sShowStallMsg; //! setting to show a "stalled on iop" message u32 sMsgNum; //! Toggle for double buffered message sending. RPC_Dgo_Cmd* sLastMsg; //! Last DGO command sent to IOP RPC_Dgo_Cmd sMsg[2]; //! DGO message buffers void kdgo_init_globals() { memset(cd, 0, sizeof(cd)); memset(x, 0, sizeof(x)); sShowStallMsg = 1; sLastMsg = nullptr; memset(sMsg, 0, sizeof(sMsg)); } /*! * Call the given RPC with the given function number and buffers. */ s32 RpcCall(s32 rpcChannel, u32 fno, bool async, void* sendBuff, s32 sendSize, void* recvBuff, s32 recvSize) { return sceSifCallRpc(&cd[rpcChannel], fno, async, sendBuff, sendSize, recvBuff, recvSize, nullptr, nullptr); } /*! * GOAL Wrapper for RpcCall. */ u64 RpcCall_wrapper(s32 rpcChannel, u32 fno, u32 async, u64 send_buff, s32 send_size, u64 recv_buff, s32 recv_size) { return sceSifCallRpc(&cd[rpcChannel], fno, async, Ptr(send_buff).c(), send_size, Ptr(recv_buff).c(), recv_size, nullptr, nullptr); } /*! * Check if the given RPC is busy, by channel. */ u32 RpcBusy(s32 channel) { return sceSifCheckStatRpc(&cd[channel].rpcd); } /*! * Wait for an RPC to not be busy. Prints a stall message if sShowStallMsg is true and we have * to wait on the IOP. Stalling here is bad because it means the rest of the game can't run. */ void RpcSync(s32 channel) { if (RpcBusy(channel)) { if (sShowStallMsg) { Msg(6, "STALL: [kernel] waiting for IOP on RPC port #%d\n", channel); } while (RpcBusy(channel)) { // an attempt to avoid spamming SIF? u32 i = 0; while (i < 1000) { i++; } } } } /*! * Setup an RPC. */ u32 RpcBind(s32 channel, s32 id) { while (true) { if (sceSifBindRpc(&cd[channel], id, 1) < 0) { MsgErr("Error: RpcBind failed on port #%d [%4.4X]\n", channel, id); return 1; } Msg(6, "kernel: RPC port #%d started [%4.4X]\n", channel, id); // FlushCache(0); // this was not optimized out in Jak 1, but is _almost_ optimized out in Jak 2 and later. u32 i = 0; while (i < 10000) { i++; } if (cd[channel].serve) { break; } Msg(6, "kernel: RPC port #%d not responding.\n", channel); // it might seem like looping here is a bad idea (unclear if sceSifBindRpc can be called // multiple times!) but this actually happens sometimes, at least on development hardware! // (also, it's not clear that the "serve" field having data in it really means anything - maybe // the sceSifBindRpc doesn't wait for the connection to be fully set up? This seems likely // because they had to put that little delay in there before checking.) } return 0; } /*! * Setup all RPCs */ u32 InitRPC() { if (!RpcBind(PLAYER_RPC_CHANNEL, PLAYER_RPC_ID) && !RpcBind(LOADER_RPC_CHANNEL, LOADER_RPC_ID) && !RpcBind(RAMDISK_RPC_CHANNEL, RAMDISK_RPC_ID) && !RpcBind(DGO_RPC_CHANNEL, DGO_RPC_ID) && !RpcBind(4, 0xdeb5) && !RpcBind(PLAY_RPC_CHANNEL, PLAY_RPC_ID)) { return 0; } printf("Entering endless loop ... please wait\n"); for (;;) { } } /*! * Send a message to the IOP to stop it. */ void StopIOP() { x[2] = 0x14; // todo - this type and message RpcSync(PLAYER_RPC_CHANNEL); RpcCall(PLAYER_RPC_CHANNEL, 0, false, x, 0x50, nullptr, 0); printf("IOP shut down\n"); // sceDmaSync(0x10009000, 0, 0); printf("DMA shut down\n"); } /*! * 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 buffer1, Ptr buffer2, Ptr 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); printf("[Begin Loading DGO RPC] %s, 0x%x, 0x%x, 0x%x\n", 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 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 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 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 the TEST.DGO file. * Presumably used for debugging DGO loads. * We don't have the TEST.DGO file, so this isn't very useful. * * DONE, * EXACT, * UNUSED */ void LoadDGOTest() { u32 lastObject = 0; // backup show stall message and set it to false // EE will be loading DGO in a loop, so it will always be stalling // no need to print it. u32 lastShowStall = sShowStallMsg; sShowStallMsg = 0; // pick somewhat arbitrary memory to load the DGO into BeginLoadingDGO("TEST.DGO", Ptr(0x4800000), Ptr(0x4c00000), Ptr(0x4000000)); while (true) { // keep trying to load. Ptr dest_buffer(0); do { dest_buffer = GetNextDGO(&lastObject); } while (!dest_buffer.offset); // print the name of the object we loaded, its destination, and its size. Msg(6, "Loaded %s at %8.8X length %d\n", (dest_buffer + 4).cast().c(), dest_buffer.offset, *(dest_buffer.cast())); if (lastObject) { break; } // okay to load the next one ContinueLoadingDGO(Ptr(0x4000000)); } sShowStallMsg = lastShowStall; } /*! * 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(name_gstr + 4).c(); auto heap = Ptr(heap_info); load_and_link_dgo_from_c(name, heap, flag, buffer_size); } /*! * 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 heap, u32 linkFlag, s32 bufferSize) { printf("[Load and Link DGO From C] %s\n", 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((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()); // size from object's link block char objName[64]; strcpy(objName, (dgoObj + 4).cast().c()); // name from dgo object header printf("[link and exec] %s %d\n", objName, lastObjectLoaded); link_and_exec(obj, objName, objSize, heap, linkFlag); // link now! // inform IOP we are done if (!lastObjectLoaded) { ContinueLoadingDGO(Ptr((heap->current + 0x3f).offset & 0xffffffc0)); } } sShowStallMsg = oldShowStall; }