diff --git a/game/kernel/common/fileio.cpp b/game/kernel/common/fileio.cpp index 11976b7784b..a1860fa7df3 100644 --- a/game/kernel/common/fileio.cpp +++ b/game/kernel/common/fileio.cpp @@ -327,16 +327,23 @@ Ptr FileLoad(char* name, Ptr heap, Ptr memory, u32 malloc_fla s32 FileSave(char* name, u8* data, s32 size) { s32 fd = sceOpen(name, SCE_WRONLY | SCE_TRUNC | SCE_CREAT); if (fd < 0) { - MsgErr("dkernel: file write !open '%s'\n", name); + MsgErr("dkernel: file write !open: '%s'\n", name); sceClose(fd); return 0xfffffffa; } - if (size != 0) { + int writeOffset = 0; + while (size != 0) { // in jak 3, this became a loop over smaller writes for some reason. - s32 written = sceWrite(fd, data, size); - if (written != size) { - MsgErr("dkernel: can't write full file '%s'\n", name); + s32 chunkSize = 0x1000000; + if (size < 0x1000000) { + chunkSize = size; // one or final write + } + s32 written = sceWrite(fd, data + writeOffset, chunkSize); + writeOffset += written; + size -= written; + if (written != chunkSize) { + MsgErr("dkernel: can't write full file: '%s'\n", name); sceClose(fd); return 0xfffffffa; } diff --git a/game/kernel/common/kboot.cpp b/game/kernel/common/kboot.cpp index 433c6fb303d..9637b4de82b 100644 --- a/game/kernel/common/kboot.cpp +++ b/game/kernel/common/kboot.cpp @@ -22,9 +22,15 @@ char DebugBootLevel[64]; // Pass to GOAL kernel on boot char DebugBootMessage[64]; +// Added in Jak X, set to true in InitIOP +bool POWERING_OFF_W; + // game configuration MasterConfig masterConfig; +// Added in Jak X +bool USE_OVERLORD2_W; + void kboot_init_globals_common() { MasterExit = RuntimeExitStatus::RUNNING; DiskBoot = 0; diff --git a/game/kernel/common/kboot.h b/game/kernel/common/kboot.h index 56ee7ddbda2..665797185ed 100644 --- a/game/kernel/common/kboot.h +++ b/game/kernel/common/kboot.h @@ -38,9 +38,15 @@ extern char DebugBootLevel[64]; // Pass to GOAL kernel on boot extern char DebugBootMessage[64]; +// Added in Jak X, set to false by Sce callback +extern bool POWERING_OFF_W; + // Added in PC port, option to run listener functions without the kernel for debugging extern u32 MasterUseKernel; +// Added in Jak X +extern bool USE_OVERLORD2_W; + struct MasterConfig { u16 language; //! GOAL language 0 u16 aspect; //! SCE_ASPECT 2 diff --git a/game/kernel/common/kdgo.cpp b/game/kernel/common/kdgo.cpp index a7cca902ce0..07cb62cd486 100644 --- a/game/kernel/common/kdgo.cpp +++ b/game/kernel/common/kdgo.cpp @@ -18,6 +18,26 @@ ee::sceSifClientData cd[6]; //! client data for each IOP Remove Procedure Call. u32 sShowStallMsg; //! setting to show a "stalled on iop" message u16 x[8]; //! stupid temporary for storing a message u32 sMsgNum; //! Toggle for double buffered message sending. +bool IOP_RUNNING_W; +bool RPC_Initialized_G; + +const s32 NUMBER_OF_RPC_CHANNELS = 7; +struct RpcCallEndFunctionArg_W { + int sema_id; + void *callback; + int third; + int fourth; +}; +RpcCallEndFunctionArg_W RpcCallEndFunctionArgs_W[NUMBER_OF_RPC_CHANNELS]; + +const s32 NUMBER_OF_RPC_CHANNEL_PAIRS = 6; // FIXME: Why is this 6 and not 7? +struct RpcChannelIdPair_W { + int channel; + int id; +}; +RpcChannelIdPair_W RpcChannels_W[NUMBER_OF_RPC_CHANNEL_PAIRS]; + +typedef void EndFunctionType(int idk1, int idk2); void kdgo_init_globals() { memset(x, 0, sizeof(x)); @@ -25,8 +45,24 @@ void kdgo_init_globals() { sShowStallMsg = 1; } +int RpcCallEndFunction_W(long param_1) { + if (param_1 != 0) { + int* args = (int*)param_1; + if ((void*)(args[1]) != nullptr) { + ((EndFunctionType*)(args[1]))(args[2], args[3]); + } + // FIXME: What to do with this? + // return iSignalSema(*args); + return 0; + } else { + int in_v0_lo; + return in_v0_lo; + } +} + /*! * Call the given RPC with the given function number and buffers. + * In Jak X, errors are written out. The conditions have been negated. */ s32 RpcCall(s32 rpcChannel, u32 fno, @@ -34,9 +70,29 @@ s32 RpcCall(s32 rpcChannel, void* sendBuff, s32 sendSize, void* recvBuff, - s32 recvSize) { - return sceSifCallRpc(&cd[rpcChannel], fno, async, sendBuff, sendSize, recvBuff, recvSize, nullptr, - nullptr); + s32 recvSize, + void* callback) { + if (rpcChannel >= NUMBER_OF_RPC_CHANNELS) { + MsgErr("dkernel: RpcCall() error; invalid port id %d\n", rpcChannel); + } else if (sendSize >= 0xffff1) { + MsgErr("dkernel: RpcCall() error; invalid send/receive sizes (ssize=%d rsize=%d)\n", (s32)sendSize, recvSize); + } else if (!((((u32)sendSize < 0xffff1) && (-1 < recvSize)) && (recvSize < 0xffff1))) { + MsgErr("dkernel: RpcCall() error; NULL send buffer (secv=0x%08x ssize=%d)\n", nullptr, sendSize); + } else if (!(recvSize < 1) || (recvBuff != nullptr)) { + MsgErr("dkernel: RpcCall() error; NULL receive buffer (recv=0x%08x rsize=%d)\n", nullptr, recvSize); + } else if ((uintptr_t)sendBuff & 0xf) { + MsgErr("dkernel: RpcCall() error; misaligned send buffer (send=0x%08x ssize=%d)\n", sendBuff, sendSize); // added missing parenthesis + } else if ((uintptr_t)recvBuff & 0xf) { + MsgErr("dkernel: RpcCall() error; misaligned receive buffer (recv=0x%08x rsize=%d\n", recvBuff, recvSize); + } else { + // FIXME: What to do with this? + // WaitSema(RpcCallEndFunctionArgs_W[rpcChannel].sema_id); + RpcCallEndFunctionArgs_W[rpcChannel].callback = callback; + RpcCallEndFunctionArgs_W[rpcChannel].fourth = 0; // in_stack_00000008; + RpcCallEndFunctionArgs_W[rpcChannel].third = 0; // in_stack_00000000; + return sceSifCallRpc(&cd[rpcChannel], fno, (int)async, sendBuff, sendSize, recvBuff, recvSize, (void*)RpcCallEndFunction_W, &RpcCallEndFunctionArgs_W[rpcChannel]); + } + return -1; } namespace { @@ -64,6 +120,7 @@ u64 RpcCall_wrapper(void* _args) { auto send_size = args->get_as(4); auto recv_buff = args->get_as(5); auto recv_size = args->get_as(6); + // FIXME: Pass to RpcCall? return sceSifCallRpc(&cd[rpcChannel], fno, async, Ptr(send_buff).c(), send_size, Ptr(recv_buff).c(), recv_size, nullptr, nullptr); } @@ -72,7 +129,11 @@ u64 RpcCall_wrapper(void* _args) { * Check if the given RPC is busy, by channel. */ u32 RpcBusy(s32 channel) { - return sceSifCheckStatRpc(&cd[channel].rpcd); + if (channel < NUMBER_OF_RPC_CHANNELS) { + return sceSifCheckStatRpc(&cd[channel].rpcd); + } else { + return 1; + } } /*! @@ -80,16 +141,23 @@ u32 RpcBusy(s32 channel) { * 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 (6 < channel) { + MsgErr("dkernel: RpcSync() error; invalid port id %d\n", channel); + } if (RpcBusy(channel)) { if (sShowStallMsg) { - Msg(6, "STALL: [kernel] waiting for IOP on RPC port #%d\n", channel); + Msg(6, "dkernel: RpcSync() warning; port #%d stalled; waiting...\n", channel); } + // FIXME: What to do with this? + // WaitSema(RpcCallEndFunctionArgs_W[channel].sema_id); + // FIXME: What to do with this? + // SignalSema(RpcCallEndFunctionArgs_W[channel].sema_id); while (RpcBusy(channel)) { - // an attempt to avoid spamming SIF? - u32 i = 0; - while (i < 1000) { - i++; - } + // FIXME: What to do with this? + // DelayThread(10000); + } + if (sShowStallMsg) { + Msg(6, "dkernel: RpcSync(); port #%d acquired\n", channel); } } } @@ -97,62 +165,90 @@ void RpcSync(s32 channel) { /*! * 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); - // In Jak 2 they do a sceSifCheckStatRpc, but we can just skip that. - - // 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; +s32 RpcBind(s32 channel, s32 id) { + if (channel < NUMBER_OF_RPC_CHANNELS) { + bool displayedWarning = false; + while (true) { + if (sceSifBindRpc(&cd[channel], id, 0) < 0) { + MsgErr("dkernel: RpcBind() error; bind failed on port #%d id 0x%08x\n", channel, id); + return -1; + } else if (cd[channel].serve) { + MsgErr("dkernel: RpcBind() port #%d id 0x%08x bound\n", channel, id); + // In Jak 2 they do a sceSifCheckStatRpc, but we can just skip that. + return 0; + } else if (!displayedWarning) { + displayedWarning = true; + MsgErr("dkernel: RpcBind() warning; port #%d id 0x%08x not responding; retrying...\n", channel, id); + } + // FIXME: What to do with this? + // DelayThread(10000); + // 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.) } - 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.) + } else { + MsgErr("dkernel: RpcBind() error; invalid port id %d\n", channel); + return -1; } - return 0; } /*! * Setup all RPCs */ -u32 InitRPC() { - if (!RpcBind(PLAYER_RPC_CHANNEL, PLAYER_RPC_ID[g_game_version]) && - !RpcBind(LOADER_RPC_CHANNEL, LOADER_RPC_ID[g_game_version]) && - !RpcBind(RAMDISK_RPC_CHANNEL, RAMDISK_RPC_ID[g_game_version]) && - !RpcBind(DGO_RPC_CHANNEL, DGO_RPC_ID[g_game_version]) && - !RpcBind(STR_RPC_CHANNEL, STR_RPC_ID[g_game_version]) && - !RpcBind(PLAY_RPC_CHANNEL, PLAY_RPC_ID[g_game_version])) { +s32 InitRPC() { + if (RPC_Initialized_G) { + MsgErr("dkernel: InitRPC() error; multiple initializations attempted"); + return -1; + } else { + for (int i = 0; i < NUMBER_OF_RPC_CHANNELS; i++) { + RpcCallEndFunctionArgs_W[i].fourth = 0; + RpcCallEndFunctionArgs_W[i].sema_id = -1; + RpcCallEndFunctionArgs_W[i].callback = nullptr; + RpcCallEndFunctionArgs_W[i].third = 0; + } + for (int i = 0; i < NUMBER_OF_RPC_CHANNEL_PAIRS; i++) { + if (!RpcBind(RpcChannels_W[i].channel, RpcChannels_W[i].id)) { + return -1; + } + } + for (int i = 0; i < NUMBER_OF_RPC_CHANNELS; i++) { + // FIXME: What to do with this? + // ee_sema_t someSema; + // memset(&someSema, 0, 0x18); // MACRO + // someSema.init_count = 1; + // someSema.max_count = 1; + // int semaId = CreateSema(&someSema); + // RpcCallEndFunctionArgs_W[i].sema_id = semaId; + // if (semaId < 0) { + // for (int j = i; j > 0; --j) { + // DeleteSema(RpcCallEndFunctionArgs_W[j].sema_id); + // RpcCallEndFunctionArgs_W[j].sema_id = -1; + // } + // return -1; + // } + } + RPC_Initialized_G = true; return 0; } - lg::print("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); - lg::print("IOP shut down\n"); - // sceDmaSync(0x10009000, 0, 0); - lg::print("DMA shut down\n"); +int StopIOP() { + if (Is_RPC_Initialized_G() && IOP_RUNNING_W) { + x[2] = 0x10; + x[3] = 0; // todo - this type and message + // RpcSync(1); // FIXME: PLAYER_RPC_CHANNEL at 1? Previously at 0. + IOP_RUNNING_W = false; + // return RpcCall(1, 0, false, x, 0x30, nullptr, 0); + lg::print("IOP shut down\n"); + return 0; + } else { + return 0; + } } /*! @@ -172,8 +268,7 @@ void LoadDGOTest() { // 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; + bool lastShowStall = setStallMsg_G(false); // pick somewhat arbitrary memory to load the DGO into BeginLoadingDGO("TEST.DGO", Ptr(0x4800000), Ptr(0x4c00000), Ptr(0x4000000)); @@ -194,9 +289,23 @@ void LoadDGOTest() { // okay to load the next one ASSERT(false); // this is different per version, annoyingly. This function is unused though, // so let's be lazy for now... - // ContinueLoadingDGO(Ptr(0x4000000)); + // ContinueLoadingDGO(Ptr(0x4800000)); } - sShowStallMsg = lastShowStall; - */ + setStallMsg_G(lastShowStall); + */ +} + +bool setStallMsg_GW(bool show) +{ + bool oldShowStallMsg; + + oldShowStallMsg = sShowStallMsg; + sShowStallMsg = show; + return oldShowStallMsg; +} + +bool Is_RPC_Initialized_G(void) +{ + return RPC_Initialized_G; } diff --git a/game/kernel/common/kdgo.h b/game/kernel/common/kdgo.h index b8f8899a9cd..c01da910d16 100644 --- a/game/kernel/common/kdgo.h +++ b/game/kernel/common/kdgo.h @@ -6,19 +6,26 @@ #include "game/kernel/common/Ptr.h" extern u32 sMsgNum; + +extern bool IOP_RUNNING_W; + +extern u32 sShowStallMsg; + s32 RpcCall(s32 rpcChannel, u32 fno, bool async, void* sendBuff, s32 sendSize, void* recvBuff, - s32 recvSize); + s32 recvSize, + void* callback = nullptr); u64 RpcCall_wrapper(void* _args); u32 RpcBusy(s32 channel); void RpcSync(s32 channel); void LoadDGOTest(); void kdgo_init_globals(); -u32 InitRPC(); -void StopIOP(); +s32 InitRPC(); +int StopIOP(); -extern u32 sShowStallMsg; +bool setStallMsg_GW(bool show); +bool Is_RPC_Initialized_G(); diff --git a/game/kernel/common/kdsnetm.cpp b/game/kernel/common/kdsnetm.cpp index 0949563e20a..de6f5829261 100644 --- a/game/kernel/common/kdsnetm.cpp +++ b/game/kernel/common/kdsnetm.cpp @@ -185,7 +185,7 @@ s32 SendFromBufferD(s32 msg_kind, u64 msg_id, char* data, s32 size) { header->msg_id = msg_id; // start send! - auto rv = ee::sceDeci2ReqSend(protoBlock.socket, header->deci2_header.dst); + auto rv = ee::sceDeci2ReqSend(protoBlock.socket, 0x48); // hard-coded? header->deci2_header.dst); if (rv < 0) { printf("1sceDeci2ReqSend fail, reason code = %08x\n", rv); return 0xfffffffa; @@ -197,7 +197,7 @@ s32 SendFromBufferD(s32 msg_kind, u64 msg_id, char* data, s32 size) { } // if send completes, exit. Otherwise if there's an error, just try again. - if (protoBlock.send_status == 0) { + if (protoBlock.send_status > -1) { // TODO: Why not check 0 anymore? break; } } diff --git a/game/kernel/common/klink.h b/game/kernel/common/klink.h index 60fb374e07a..1e8c082d155 100644 --- a/game/kernel/common/klink.h +++ b/game/kernel/common/klink.h @@ -108,6 +108,7 @@ struct link_control { uint32_t jak3_work_v2_v4(); uint32_t jak3_work_v5(); uint32_t jak3_work_opengoal(); + uint32_t jakx_work_v2_v4(); uint32_t jakx_work_v5(); uint32_t jakx_work_opengoal(); diff --git a/game/kernel/common/klisten.cpp b/game/kernel/common/klisten.cpp index c7ee549da62..9b383431a21 100644 --- a/game/kernel/common/klisten.cpp +++ b/game/kernel/common/klisten.cpp @@ -23,7 +23,7 @@ void klisten_init_globals() { * Changed slightly, it will also print to stdout if there's no compiler connected. */ void ClearPending() { - if (!MasterDebug || !ListenerStatus) { + if (!MasterDebug) { // TODO: why is ListenerStatus gone? // if we aren't debugging or connected print the print buffer to stdout. if (PrintPending.offset != 0) { auto size = strlen(PrintBufArea.cast().c() + sizeof(ListenerMessageHeader)); @@ -34,20 +34,11 @@ void ClearPending() { } } else { if (ListenerStatus) { - if (OutputPending.offset != 0) { + if (OutputPending.offset != 0) { // TODO: why is the loop gone here? // note - same 64 kB patch as prints done here char* msg = OutputBufArea.cast().c() + sizeof(ListenerMessageHeader); auto size = strlen(msg); - while (size > 0) { - // sends larger than 64 kB are broken by the GoalProtoBuffer thing, so they are split - auto send_size = size; - if (send_size > 64000) { - send_size = 64000; - } - SendFromBuffer(msg, send_size); - size -= send_size; - msg += send_size; - } + SendFromBuffer(msg, (s32)size); clear_output(); } @@ -77,6 +68,7 @@ void ClearPending() { * ListenerMessageKind::MSG_ACK field. Both the type and msg_id fields are sent, which is enough * for it to work. * Note: jak2 sent 0 length acks. + * Then Jak X as well? */ void SendAck() { if (MasterDebug) { diff --git a/game/kernel/common/kmachine.cpp b/game/kernel/common/kmachine.cpp index 0b4296102fe..bac216a7462 100644 --- a/game/kernel/common/kmachine.cpp +++ b/game/kernel/common/kmachine.cpp @@ -42,6 +42,8 @@ u8 pad_dma_buf[2 * SCE_PAD_DMA_BUFFER_SIZE]; u32 vif1_interrupt_handler = 0; u32 vblank_interrupt_handler = 0; +bool cd_S_INITIALIZE_CD_W = false; // New in Jak X + Timer ee_clock_timer; void kmachine_init_globals_common() { @@ -56,23 +58,54 @@ void kmachine_init_globals_common() { /*! * Initialize the CD Drive - * DONE, EXACT */ void InitCD() { - lg::info("Initializing CD drive. This may take a while..."); - ee::sceCdInit(SCECdINIT); - ee::sceCdMmode(SCECdDVD); - while (ee::sceCdDiskReady(0) == SCECdNotReady) { - lg::debug("Drive not ready... insert a disk!"); + if (cd_S_INITIALIZE_CD_W) { + s32 result; + while (true) { + strlen("dkernel: Initializing DVD drive...\n"); // FIXME: Why is there a strlen here? + printf("dkernel: Initializing DVD drive...\n"); + result = ee::sceCdInit(SCECdINIT); + if (result != 0) { + break; + } + printf("dkernel: DVD drive initialization failed, retrying...\n"); + } + printf("dkernel: DVD drive initialized; result=%d\n", result); } - lg::debug("Disk type {}\n", ee::sceCdGetDiskType()); } /*! * Initialize the GS and display the splash screen. - * Not yet implemented. TODO */ -void InitVideo() {} +void InitVideo() { + // undefined auStack_b0 [96]; + // sceGsResetGraph(0, 1, 3, 0); + // sceGsSetDefLoadImage(auStack_b0, 0x2c00, 8, 0, 0, 0, 0x200, 0xe0); + // FlushCache(0); + // sceGsExecLoadImage(auStack_b0, 0x1000000); + // sceGsSetDefLoadImage(auStack_b0, 0x2c00, 8, 0, 0, 0xe0, 0x200, 0xe0); + // FlushCache(0); + // sceGsExecLoadImage(auStack_b0, 0x1070000); + + // DisplayEnv display_W; + // memset(&display_W, 0, 0x28); + // display_W.gs_pmode2 = display_W.gs_pmode2 & 0xfffffffffffffffd | 1; + // display_W.gs_pmode = display_W.gs_pmode & 0xfffffffffffffffd | 0x60; + // display_W.gs_display_fb = display_W.gs_display_fb & 0xffc00000fff00000 | 0x1160; + // display_W.gs_display = display_W.gs_display & 0xff800000e0000000 | 0x1bf9ff0204a28c; + // display_W.gs_bgcolor._0_1_ = 0; + // display_W.gs_bgcolor._1_1_ = 0; + // display_W.gs_bgcolor._2_1_ = 0; + + // sceGsSyncV(0); + // sceGsPutDispEnv(&display_W); + // for (int i = 0; i < 4; i++) { + // sceGsSyncV(0); + // } + // display_W.gs_pmode = display_W.gs_pmode | 2; + // sceGsPutDispEnv(&display_W); +} /*! * Flush caches. Does all the memory, regardless of what you specify @@ -88,6 +121,8 @@ void CacheFlush(void* mem, int size) { * Open a new controller pad. * Set the new_pad flag to 1 and state to 0. * Prints an error if it fails to open. + * FIXME: Different in Jak X, missing CPad library v2 symbols. + * See decompiled function here: https://github.com/yodaxtah/jakx-c-kernel-decompiled/blob/5ca699ecb970291b2263dca77ed5157c30f820a8/elf/kernel/common/kmachine.cpp#L126 */ u64 CPadOpen(u64 cpad_info, s32 pad_number) { auto cpad = Ptr(cpad_info).c(); @@ -106,6 +141,8 @@ u64 CPadOpen(u64 cpad_info, s32 pad_number) { /*! * Not checked super carefully for jak 2, but looks the same + * FIXME: Different in Jak X, missing CPad library v2 symbols. + * See decompiled function here: https://github.com/yodaxtah/jakx-c-kernel-decompiled/blob/5ca699ecb970291b2263dca77ed5157c30f820a8/elf/kernel/common/kmachine.cpp#L143 */ u64 CPadGetData(u64 cpad_info) { using namespace ee; @@ -238,6 +275,18 @@ void InstallHandler(u32 handler_idx, u32 handler_func) { // nothing used this in jak1, hopefully same for 2 void InstallDebugHandler() { ASSERT(false); + // SetDebugHandler(); +} + +s32 kmkdir(u64 name) { + char acStack_90[128]; + if (Ptr(name)->data()[4] == '/') { // start from the fourth character? + sprintf(acStack_90, "%s", Ptr(name)->data() + 5); + } + else { + sprintf(acStack_90, "host:%s", Ptr(name)->data() + 4); + } + return ee::sceMkDir(acStack_90, 0x1fd); } /*! @@ -328,8 +377,20 @@ u64 kclose(u64 fs) { } // TODO dma_to_iop -void dma_to_iop() { - ASSERT(false); +bool dma_to_iop(void* src_G, int size_G, void* dest_G) { + // SifDmaTransfer_t transfer_W; + // transfer_W.attr = 0; + // transfer_W.src = src_G; + // transfer_W.dest = dest_G; + // transfer_W.size = size_G; + // u32 id = sceSifSetDma(&transfer_W, 1); + // if (id != 0) { + // s32 sVar1; + // do { + // sVar1 = sceSifDmaStat(id); + // } while (-1 < sVar1); + // } + // return id == 0; } u64 DecodeLanguage() { @@ -359,9 +420,14 @@ u64 DecodeInactiveTimeout() { } void DecodeTime(u32 ptr) { - Ptr clock(ptr); // in jak2, if this fails, they do a sceScfGetLocalTimefromRTC - sceCdReadClock(clock.c()); + // in jak3, they do a memset? + // in jakx, they reused jak2's function, but moved the clock to underlord? + // FIXME: Move underlordRpcCall1_W underlord.h + // FIXME: Move underlordRpcCall1_W underlord.h + // if (ptr != 0 && underlordRpcCall1_W() != 0) { + // sceScfGetLocalTimefromRTC(ptr); + // } } void vif_interrupt_callback(int bucket_id) { diff --git a/game/kernel/common/kmachine.h b/game/kernel/common/kmachine.h index b5b1730d19d..3ef9fc8aa63 100644 --- a/game/kernel/common/kmachine.h +++ b/game/kernel/common/kmachine.h @@ -25,6 +25,8 @@ extern u32 reboot_iop; // renamed to reboot_iop to avoid conflict extern const char* init_types[]; extern u32 vblank_interrupt_handler; +extern bool cd_S_INITIALIZE_CD_W; // new in Jak X + void kmachine_init_globals_common(); /*! @@ -45,13 +47,14 @@ u64 CPadOpen(u64 cpad_info, s32 pad_number); u64 CPadGetData(u64 cpad_info); void InstallHandler(u32 handler_idx, u32 handler_func); void InstallDebugHandler(); +s32 kmkdir(u64 name); s32 klength(u64 fs); s32 kseek(u64 fs, s32 offset, s32 where); s32 kread(u64 fs, u64 buffer, s32 size); s32 kwrite(u64 fs, u64 buffer, s32 size); s32 kmkdir(u64 name); u64 kclose(u64 fs); -void dma_to_iop(); +bool dma_to_iop(void* src_G, int size_G, void* dest_G); u64 DecodeLanguage(); u64 DecodeAspect(); u64 DecodeVolume(); diff --git a/game/kernel/common/kmalloc.cpp b/game/kernel/common/kmalloc.cpp index a7917d15d13..019a8bd547b 100644 --- a/game/kernel/common/kmalloc.cpp +++ b/game/kernel/common/kmalloc.cpp @@ -90,7 +90,7 @@ Ptr kheapstatus(Ptr heap) { } for (int i = 0; i < NUM_CATEGORIES; i++) { - printf(" %d: %d %d\n", i, MemItemsCount[i], MemItemsSize[i]); + printf(" %d: %d %d\n", i, MemItemsCount[i], MemItemsSize[i]); } // might not have returned heap in jak 1 @@ -133,7 +133,7 @@ Ptr kmalloc(Ptr heap, s32 size, u32 flags, char const* name) { // if we got a null heap, put it on the global heap, but warn about it if (!heap.offset) { // the 0 is uninitialized in jak1, set to zero in jak 2. might just be compiler differences. - Msg(6, "-----------> kmalloc: alloc %s, mem %s #x%x (a:%d %dbytes)\n", "DEBUG", name, 0, + Msg(6, "--------------------> kmalloc: alloc %s mem %s #x%x (a:%d %dbytes)\n", "DEBUG", name, 0, alignment_flag, size); heap = kglobalheap; } @@ -163,9 +163,6 @@ Ptr kmalloc(Ptr heap, s32 size, u32 flags, char const* name) { } heap->current.offset = memend; - if (flags & KMALLOC_MEMSET) - std::memset(Ptr(memstart).c(), 0, (size_t)size); - return Ptr(memstart); } else { // allocate from top if (alignment_flag == 0) { @@ -186,22 +183,25 @@ Ptr kmalloc(Ptr heap, s32 size, u32 flags, char const* name) { } heap->top.offset = memstart; + } - if (flags & KMALLOC_MEMSET) - std::memset(Ptr(memstart).c(), 0, (size_t)size); - - // this logging was added in Jak 3, but we port it back to all games: - if ((heap == kglobalheap) && (kheaplogging != 0)) { - if (strcmp(name, "string") == 0) { - MemItemsCount[STRING]++; - MemItemsSize[STRING] += size; - } else if (strcmp(name, "type") == 0) { - MemItemsCount[TYPE]++; - MemItemsSize[TYPE] += size; - } + // NOTE: This code is slightly different to jak3. + // Naughty Dog could have moved this out of the if statement in Jak X... + // Or this was intentionally split by OpenGoal already? + if (flags & KMALLOC_MEMSET) + std::memset(Ptr(memstart).c(), 0, (size_t)size); + + // this logging was added in Jak 3, but we port it back to all games: + if ((heap == kglobalheap) && (kheaplogging != 0)) { + if (strcmp(name, "string") == 0) { + MemItemsCount[STRING]++; + MemItemsSize[STRING] += size; + } else if (strcmp(name, "type") == 0) { + MemItemsCount[TYPE]++; + MemItemsSize[TYPE] += size; } - return Ptr(memstart); } + return Ptr(memstart); } /*! diff --git a/game/kernel/common/kmemcard.cpp b/game/kernel/common/kmemcard.cpp index 61ecde6f845..1a535c9c24a 100644 --- a/game/kernel/common/kmemcard.cpp +++ b/game/kernel/common/kmemcard.cpp @@ -91,6 +91,14 @@ const char* filename_jak3[12] = { "BASCUS-97330AYBABTU!/bank4.bin", "BASCUS-97330AYBABTU!/bank5.bin", "BASCUS-97330AYBABTU!/bank6.bin", "BASCUS-97330AYBABTU!/bank7.bin"}; +const char* filename_jakx[12] = { + "BASCUS-97429AYBABTU!", "BASCUS-97429AYBABTU!/icon.sys", + "BASCUS-97429AYBABTU!/icon.ico", "BASCUS-97429AYBABTU!/BASCUS-97429AYBABTU!", + "BASCUS-97429AYBABTU!/bank0.bin", "BASCUS-97429AYBABTU!/bank1.bin", + "BASCUS-97429AYBABTU!/bank2.bin", "BASCUS-97429AYBABTU!/bank3.bin", + "BASCUS-97429AYBABTU!/bank4.bin", "BASCUS-97429AYBABTU!/bank5.bin", + "BASCUS-97429AYBABTU!/bank6.bin", "BASCUS-97429AYBABTU!/bank7.bin"}; + const char* mc_get_filename_no_dir(GameVersion version, int ndx) { const char** filenames = nullptr; switch (version) { @@ -103,6 +111,9 @@ const char* mc_get_filename_no_dir(GameVersion version, int ndx) { case GameVersion::Jak3: filenames = filename_jak3; break; + case GameVersion::JakX: + filenames = filename_jakx; + break; } return filenames[ndx]; } diff --git a/game/kernel/common/kprint.cpp b/game/kernel/common/kprint.cpp index 1f2bc4df492..82792af75a5 100644 --- a/game/kernel/common/kprint.cpp +++ b/game/kernel/common/kprint.cpp @@ -72,6 +72,7 @@ void init_output() { use_debug = MasterDebug && DebugSegment; break; case GameVersion::Jak3: + case GameVersion::JakX: use_debug = MasterDebug || DebugSegment; break; default: diff --git a/game/kernel/common/kscheme.cpp b/game/kernel/common/kscheme.cpp index ae43de721d3..508a3a27589 100644 --- a/game/kernel/common/kscheme.cpp +++ b/game/kernel/common/kscheme.cpp @@ -28,6 +28,8 @@ u32 FastLink; // but is enabled when loading the engine. Ptr EnableMethodSet; +bool Crc32_Initialized_W; + void kscheme_init_globals_common() { SymbolTable2.offset = 0; LastSymbol.offset = 0; @@ -38,6 +40,7 @@ void kscheme_init_globals_common() { } EnableMethodSet.offset = 0; FastLink = 0; + Crc32_Initialized_W = false; } /*! @@ -45,9 +48,9 @@ void kscheme_init_globals_common() { */ void init_crc() { for (u32 i = 0; i < 0x100; i++) { - u32 n = i << 24; + u32 n = i; for (u32 j = 0; j < 8; j++) { - n = n & 0x80000000 ? (n << 1) ^ CRC_POLY : (n << 1); + n = (n & 1 != 0) ? (n >> 1) ^ REVERSED_CRC_POLY : (n >> 1); } crc_table[i] = n; } @@ -57,9 +60,13 @@ void init_crc() { * Take the CRC32 hash of some data */ u32 crc32(const u8* data, s32 size) { - uint32_t crc = 0; - for (int i = size; i != 0; i--, data++) { - crc = crc_table[crc >> 24] ^ ((crc << 8) | *data); + if (!Crc32_Initialized_W) { + init_crc(); + Crc32_Initialized_W = true; + } + uint32_t crc = 0xffffffff; + for (int i = size; i > 0; i--, data++) { + crc = crc_table[crc & 0xff ^ (unsigned int)*data] ^ (crc >> 8); } ASSERT(~crc); diff --git a/game/kernel/common/kscheme.h b/game/kernel/common/kscheme.h index b78d1e759ab..b040c07a851 100644 --- a/game/kernel/common/kscheme.h +++ b/game/kernel/common/kscheme.h @@ -14,6 +14,7 @@ extern Ptr EnableMethodSet; void kscheme_init_globals_common(); constexpr u32 CRC_POLY = 0x04c11db7; +constexpr u32 REVERSED_CRC_POLY = 0xedb88320; constexpr u32 EMPTY_HASH = 0x8454B6E6; constexpr u32 OFFSET_MASK = 7; diff --git a/game/kernel/jakx/kboot.cpp b/game/kernel/jakx/kboot.cpp index bf4f74f1d9f..8e60638e8c5 100644 --- a/game/kernel/jakx/kboot.cpp +++ b/game/kernel/jakx/kboot.cpp @@ -174,13 +174,12 @@ void KernelShutdown(u32 reason) { } int KernelCheckAndDispatch() { - // TODO - jak x - /*while (MasterExit == RuntimeExitStatus::RUNNING && !POWERING_OFF_W) { + while (MasterExit == RuntimeExitStatus::RUNNING && !POWERING_OFF_W) { KernelDispatch(kernel_dispatcher->value()); } if (POWERING_OFF_W) { KernelShutdown(3); - }*/ + } return (u32)MasterExit; } diff --git a/game/kernel/jakx/kdgo.cpp b/game/kernel/jakx/kdgo.cpp index ff2db534915..aea765a5b66 100644 --- a/game/kernel/jakx/kdgo.cpp +++ b/game/kernel/jakx/kdgo.cpp @@ -8,8 +8,8 @@ #include "game/kernel/common/kdgo.h" #include "game/kernel/common/kmalloc.h" #include "game/kernel/common/kprint.h" -#include "game/kernel/jakx/kboot.h" #include "game/kernel/jakx/klink.h" +#include "game/kernel/jakx/kboot.h" #include "game/kernel/jakx/kmachine.h" #include "game/overlord/jakx/rpc_interface.h" @@ -97,7 +97,7 @@ Ptr GetNextDGO(u32* lastObjectFlag) { } 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"); // NOTE: this case was not present in Jak Xh + printf("last message not set!\n"); // NOTE: this case was not present in Jak Xh } return buffer; } @@ -158,78 +158,74 @@ void load_and_link_dgo_from_c(const char* name, // build filename. If no extension is given, default to CGO. char fileName[16]; - kstrcpyup(fileName, name); // FIXME: Similar decompilation to Jak 3, yet I don't understand how - // it's functionally the same + kstrcpyup(fileName, name); // FIXME: Similar decompilation to Jak 3, yet I don't understand how it's functionally the same 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. - // - // TODO - jakx - // bool oldShowStall = setStallMsg_GW(false); - - // if (!POWERING_OFF_W) { - // // 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 && !POWERING_OFF_W) { - // // 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; - // } - - // // FIXME: possibly enable this function call - // // FUN_0027cc90_patch(dgoObj, bufferSize); - - // // 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 - // 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); - // { - // auto p = scoped_prof(fmt::format("link-{}", objName).c_str()); - // link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now! - // } - - // // inform IOP we are done - // if (lastObjectLoaded) { - // break; - // } - // if (POWERING_OFF_W == false) { - // ContinueLoadingDGO(buffer1, buffer2, Ptr((heap->current + 0x3f).offset & 0xffffffc0)); - // } - // } - //} - - // lg::info("load_and_link_dgo_from_c took {:.3f} s\n", timer.getSeconds()); - // if (!POWERING_OFF_W) { - // setStallMsg_GW(oldShowStall); - // } else { - // KernelShutdown(3); - // ShutdownMachine(3); - // Msg(6, "load_and_link_dgo_from_c: cannot continue; load aborted\n"); - // while (true) { - // ; /* WARNING: Do nothing block with infinite loop */ - // } - // } + bool oldShowStall = setStallMsg_GW(false); + + if (!POWERING_OFF_W) { + // 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 && !POWERING_OFF_W) { + // 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; + } + + // FIXME: possibly enable this function call + // FUN_0027cc90_patch(dgoObj, bufferSize); + + // 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 + 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); + { + auto p = scoped_prof(fmt::format("link-{}", objName).c_str()); + link_and_exec(obj, objName, objSize, heap, linkFlag, jump_from_c_to_goal); // link now! + } + + // inform IOP we are done + if (lastObjectLoaded) { + break; + } + if (POWERING_OFF_W == false) { + ContinueLoadingDGO(buffer1, buffer2, Ptr((heap->current + 0x3f).offset & 0xffffffc0)); + } + } + } + + lg::info("load_and_link_dgo_from_c took {:.3f} s\n", timer.getSeconds()); + if (!POWERING_OFF_W) { + setStallMsg_GW(oldShowStall); + } else { + KernelShutdown(3); + ShutdownMachine(3); + Msg(6, "load_and_link_dgo_from_c: cannot continue; load aborted\n"); + while (true) { + ; /* WARNING: Do nothing block with infinite loop */ + } + } } } // namespace jakx