From fa8658ff83a394ccb85546ae494e66234a599cd7 Mon Sep 17 00:00:00 2001
From: Michael Oliver <michael.oliver_@outlook.com>
Date: Tue, 4 Mar 2025 22:59:25 +0000
Subject: [PATCH 1/2] feat: add `rawfilesdump` cmd

---
 src/game/mp_main.cpp  |  73 ++++++++++++++++++-------
 src/game/mp_structs.h | 120 +++++++++++++++++++++++++++++++++++++++++-
 src/game/sp_main.cpp  |  72 ++++++++++++++++++-------
 src/game/sp_structs.h | 119 ++++++++++++++++++++++++++++++++++++++++-
 4 files changed, 342 insertions(+), 42 deletions(-)

diff --git a/src/game/mp_main.cpp b/src/game/mp_main.cpp
index de6e6e6..fc5b724 100644
--- a/src/game/mp_main.cpp
+++ b/src/game/mp_main.cpp
@@ -45,6 +45,10 @@ namespace mp
     Cmd_AddCommandInternal_t Cmd_AddCommandInternal = reinterpret_cast<Cmd_AddCommandInternal_t>(0x8223ADE0);
 
     Com_Printf_t Com_Printf = reinterpret_cast<Com_Printf_t>(0x82237000);
+    Com_PrintError_t Com_PrintError = reinterpret_cast<Com_PrintError_t>(0x82235C50);
+    Com_PrintWarning_t Com_PrintWarning = reinterpret_cast<Com_PrintWarning_t>(0x822356B8);
+
+    DB_EnumXAssets_FastFile_t DB_EnumXAssets_FastFile = reinterpret_cast<DB_EnumXAssets_FastFile_t>(0x8229ED48);
 
     Load_MapEntsPtr_t Load_MapEntsPtr = reinterpret_cast<Load_MapEntsPtr_t>(0x822A9648);
 
@@ -158,22 +162,8 @@ namespace mp
 
     char *Scr_ReadFile_FastFile_Hook(const char *filename, const char *extFilename, const char *codePos, bool archive)
     {
-
         xbox::DbgPrint("Scr_ReadFile_FastFile_Hook extFilename=%s \n", extFilename);
 
-        auto contents = Scr_ReadFile_FastFile_Detour.GetOriginal<decltype(&Scr_ReadFile_FastFile_Hook)>()(filename, extFilename, codePos, archive);
-
-        // Dump the file to disk if it exists
-        // It might not exist if it's not a stock file
-        if (contents != nullptr)
-        {
-            // Dump the file to disk
-            std::string file_path = "game:\\dump\\";
-            file_path += extFilename;
-            std::replace(file_path.begin(), file_path.end(), '/', '\\'); // Replace forward slashes with backslashes
-            filesystem::write_file_to_disk(file_path.c_str(), contents, strlen(contents));
-        }
-
         std::string raw_file_path = "game:\\raw\\";
         raw_file_path += extFilename;
         std::replace(raw_file_path.begin(), raw_file_path.end(), '/', '\\'); // Replace forward slashes with backslashes
@@ -203,12 +193,56 @@ namespace mp
             }
         }
 
-        return contents;
+        return Scr_ReadFile_FastFile_Detour.GetOriginal<decltype(&Scr_ReadFile_FastFile_Hook)>()(filename, extFilename, codePos, archive);
+    }
+
+    const unsigned int MAX_RAWFILES = 2048;
+    struct RawFileList
+    {
+        unsigned int count;
+        RawFile *files[MAX_RAWFILES];
+    };
+
+    void R_AddRawFileToList(void *asset, void *inData)
+    {
+        RawFileList *rawFileList = reinterpret_cast<RawFileList *>(inData);
+        RawFile *rawFile = reinterpret_cast<RawFile *>(asset);
+
+        if (!rawFile)
+        {
+            Com_PrintError(CON_CHANNEL_ERROR, "R_AddRawFileToList: Null RawFile!\n");
+            return;
+        }
+
+        if (rawFileList->count >= MAX_RAWFILES)
+        {
+            Com_PrintError(CON_CHANNEL_ERROR, "R_AddRawFileToList: RawFileList is full!\n");
+            return;
+        }
+
+        rawFileList->files[rawFileList->count++] = rawFile;
+    }
+
+    void R_GetRawFileList(RawFileList *rawFileList)
+    {
+        rawFileList->count = 0;
+        DB_EnumXAssets_FastFile(ASSET_TYPE_RAWFILE, R_AddRawFileToList, rawFileList, true);
     }
 
-    void Cmd_test_f()
+    void Cmd_rawfilesdump()
     {
-        xbox::DbgPrint("test command\n");
+        RawFileList rawFileList;
+        R_GetRawFileList(&rawFileList);
+
+        Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumping %d raw files to `raw\\` %d\n", rawFileList.count);
+
+        for (unsigned int i = 0; i < rawFileList.count; i++)
+        {
+            auto rawfile = rawFileList.files[i];
+            std::string asset_name = rawfile->name;
+            std::replace(asset_name.begin(), asset_name.end(), '/', '\\'); // Replace forward slashes with backslashes
+            filesystem::write_file_to_disk(("game:\\dump\\" + asset_name).c_str(), rawfile->buffer, rawfile->len);
+        }
     }
 
     void init()
@@ -227,8 +261,7 @@ namespace mp
         Scr_ReadFile_FastFile_Detour = Detour(Scr_ReadFile_FastFile, Scr_ReadFile_FastFile_Hook);
         Scr_ReadFile_FastFile_Detour.Install();
 
-        cmd_function_s *test_cmd = new cmd_function_s;
-
-        Cmd_AddCommandInternal("test", Cmd_test_f, test_cmd);
+        cmd_function_s *rawfilesdump_VAR = new cmd_function_s;
+        Cmd_AddCommandInternal("rawfilesdump", Cmd_rawfilesdump, rawfilesdump_VAR);
     }
 }
diff --git a/src/game/mp_structs.h b/src/game/mp_structs.h
index e55935d..ffda4ed 100644
--- a/src/game/mp_structs.h
+++ b/src/game/mp_structs.h
@@ -2,7 +2,6 @@
 
 namespace mp
 {
-
     struct cmd_function_s
     {
         cmd_function_s *next;
@@ -157,6 +156,118 @@ namespace mp
         int numEntityChars;
     };
 
+    enum XAssetType : __int32
+    {
+        ASSET_TYPE_XMODELPIECES = 0x0,
+        ASSET_TYPE_PHYSPRESET = 0x1,
+        ASSET_TYPE_XANIMPARTS = 0x2,
+        ASSET_TYPE_XMODEL = 0x3,
+        ASSET_TYPE_MATERIAL = 0x4,
+        ASSET_TYPE_PIXELSHADER = 0x5,
+        ASSET_TYPE_TECHNIQUE_SET = 0x6,
+        ASSET_TYPE_IMAGE = 0x7,
+        ASSET_TYPE_SOUND = 0x8,
+        ASSET_TYPE_SOUND_CURVE = 0x9,
+        ASSET_TYPE_LOADED_SOUND = 0xA,
+        ASSET_TYPE_CLIPMAP = 0xB,
+        ASSET_TYPE_CLIPMAP_PVS = 0xC,
+        ASSET_TYPE_COMWORLD = 0xD,
+        ASSET_TYPE_GAMEWORLD_SP = 0xE,
+        ASSET_TYPE_GAMEWORLD_MP = 0xF,
+        ASSET_TYPE_MAP_ENTS = 0x10,
+        ASSET_TYPE_GFXWORLD = 0x11,
+        ASSET_TYPE_LIGHT_DEF = 0x12,
+        ASSET_TYPE_UI_MAP = 0x13,
+        ASSET_TYPE_FONT = 0x14,
+        ASSET_TYPE_MENULIST = 0x15,
+        ASSET_TYPE_MENU = 0x16,
+        ASSET_TYPE_LOCALIZE_ENTRY = 0x17,
+        ASSET_TYPE_WEAPON = 0x18,
+        ASSET_TYPE_SNDDRIVER_GLOBALS = 0x19,
+        ASSET_TYPE_FX = 0x1A,
+        ASSET_TYPE_IMPACT_FX = 0x1B,
+        ASSET_TYPE_AITYPE = 0x1C,
+        ASSET_TYPE_MPTYPE = 0x1D,
+        ASSET_TYPE_CHARACTER = 0x1E,
+        ASSET_TYPE_XMODELALIAS = 0x1F,
+        ASSET_TYPE_RAWFILE = 0x20,
+        ASSET_TYPE_STRINGTABLE = 0x21,
+        ASSET_TYPE_COUNT = 0x22,
+        ASSET_TYPE_STRING = 0x22,
+        ASSET_TYPE_ASSETLIST = 0x23,
+    };
+
+    struct RawFile
+    {
+        const char *name;
+        int len;
+        const char *buffer;
+    };
+
+    union XAssetHeader
+    {
+        // XModelPieces *xmodelPieces;
+        // PhysPreset *physPreset;
+        // XAnimParts *parts;
+        // XModel *model;
+        // Material *material;
+        // MaterialPixelShader *pixelShader;
+        // MaterialVertexShader *vertexShader;
+        // MaterialTechniqueSet *techniqueSet;
+        // GfxImage *image;
+        // snd_alias_list_t *sound;
+        // SndCurve *sndCurve;
+        // LoadedSound *loadSnd;
+        // clipMap_t *clipMap;
+        // ComWorld *comWorld;
+        // GameWorldSp *gameWorldSp;
+        // GameWorldMp *gameWorldMp;
+        // MapEnts *mapEnts;
+        // GfxWorld *gfxWorld;
+        // GfxLightDef *lightDef;
+        // Font_s *font;
+        // MenuList *menuList;
+        // menuDef_t *menu;
+        // LocalizeEntry *localize;
+        // WeaponDef *weapon;
+        // SndDriverGlobals *sndDriverGlobals;
+        // const FxEffectDef *fx;
+        // FxImpactTable *impactFx;
+        RawFile *rawfile;
+        // StringTable *stringTable;
+        void *data;
+    };
+
+    enum conChannel_t : __int32
+    {
+        CON_CHANNEL_DONT_FILTER = 0x0,
+        CON_CHANNEL_ERROR = 0x1,
+        CON_CHANNEL_GAMENOTIFY = 0x2,
+        CON_CHANNEL_BOLDGAME = 0x3,
+        CON_CHANNEL_SUBTITLE = 0x4,
+        CON_CHANNEL_OBITUARY = 0x5,
+        CON_CHANNEL_LOGFILEONLY = 0x6,
+        CON_CHANNEL_CONSOLEONLY = 0x7,
+        CON_CHANNEL_GFX = 0x8,
+        CON_CHANNEL_SOUND = 0x9,
+        CON_CHANNEL_FILES = 0xA,
+        CON_CHANNEL_DEVGUI = 0xB,
+        CON_CHANNEL_PROFILE = 0xC,
+        CON_CHANNEL_UI = 0xD,
+        CON_CHANNEL_CLIENT = 0xE,
+        CON_CHANNEL_SERVER = 0xF,
+        CON_CHANNEL_SYSTEM = 0x10,
+        CON_CHANNEL_PLAYERWEAP = 0x11,
+        CON_CHANNEL_AI = 0x12,
+        CON_CHANNEL_ANIM = 0x13,
+        CON_CHANNEL_PHYS = 0x14,
+        CON_CHANNEL_FX = 0x15,
+        CON_CHANNEL_LEADERBOARDS = 0x16,
+        CON_CHANNEL_PARSERSCRIPT = 0x17,
+        CON_CHANNEL_SCRIPT = 0x18,
+        CON_BUILTIN_CHANNEL_COUNT = 0x19,
+    };
+
     typedef void (*Cbuf_AddText_t)(int localClientNum, const char *text);
 
     typedef void (*CL_ConsolePrint_t)(int localClientNum, int channel, const char *txt, int duration, int pixelWidth, int flags);
@@ -164,7 +275,12 @@ namespace mp
 
     typedef void (*Cmd_AddCommandInternal_t)(const char *cmdName, void (*function)(), cmd_function_s *allocedCmd);
 
-    typedef void (*Com_Printf_t)(int channel, const char *fmt, ...);
+    typedef void (*Com_PrintError_t)(conChannel_t channel, const char *fmt, ...);
+    typedef void (*Com_PrintMessage_t)(conChannel_t channel, const char *msg, int error);
+    typedef void (*Com_Printf_t)(conChannel_t channel, const char *fmt, ...);
+    typedef void (*Com_PrintWarning_t)(conChannel_t channel, const char *fmt, ...);
+
+    typedef void (*DB_EnumXAssets_FastFile_t)(XAssetType type, void (*func)(void *asset, void *inData), void *inData, bool includeOverride);
 
     typedef void (*Load_MapEntsPtr_t)();
 
diff --git a/src/game/sp_main.cpp b/src/game/sp_main.cpp
index 4e98e64..d0673a1 100644
--- a/src/game/sp_main.cpp
+++ b/src/game/sp_main.cpp
@@ -44,7 +44,11 @@ namespace sp
 
     Cmd_AddCommandInternal_t Cmd_AddCommandInternal = reinterpret_cast<Cmd_AddCommandInternal_t>(0x821DFAD0);
 
+    Com_PrintError_t Com_PrintError = reinterpret_cast<Com_PrintError_t>(0x821DAC90);
     Com_Printf_t Com_Printf = reinterpret_cast<Com_Printf_t>(0x821DC0A0);
+    Com_PrintWarning_t Com_PrintWarning = reinterpret_cast<Com_PrintWarning_t>(0x821DA798);
+
+    DB_EnumXAssets_FastFile_t DB_EnumXAssets_FastFile = reinterpret_cast<DB_EnumXAssets_FastFile_t>(0x822AEF88);
 
     Scr_AddSourceBuffer_t Scr_AddSourceBuffer = reinterpret_cast<Scr_AddSourceBuffer_t>(0x821C5A18);
     Scr_ReadFile_FastFile_t Scr_ReadFile_FastFile = reinterpret_cast<Scr_ReadFile_FastFile_t>(0x821C5978);
@@ -89,19 +93,6 @@ namespace sp
     {
         xbox::DbgPrint("Scr_ReadFile_FastFile_Hook extFilename=%s \n", extFilename);
 
-        auto contents = Scr_ReadFile_FastFile_Detour.GetOriginal<decltype(&Scr_ReadFile_FastFile_Hook)>()(filename, extFilename, codePos, archive);
-
-        // Dump the file to disk if it exists
-        // It might not exist if it's not a stock file
-        if (contents != nullptr)
-        {
-            // Dump the file to disk
-            std::string file_path = "game:\\dump\\";
-            file_path += extFilename;
-            std::replace(file_path.begin(), file_path.end(), '/', '\\'); // Replace forward slashes with backslashes
-            filesystem::write_file_to_disk(file_path.c_str(), contents, strlen(contents));
-        }
-
         std::string raw_file_path = "game:\\raw\\";
         raw_file_path += extFilename;
         std::replace(raw_file_path.begin(), raw_file_path.end(), '/', '\\'); // Replace forward slashes with backslashes
@@ -131,12 +122,56 @@ namespace sp
             }
         }
 
-        return contents;
+        return Scr_ReadFile_FastFile_Detour.GetOriginal<decltype(&Scr_ReadFile_FastFile_Hook)>()(filename, extFilename, codePos, archive);
     }
 
-    void Cmd_test_f()
+    const unsigned int MAX_RAWFILES = 2048;
+    struct RawFileList
+    {
+        unsigned int count;
+        RawFile *files[MAX_RAWFILES];
+    };
+
+    void R_AddRawFileToList(void *asset, void *inData)
     {
-        xbox::DbgPrint("test command\n");
+        RawFileList *rawFileList = reinterpret_cast<RawFileList *>(inData);
+        RawFile *rawFile = reinterpret_cast<RawFile *>(asset);
+
+        if (!rawFile)
+        {
+            Com_PrintError(CON_CHANNEL_ERROR, "R_AddRawFileToList: Null RawFile!\n");
+            return;
+        }
+
+        if (rawFileList->count >= MAX_RAWFILES)
+        {
+            Com_PrintError(CON_CHANNEL_ERROR, "R_AddRawFileToList: RawFileList is full!\n");
+            return;
+        }
+
+        rawFileList->files[rawFileList->count++] = rawFile;
+    }
+
+    void R_GetRawFileList(RawFileList *rawFileList)
+    {
+        rawFileList->count = 0;
+        DB_EnumXAssets_FastFile(ASSET_TYPE_RAWFILE, R_AddRawFileToList, rawFileList, true);
+    }
+
+    void Cmd_rawfilesdump()
+    {
+        RawFileList rawFileList;
+        R_GetRawFileList(&rawFileList);
+
+        Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumping %d raw files to `raw\\` %d\n", rawFileList.count);
+
+        for (unsigned int i = 0; i < rawFileList.count; i++)
+        {
+            auto rawfile = rawFileList.files[i];
+            std::string asset_name = rawfile->name;
+            std::replace(asset_name.begin(), asset_name.end(), '/', '\\'); // Replace forward slashes with backslashes
+            filesystem::write_file_to_disk(("game:\\dump\\" + asset_name).c_str(), rawfile->buffer, rawfile->len);
+        }
     }
 
     void init()
@@ -152,8 +187,7 @@ namespace sp
         Scr_ReadFile_FastFile_Detour = Detour(Scr_ReadFile_FastFile, Scr_ReadFile_FastFile_Hook);
         Scr_ReadFile_FastFile_Detour.Install();
 
-        cmd_function_s *test_cmd = new cmd_function_s;
-
-        Cmd_AddCommandInternal("test", Cmd_test_f, test_cmd);
+        cmd_function_s *rawfilesdump_VAR = new cmd_function_s;
+        Cmd_AddCommandInternal("rawfilesdump", Cmd_rawfilesdump, rawfilesdump_VAR);
     }
 }
diff --git a/src/game/sp_structs.h b/src/game/sp_structs.h
index 5553ac2..6a95cf5 100644
--- a/src/game/sp_structs.h
+++ b/src/game/sp_structs.h
@@ -149,6 +149,118 @@ namespace sp
         void(__fastcall *function)();
     };
 
+    enum XAssetType : __int32
+    {
+        ASSET_TYPE_XMODELPIECES = 0x0,
+        ASSET_TYPE_PHYSPRESET = 0x1,
+        ASSET_TYPE_XANIMPARTS = 0x2,
+        ASSET_TYPE_XMODEL = 0x3,
+        ASSET_TYPE_MATERIAL = 0x4,
+        ASSET_TYPE_PIXELSHADER = 0x5,
+        ASSET_TYPE_TECHNIQUE_SET = 0x6,
+        ASSET_TYPE_IMAGE = 0x7,
+        ASSET_TYPE_SOUND = 0x8,
+        ASSET_TYPE_SOUND_CURVE = 0x9,
+        ASSET_TYPE_LOADED_SOUND = 0xA,
+        ASSET_TYPE_CLIPMAP = 0xB,
+        ASSET_TYPE_CLIPMAP_PVS = 0xC,
+        ASSET_TYPE_COMWORLD = 0xD,
+        ASSET_TYPE_GAMEWORLD_SP = 0xE,
+        ASSET_TYPE_GAMEWORLD_MP = 0xF,
+        ASSET_TYPE_MAP_ENTS = 0x10,
+        ASSET_TYPE_GFXWORLD = 0x11,
+        ASSET_TYPE_LIGHT_DEF = 0x12,
+        ASSET_TYPE_UI_MAP = 0x13,
+        ASSET_TYPE_FONT = 0x14,
+        ASSET_TYPE_MENULIST = 0x15,
+        ASSET_TYPE_MENU = 0x16,
+        ASSET_TYPE_LOCALIZE_ENTRY = 0x17,
+        ASSET_TYPE_WEAPON = 0x18,
+        ASSET_TYPE_SNDDRIVER_GLOBALS = 0x19,
+        ASSET_TYPE_FX = 0x1A,
+        ASSET_TYPE_IMPACT_FX = 0x1B,
+        ASSET_TYPE_AITYPE = 0x1C,
+        ASSET_TYPE_MPTYPE = 0x1D,
+        ASSET_TYPE_CHARACTER = 0x1E,
+        ASSET_TYPE_XMODELALIAS = 0x1F,
+        ASSET_TYPE_RAWFILE = 0x20,
+        ASSET_TYPE_STRINGTABLE = 0x21,
+        ASSET_TYPE_COUNT = 0x22,
+        ASSET_TYPE_STRING = 0x22,
+        ASSET_TYPE_ASSETLIST = 0x23,
+    };
+
+    struct RawFile
+    {
+        const char *name;
+        int len;
+        const char *buffer;
+    };
+
+    union XAssetHeader
+    {
+        // XModelPieces *xmodelPieces;
+        // PhysPreset *physPreset;
+        // XAnimParts *parts;
+        // XModel *model;
+        // Material *material;
+        // MaterialPixelShader *pixelShader;
+        // MaterialVertexShader *vertexShader;
+        // MaterialTechniqueSet *techniqueSet;
+        // GfxImage *image;
+        // snd_alias_list_t *sound;
+        // SndCurve *sndCurve;
+        // LoadedSound *loadSnd;
+        // clipMap_t *clipMap;
+        // ComWorld *comWorld;
+        // GameWorldSp *gameWorldSp;
+        // GameWorldMp *gameWorldMp;
+        // MapEnts *mapEnts;
+        // GfxWorld *gfxWorld;
+        // GfxLightDef *lightDef;
+        // Font_s *font;
+        // MenuList *menuList;
+        // menuDef_t *menu;
+        // LocalizeEntry *localize;
+        // WeaponDef *weapon;
+        // SndDriverGlobals *sndDriverGlobals;
+        // const FxEffectDef *fx;
+        // FxImpactTable *impactFx;
+        RawFile *rawfile;
+        // StringTable *stringTable;
+        void *data;
+    };
+
+    enum conChannel_t : __int32
+    {
+        CON_CHANNEL_DONT_FILTER = 0x0,
+        CON_CHANNEL_ERROR = 0x1,
+        CON_CHANNEL_GAMENOTIFY = 0x2,
+        CON_CHANNEL_BOLDGAME = 0x3,
+        CON_CHANNEL_SUBTITLE = 0x4,
+        CON_CHANNEL_OBITUARY = 0x5,
+        CON_CHANNEL_LOGFILEONLY = 0x6,
+        CON_CHANNEL_CONSOLEONLY = 0x7,
+        CON_CHANNEL_GFX = 0x8,
+        CON_CHANNEL_SOUND = 0x9,
+        CON_CHANNEL_FILES = 0xA,
+        CON_CHANNEL_DEVGUI = 0xB,
+        CON_CHANNEL_PROFILE = 0xC,
+        CON_CHANNEL_UI = 0xD,
+        CON_CHANNEL_CLIENT = 0xE,
+        CON_CHANNEL_SERVER = 0xF,
+        CON_CHANNEL_SYSTEM = 0x10,
+        CON_CHANNEL_PLAYERWEAP = 0x11,
+        CON_CHANNEL_AI = 0x12,
+        CON_CHANNEL_ANIM = 0x13,
+        CON_CHANNEL_PHYS = 0x14,
+        CON_CHANNEL_FX = 0x15,
+        CON_CHANNEL_LEADERBOARDS = 0x16,
+        CON_CHANNEL_PARSERSCRIPT = 0x17,
+        CON_CHANNEL_SCRIPT = 0x18,
+        CON_BUILTIN_CHANNEL_COUNT = 0x19,
+    };
+
     typedef void (*Cbuf_AddText_t)(int localClientNum, const char *text);
 
     typedef void (*CL_ConsolePrint_t)(int localClientNum, int channel, const char *txt, int duration, int pixelWidth, int flags);
@@ -156,7 +268,12 @@ namespace sp
 
     typedef void (*Cmd_AddCommandInternal_t)(const char *cmdName, void (*function)(), cmd_function_s *allocedCmd);
 
-    typedef void (*Com_Printf_t)(int channel, const char *fmt, ...);
+    typedef void (*Com_PrintError_t)(conChannel_t channel, const char *fmt, ...);
+    typedef void (*Com_PrintMessage_t)(conChannel_t channel, const char *msg, int error);
+    typedef void (*Com_Printf_t)(conChannel_t channel, const char *fmt, ...);
+    typedef void (*Com_PrintWarning_t)(conChannel_t channel, const char *fmt, ...);
+
+    typedef void (*DB_EnumXAssets_FastFile_t)(XAssetType type, void (*func)(void *asset, void *inData), void *inData, bool includeOverride);
 
     typedef char *(*Scr_AddSourceBuffer_t)(const char *filename, const char *extFilename, const char *codePos, bool archive);
     typedef char *(*Scr_ReadFile_FastFile_t)(const char *filename, const char *extFilename, const char *codePos, bool archive);

From 75301b9a02e877e4803ce62834ec31841ecd5998 Mon Sep 17 00:00:00 2001
From: Michael Oliver <michael.oliver_@outlook.com>
Date: Tue, 4 Mar 2025 23:23:49 +0000
Subject: [PATCH 2/2] fix: ensure all nested directories are created

---
 src/filesystem.cpp | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/src/filesystem.cpp b/src/filesystem.cpp
index a7a2554..b54a7a7 100644
--- a/src/filesystem.cpp
+++ b/src/filesystem.cpp
@@ -10,25 +10,30 @@ namespace filesystem
 {
     void create_nested_dirs(const char *path)
     {
+        if (!path || !*path)
+            return;
+
         char temp_path[256];
-        strncpy(temp_path, path, sizeof(temp_path));
+        strncpy(temp_path, path, sizeof(temp_path) - 1);
+        temp_path[sizeof(temp_path) - 1] = '\0';
 
         char *p = temp_path;
-        while (*p)
+
+        // Skip leading drive letter (e.g., "C:\") or "game:\" prefix
+        if ((p[0] && p[1] == ':') || strncmp(p, "game:\\", 6) == 0)
+            p += (p[1] == ':' ? 3 : 6); // Move past "C:\" or "game:\"
+
+        for (; *p; p++)
         {
             if (*p == '\\' || *p == '/')
             {
                 *p = '\0';
-
-                // Create the directory if it doesn't exist
-                _mkdir(temp_path);
-
+                _mkdir(temp_path); // Attempt to create the directory
                 *p = '\\';
             }
-            p++;
         }
 
-        _mkdir(temp_path);
+        _mkdir(temp_path); // Create final directory
     }
 
     /**