diff --git a/port/include/fs.h b/port/include/fs.h index 364ece5a2..24858460d 100644 --- a/port/include/fs.h +++ b/port/include/fs.h @@ -14,6 +14,7 @@ s32 fsPathIsAbsolute(const char *path); s32 fsPathIsCwdRelative(const char *path); void *fsFileLoad(const char *name, u32 *outSize); +s32 fsFileLoadTo(const char *name, void *dst, u32 dstSize); s32 fsFileSize(const char *name); FILE *fsFileOpenWrite(const char *name); diff --git a/port/include/mod.h b/port/include/mod.h index 6f8b6a279..5ebac5615 100644 --- a/port/include/mod.h +++ b/port/include/mod.h @@ -5,6 +5,13 @@ #define MOD_CONFIG_FNAME "modconfig.txt" +struct animtableentry; + s32 modConfigLoad(const char *path); +s32 modTextureLoad(u16 num, void *dst, u32 dstSize); + +s32 modAnimationLoadDescriptor(u16 num, struct animtableentry *anim); +void *modAnimationLoadData(u16 num); + #endif diff --git a/port/src/fs.c b/port/src/fs.c index 948cf8f93..bb863af09 100644 --- a/port/src/fs.c +++ b/port/src/fs.c @@ -184,6 +184,37 @@ const char *fsGetModDir(void) return modDir[0] ? modDir : NULL; } +s32 fsFileLoadTo(const char *name, void *dst, u32 dstSize) +{ + const char *fullName = fsFullPath(name); + + FILE *f = fopen(fullName, "rb"); + if (!f) { + return -1; + } + + fseek(f, 0, SEEK_END); + const s32 size = ftell(f); + fseek(f, 0, SEEK_SET); + + if (size < 0) { + sysLogPrintf(LOG_ERROR, "fsFileLoadTo: empty file or invalid size (%d): %s", size, fullName); + fclose(f); + return -1; + } + + if ((u32)size > dstSize) { + sysLogPrintf(LOG_ERROR, "fsFileLoadTo: file too big for buffer (%u > %u): %s", size, dstSize, fullName); + fclose(f); + return -1; + } + + fread(dst, 1, size, f); + fclose(f); + + return size; +} + void *fsFileLoad(const char *name, u32 *outSize) { const char *fullName = fsFullPath(name); diff --git a/port/src/mod.c b/port/src/mod.c index 3984e3f96..0cfeb19d2 100644 --- a/port/src/mod.c +++ b/port/src/mod.c @@ -10,6 +10,9 @@ #include "data.h" #include "game/stagetable.h" +#define MOD_TEXTURES_DIR "textures" +#define MOD_ANIMATIONS_DIR "animations" + extern struct stagemusic g_StageTracks[]; extern struct stageallocation g_StageAllocations8Mb[]; @@ -42,6 +45,13 @@ extern struct stageallocation g_StageAllocations8Mb[]; } \ v = strUnquote(token); +#define PARSE_INT(sec, name, v, min, max, ret) \ + p = modConfigParseIntValue(p, token, &v); \ + if (!p || v < (min) || v > (max)) { \ + sysLogPrintf(LOG_ERROR, "mod: %s: invalid " name " value: %s", sec, token); \ + return ret; \ + } + static inline char *modConfigParseFileValue(char *p, char *token, s32 *filenum) { p = strParseToken(p, token, NULL); @@ -404,3 +414,95 @@ s32 modConfigLoad(const char *fname) sysMemFree(data); return success; } + +s32 modTextureLoad(u16 num, void *dst, u32 dstSize) +{ + static s32 dirExists = -1; + if (dirExists < 0) { + dirExists = (fsFileSize(MOD_TEXTURES_DIR "/") >= 0); + } + + if (!dirExists) { + return -1; + } + + char path[FS_MAXPATH + 1]; + snprintf(path, sizeof(path), MOD_TEXTURES_DIR "/%04x.bin", num); + + const s32 ret = fsFileLoadTo(path, dst, dstSize); + if (ret > 0) { + sysLogPrintf(LOG_NOTE, "mod: loaded external texture %04x", num); + } + + return ret; +} + +void *modAnimationLoadData(u16 num) +{ + char path[FS_MAXPATH + 1]; + // load the animation data + snprintf(path, sizeof(path), MOD_ANIMATIONS_DIR "/%04x.bin", num); + void *data = fsFileLoad(path, NULL); + if (!data) { + sysFatalError("External animation %04x has no data file.\nEnsure that it is placed at %s or delete the descriptor.", num, path); + } + return data; +} + +s32 modAnimationLoadDescriptor(u16 num, struct animtableentry *anim) +{ + static s32 dirExists = -1; + if (dirExists < 0) { + dirExists = (fsFileSize(MOD_ANIMATIONS_DIR "/") >= 0); + } + + if (!dirExists) { + return false; + } + + char path[FS_MAXPATH + 1]; + + // load the descriptor, if any + snprintf(path, sizeof(path), MOD_ANIMATIONS_DIR "/%04x.txt", num); + if (fsFileSize(path) <= 0) { + return false; + } + + char *desc = fsFileLoad(path, NULL); + if (!desc) { + return false; + } + + // parse the descriptor + char token[UTIL_MAX_TOKEN + 1] = { 0 }; + char *p = strParseToken(desc, token, NULL); + s32 tmp = 0; + while (p && token[0]) { + if (!strcmp(token, "numframes")) { + PARSE_INT(path, "numframes", tmp, 0, 0xFFFF, false); + anim->numframes = tmp; + } else if (!strcmp(token, "bytesperframe")) { + PARSE_INT(path, "bytesperframe", tmp, 0, 0xFFFF, false); + anim->bytesperframe = tmp; + } else if (!strcmp(token, "headerlen")) { + PARSE_INT(path, "headerlen", tmp, 0, 0xFFFF, false); + anim->headerlen = tmp; + } else if (!strcmp(token, "framelen")) { + PARSE_INT(path, "framelen", tmp, 0, 0xFF, false); + anim->framelen = tmp; + } else if (!strcmp(token, "flags")) { + PARSE_INT(path, "flags", tmp, 0, 0xFF, false); + anim->flags = tmp; + } else { + sysLogPrintf(LOG_ERROR, "mod: %s: invalid key: %s", path, token); + return false; + } + p = strParseToken(p, token, NULL); + } + + sysMemFree(desc); + + sysLogPrintf(LOG_NOTE, "mod: loaded external animation %04x", num); + + return true; +} diff --git a/port/src/preprocess.c b/port/src/preprocess.c index 856f41686..8a2b02c2f 100644 --- a/port/src/preprocess.c +++ b/port/src/preprocess.c @@ -12,6 +12,7 @@ #include "game/texdecompress.h" #include "preprocess.h" #include "romdata.h" +#include "mod.h" #include "system.h" static inline f32 swapF32(f32 x) { *(u32 *)&x = PD_BE32(*(u32 *)&x); return x; } @@ -1011,18 +1012,25 @@ void preprocessAnimations(u8 *data, u32 size) // set the anim table pointers as well extern u8 *_animationsTableRomStart; extern u8 *_animationsTableRomEnd; + // the animation table is at the end of the segment u32 *animtbl = (void *)(data + size - 0x38a0); _animationsTableRomStart = (u8 *)animtbl; _animationsTableRomEnd = data + size; + PD_SWAP_VAL(*animtbl); const u32 count = *animtbl++; + struct animtableentry *anim = (struct animtableentry *)animtbl; for (u32 i = 0; i < count; ++i, ++anim) { PD_SWAP_VAL(anim->numframes); PD_SWAP_VAL(anim->bytesperframe); PD_SWAP_VAL(anim->headerlen); PD_SWAP_VAL(anim->data); + // if an external replacement exists, replace the table entry and mark the offset + if (modAnimationLoadDescriptor(i, anim) > 0) { + anim->data = 0xffffffff; + } } } diff --git a/src/game/texdecompress.c b/src/game/texdecompress.c index ad35e5f91..a6a4ce296 100644 --- a/src/game/texdecompress.c +++ b/src/game/texdecompress.c @@ -10,6 +10,9 @@ #include "lib/rzip.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "mod.h" +#endif struct texture *g_Textures; u32 var800aabc4; @@ -2256,12 +2259,19 @@ void texLoad(texnum_t *updateword, struct texpool *pool, bool unusedarg) return; } - // Copy the compressed texture to RAM - dmaExec(alignedcompbuffer, - (romptr_t) REF_SEG _texturesdataSegmentRomStart + (thisoffset & 0xfffffff8), - ((u32) (nextoffset - thisoffset) + 0x1f) >> 4 << 4); - - compptr = (u8 *) alignedcompbuffer + (thisoffset & 7); +#ifndef PLATFORM_N64 + // try to load external replacement if present + if (modTextureLoad(g_TexNumToLoad, alignedcompbuffer, 4096) > 0) { + compptr = alignedcompbuffer; + } else +#endif + { + // Copy the compressed texture to RAM + dmaExec(alignedcompbuffer, + (romptr_t) REF_SEG _texturesdataSegmentRomStart + (thisoffset & 0xfffffff8), + ((u32) (nextoffset - thisoffset) + 0x1f) >> 4 << 4); + compptr = (u8 *) alignedcompbuffer + (thisoffset & 7); + } thisoffset = 0; hasloddata = (*compptr & 0x80) >> 7; iszlib = (*compptr & 0x40) >> 6; diff --git a/src/lib/anim.c b/src/lib/anim.c index 025eb4866..56efbb010 100644 --- a/src/lib/anim.c +++ b/src/lib/anim.c @@ -12,6 +12,9 @@ #include "lib/libc/ll.h" #include "data.h" #include "types.h" +#ifndef PLATFORM_N64 +#include "mod.h" +#endif #define ANIM_HEADER_CACHE_SIZE 40 #define ANIM_FRAME_CACHE_SIZE 32 @@ -39,6 +42,10 @@ s32 g_AnimMaxHeaderLength = 608; bool g_AnimHostEnabled = false; u8 *g_AnimHostSegment = NULL; +#ifndef PLATFORM_N64 +u8 **g_AnimReplacements; +#endif + extern u8 EXT_SEG _animationsTableRomStart; extern u8 EXT_SEG _animationsTableRomEnd; @@ -81,6 +88,10 @@ void animsInit(void) g_AnimHeaderBytes = mempAlloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * sizeof(*g_AnimHeaderBytes)), MEMPOOL_PERMANENT); g_AnimHeaderAnimNums = mempAlloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * sizeof(*g_AnimHeaderAnimNums)), MEMPOOL_PERMANENT); g_AnimHeaderBirths = mempAlloc(ALIGN64(ANIM_HEADER_CACHE_SIZE * sizeof(*g_AnimHeaderBirths)), MEMPOOL_PERMANENT); +#ifndef PLATFORM_N64 + g_AnimReplacements = mempAlloc(ALIGN64(g_NumAnimations * sizeof(u8 *)), MEMPOOL_PERMANENT); + bzero(g_AnimReplacements, g_NumAnimations * sizeof(u8 *)); +#endif animsInitTables(); @@ -305,6 +316,16 @@ u8 animLoadFrame(s16 animnum, s32 framenum) if (g_Anims[animnum].bytesperframe) { offset = g_Anims[animnum].bytesperframe * loadframenum + (g_Anims[animnum].data + g_Anims[animnum].headerlen); +#ifndef PLATFORM_N64 + if (g_Anims[animnum].data == 0xffffffff) { + // load external replacement (this will fatal error if there's no data) + if (!g_AnimReplacements[animnum]) { + g_AnimReplacements[animnum] = modAnimationLoadData(animnum); + } + offset = g_Anims[animnum].bytesperframe * loadframenum + g_Anims[animnum].headerlen; + g_AnimFrameBytes[slot] = g_AnimReplacements[animnum] + offset; + } else +#endif g_AnimFrameBytes[slot] = animDma(&g_AnimFrameByteSlots[slot * g_AnimMaxBytesPerFrame], offset, g_Anims[animnum].bytesperframe); } else { g_AnimFrameBytes[slot] = &g_AnimFrameByteSlots[slot * g_AnimMaxBytesPerFrame]; @@ -355,6 +376,15 @@ void animLoadHeader(s16 animnum) tmp = g_Anims[animnum].headerlen; +#ifndef PLATFORM_N64 + if (g_Anims[animnum].data == 0xffffffff) { + // load external replacement (this will fatal error if there's no data) + if (!g_AnimReplacements[animnum]) { + g_AnimReplacements[animnum] = modAnimationLoadData(animnum); + } + g_AnimHeaderBytes[slot] = g_AnimReplacements[animnum]; + } else +#endif g_AnimHeaderBytes[slot] = animDma(&g_AnimHeaderByteSlots[slot * g_AnimMaxHeaderLength], g_Anims[animnum].data, tmp); g_AnimToHeaderSlot[animnum] = slot; g_AnimHeaderAnimNums[slot] = animnum;