From d94135f785d451d2f08686e41a73393de5abf16f Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 4 Sep 2026 00:33:47 -0600 Subject: [PATCH] Avoid overlay files overflowing solid heaps (#2387) --- include/d/actor/d_a_alink.h | 1 + include/d/actor/d_a_player.h | 15 ++++++ libs/JSystem/src/JKernel/JKRArchivePri.cpp | 17 +++++-- src/d/actor/d_a_alink.cpp | 12 ++++- src/d/actor/d_a_alink_swindow.inc | 32 ++++++++++++ src/d/actor/d_a_midna.cpp | 1 + src/d/actor/d_a_player.cpp | 58 ++++++++++++++++++++++ 7 files changed, 131 insertions(+), 5 deletions(-) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 95669633fd..75ac1aac2b 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4580,6 +4580,7 @@ public: bool mHsChainInterpCurrValid; bool mIsRollstab = false; + void* mAnmBuffers[3] = {}; #endif }; // Size: 0x385C diff --git a/include/d/actor/d_a_player.h b/include/d/actor/d_a_player.h index 5028442f68..ff8a165ef8 100644 --- a/include/d/actor/d_a_player.h +++ b/include/d/actor/d_a_player.h @@ -89,6 +89,14 @@ public: #define PLAYER_CREATE_ANM_HEAP(heap, type, name) (heap).createHeap(type) #endif +#if TARGET_PC +inline u32 daPy_getAnmResourceSize(u16 i_resId, u32 i_minSize) { + JKRArchive* archive = dComIfGp_getAnmArchive(); + u32 size = archive->getFileSize(archive->findIdxResource(i_resId)); + return size > i_minSize ? size : i_minSize; +} +#endif + class daPy_anmHeap_c { public: enum daAlinkHEAP_TYPE { @@ -105,6 +113,9 @@ public: void* mallocBuffer(); #if TARGET_PC void createHeap(daAlinkHEAP_TYPE i_heapType, const char* name); + void reserveBuffer(u16 i_resId); + void* allocTempBuffer(u16 i_resId, u32* io_size); + void freeTempBuffers(); #else void createHeap(daAlinkHEAP_TYPE i_heapType); #endif @@ -134,6 +145,10 @@ public: /* 0x08 */ u32 mBufferSize; /* 0x0C */ u8* mBuffer; /* 0x10 */ JKRSolidHeap* mAnimeHeap; +#if TARGET_PC + u8* mOwnedBuffer = NULL; + void** mTempBuffers = NULL; +#endif }; // Size = 0x14 class daPy_actorKeep_c { diff --git a/libs/JSystem/src/JKernel/JKRArchivePri.cpp b/libs/JSystem/src/JKernel/JKRArchivePri.cpp index eed317a357..f81082cc2f 100644 --- a/libs/JSystem/src/JKernel/JKRArchivePri.cpp +++ b/libs/JSystem/src/JKernel/JKRArchivePri.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "JSystem/JKernel/JKRDvdRipper.h" #if _WIN32 #include @@ -21,6 +22,8 @@ std::atomic JKRArchive::sArcOverlayGeneration{0}; namespace { +inline constexpr borealis::Log Log{"JKRArchivePri"}; + void* alloc_overlay_buffer(u32 size) { #if _WIN32 return _aligned_malloc(size, alignof(std::max_align_t)); @@ -452,12 +455,18 @@ bool JKRArchive::copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* ent return false; } - const u32 copySize = overlaySize < bufferSize ? overlaySize : bufferSize; - if (copySize != 0) { - memcpy(buffer, overlayData, copySize); + if (overlaySize > bufferSize) { + std::string path; + getOverlayPath(entry, path); + Log.error("Overlay %s is %u bytes but the game reserved %u\n", path.c_str(), overlaySize, + bufferSize); + return false; + } + if (overlaySize != 0) { + memcpy(buffer, overlayData, overlaySize); } if (outSize != nullptr) { - *outSize = copySize; + *outSize = overlaySize; } return true; } diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 45829188db..4d1bf182db 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -4313,7 +4313,9 @@ int daAlink_c::createHeap() { return 0; } - JKRReadIdxResource(mFaceBckHeap.getBuffer(), 0xC00, dRes_ID_ALANM_BCK_FAT_e, dComIfGp_getAnmArchive()); + IF_DUSK(mFaceBckHeap.reserveBuffer(dRes_ID_ALANM_BCK_FAT_e);) + JKRReadIdxResource(mFaceBckHeap.getBuffer(), DUSK_IF_ELSE(mFaceBckHeap.getBufferSize(), 0xC00), + dRes_ID_ALANM_BCK_FAT_e, dComIfGp_getAnmArchive()); J3DAnmTransform* bck = (J3DAnmTransform*)J3DAnmLoaderDataBase::load(mFaceBckHeap.getBuffer()); if (!mFaceBck.init(bck, FALSE, J3DFrameCtrl::EMode_LOOP, 1.0f, 0, -1, false)) { return 0; @@ -14307,7 +14309,11 @@ BOOL daAlink_c::checkMagicArmorWearAbility() const { J3DModelData* daAlink_c::loadAramBmd(u16 i_resIdx, u32 i_bufSize) { JKRArchive* anmArchive = dComIfGp_getAnmArchive(); +#if TARGET_PC + u8* tmpBuffer = (u8*)mItemHeap[field_0x2fa0].allocTempBuffer(i_resIdx, &i_bufSize); +#else u8* tmpBuffer = JKR_NEW_ARRAY_ARGS(u8, i_bufSize, 0x20); +#endif JKRReadIdxResource(tmpBuffer, i_bufSize, i_resIdx, anmArchive); #if DEBUG @@ -14328,7 +14334,11 @@ J3DModelData* daAlink_c::loadAramBmd(u16 i_resIdx, u32 i_bufSize) { } void* daAlink_c::loadAram(u16 i_resIdx, u32 i_bufSize) { +#if TARGET_PC + u8* tmpBuffer = (u8*)mItemHeap[field_0x2fa0].allocTempBuffer(i_resIdx, &i_bufSize); +#else u8* tmpBuffer = JKR_NEW_ARRAY_ARGS(u8, i_bufSize, 0x20); +#endif JKRReadIdxResource(tmpBuffer, i_bufSize, i_resIdx, dComIfGp_getAnmArchive()); #if DEBUG daPy_aramBufferCheck(tmpBuffer, i_bufSize); diff --git a/src/d/actor/d_a_alink_swindow.inc b/src/d/actor/d_a_alink_swindow.inc index 33f1fee37b..0359bc99bb 100644 --- a/src/d/actor/d_a_alink_swindow.inc +++ b/src/d/actor/d_a_alink_swindow.inc @@ -11,6 +11,16 @@ static int daAlink_modelCallBack(J3DJoint* i_joint, int param_1); static int daAlink_headModelCallBack(J3DJoint* i_joint, int param_1); static int daAlink_wolfModelCallBack(J3DJoint* i_joint, int param_1); +#if TARGET_PC +static void* read_anm_resource(void** o_buffer, u16 i_resIdx, u32 i_fixedSize) { + u32 size = daPy_getAnmResourceSize(i_resIdx, i_fixedSize); + *o_buffer = JKRAllocFromSysHeap(size, 0x20); + JUT_ASSERT(__LINE__, *o_buffer); + JKRReadIdxResource(*o_buffer, size, i_resIdx, dComIfGp_getAnmArchive()); + return *o_buffer; +} +#endif + void daAlink_c::setArcName(BOOL i_isWolf) { if (i_isWolf) { mArcName = l_wArcName; @@ -247,8 +257,10 @@ void daAlink_c::changeModelDataDirectWolf(int param_0) { void daAlink_c::initStatusWindow() { onNoResetFlg2(FLG2_STATUS_WINDOW_DRAW); +#if !TARGET_PC void* tmpBuffer = JKR_NEW_ARRAY_ARGS(void*, 0x500, 0x20); JUT_ASSERT(394, tmpBuffer); +#endif u16 bckResIdx, btpResIdx, btkResIdx; if (checkWolf()) { @@ -268,7 +280,11 @@ void daAlink_c::initStatusWindow() { changeModelDataDirect(0); } +#if TARGET_PC + void* tmpBuffer = read_anm_resource(&mAnmBuffers[0], bckResIdx, 0x1400); +#else JKRReadIdxResource(tmpBuffer, 0x1400, bckResIdx, dComIfGp_getAnmArchive()); +#endif m_sWindowBck = JKR_NEW mDoExt_bckAnm(); JUT_ASSERT(428, m_sWindowBck); @@ -276,20 +292,28 @@ void daAlink_c::initStatusWindow() { JUT_ASSERT(433, FALSE); } +#if TARGET_PC + tmpBuffer = read_anm_resource(&mAnmBuffers[1], btpResIdx, 0x400); +#else tmpBuffer = JKR_NEW_ARRAY_ARGS(void*, 0x100, 0x20); JUT_ASSERT(437, tmpBuffer); JKRReadIdxResource(tmpBuffer, 0x400, btpResIdx, dComIfGp_getAnmArchive()); +#endif // this should call J3DAnmLoaderDataBase::load(const void*) but it breaks retail J3DAnmTexPattern* btp = (J3DAnmTexPattern*)J3DAnmLoaderDataBase::load(tmpBuffer, J3DLOADER_UNK_FLAG0); btp->setFrame(0.0f); btp->searchUpdateMaterialID(field_0x06c0); field_0x06c0->entryTexNoAnimator(btp); +#if TARGET_PC + tmpBuffer = read_anm_resource(&mAnmBuffers[2], btkResIdx, 0x400); +#else tmpBuffer = JKR_NEW_ARRAY_ARGS(void*, 0x100, 0x20); JUT_ASSERT(449, tmpBuffer); JKRReadIdxResource(tmpBuffer, 0x400, btkResIdx, dComIfGp_getAnmArchive()); +#endif // this should call J3DAnmLoaderDataBase::load(const void*) but it breaks retail J3DAnmTextureSRTKey* btk = (J3DAnmTextureSRTKey*)J3DAnmLoaderDataBase::load(tmpBuffer, J3DLOADER_UNK_FLAG0); btk->setFrame(0.0f); @@ -387,4 +411,12 @@ void daAlink_c::resetStatusWindow() { offNoResetFlg2(FLG2_STATUS_WINDOW_DRAW); m_sWindowBck = NULL; +#if TARGET_PC + for (int i = 0; i < 3; i++) { + if (mAnmBuffers[i] != NULL) { + JKRFreeToSysHeap(mAnmBuffers[i]); + mAnmBuffers[i] = NULL; + } + } +#endif } diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index a50753cdf0..0be48b3794 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -496,6 +496,7 @@ int daMidna_c::createHeap() { } } + IF_DUSK(mBckHeap[0].reserveBuffer(0x1DC);) JKRReadIdxResource(mBckHeap[0].getBuffer(), mBckHeap[0].getBufferSize(), 0x1DC, dComIfGp_getAnmArchive()); J3DAnmTransform* md_anm = (J3DAnmTransform*)J3DAnmLoaderDataBase::load(mBckHeap[0].getBuffer()); modelData = (J3DModelData*)dComIfG_getObjectRes(l_arcName, 14); diff --git a/src/d/actor/d_a_player.cpp b/src/d/actor/d_a_player.cpp index 8c97bffb6f..e10f7e490c 100644 --- a/src/d/actor/d_a_player.cpp +++ b/src/d/actor/d_a_player.cpp @@ -224,6 +224,12 @@ daPy_anmHeap_c::~daPy_anmHeap_c() { if (mAnimeHeap != NULL) { mDoExt_destroySolidHeap(mAnimeHeap); } +#if TARGET_PC + freeTempBuffers(); + if (mOwnedBuffer != NULL) { + JKRFreeToSysHeap(mOwnedBuffer); + } +#endif } void daPy_anmHeap_c::initData() { @@ -237,6 +243,56 @@ void* daPy_anmHeap_c::mallocBuffer() { return mBuffer; } +#if TARGET_PC +constexpr u32 kAlignment = 0x20; + +void daPy_anmHeap_c::reserveBuffer(u16 i_resId) { + // Ensure mBuffer is large enough to hold the resource + u32 size = daPy_getAnmResourceSize(i_resId, mBufferSize); + if (size <= mBufferSize) { + return; + } + + // If not, replace it with a new buffer allocated from the system heap. Callers still copy + // archive data in on every load: setAnmTransform bswaps key tables in place, so we can't point + // it at the archive's cached copy directly. + u8* buffer = static_cast(JKRAllocFromSysHeap(size, kAlignment)); + JUT_ASSERT(__LINE__, buffer != NULL); + if (mOwnedBuffer != NULL) { + JKRFreeToSysHeap(mOwnedBuffer); + } + mOwnedBuffer = buffer; // Mark it as owned so we release it later + mBuffer = buffer; + mBufferSize = size; +} + +void* daPy_anmHeap_c::allocTempBuffer(u16 i_resId, u32* io_size) { + // Check if the resource can fit in io_size + u32 size = daPy_getAnmResourceSize(i_resId, *io_size); + if (size <= *io_size) { + return JKR_NEW_ARRAY_ARGS(u8, *io_size, kAlignment); + } + + // If not, allocate a new temp buffer from the system heap, plus kAlignment extra bytes. + // We stash a pointer to the next buffer at the beginning, forming a chain so we can free + // them all later. + void** temp = static_cast(JKRAllocFromSysHeap(kAlignment + size, kAlignment)); + JUT_ASSERT(__LINE__, temp != NULL); + *temp = mTempBuffers; + mTempBuffers = temp; + *io_size = size; + return reinterpret_cast(temp) + kAlignment; +} + +void daPy_anmHeap_c::freeTempBuffers() { + while (mTempBuffers != NULL) { + void** temp = mTempBuffers; + mTempBuffers = static_cast(*temp); + JKRFreeToSysHeap(temp); + } +} +#endif + void daPy_anmHeap_c::createHeap(daPy_anmHeap_c::daAlinkHEAP_TYPE i_heapType, const char* name) { u32 size; @@ -310,6 +366,7 @@ void* daPy_anmHeap_c::loadData(u16 i_resId) { }; if (mArcNo == 0xFFFF) { + IF_DUSK(reserveBuffer(i_resId);) JKRReadIdxResource(mBuffer, mBufferSize, i_resId, dComIfGp_getAnmArchive()); #if DEBUG daPy_aramBufferCheck(mBuffer, mBufferSize); @@ -363,6 +420,7 @@ void* daPy_anmHeap_c::loadDataDemoRID(u16 i_resID, u16 i_arcNo) { JKRHeap* daPy_anmHeap_c::setAnimeHeap() { mAnimeHeap->freeAll(); + IF_DUSK(freeTempBuffers();) return mDoExt_setCurrentHeap(mAnimeHeap); }