From 77dd2d628fef32114d55cebf08a7ad8429c52409 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Tue, 8 Apr 2025 14:40:22 +0100 Subject: [PATCH 1/4] feat: add initial pre alpha 328 build support --- iw3xe.vcxproj | 7 + resources/xenia/plugins/415607E6/plugins.toml | 12 +- src/game/game.cpp | 6 + src/game/iw3-328/cg_consolecmds.cpp | 277 ++++++++++++++++++ src/game/iw3-328/cg_consolecmds.h | 10 + src/game/iw3-328/main.cpp | 12 + src/game/iw3-328/main.h | 8 + src/game/iw3-328/scr_parser.cpp | 57 ++++ src/game/iw3-328/scr_parser.h | 4 + src/game/iw3-328/structs.h | 6 + 10 files changed, 397 insertions(+), 2 deletions(-) create mode 100644 src/game/iw3-328/cg_consolecmds.cpp create mode 100644 src/game/iw3-328/cg_consolecmds.h create mode 100644 src/game/iw3-328/main.cpp create mode 100644 src/game/iw3-328/main.h create mode 100644 src/game/iw3-328/scr_parser.cpp create mode 100644 src/game/iw3-328/scr_parser.h create mode 100644 src/game/iw3-328/structs.h diff --git a/iw3xe.vcxproj b/iw3xe.vcxproj index 1f74455..5afd80b 100644 --- a/iw3xe.vcxproj +++ b/iw3xe.vcxproj @@ -71,6 +71,9 @@ + + + @@ -80,6 +83,10 @@ + + + + diff --git a/resources/xenia/plugins/415607E6/plugins.toml b/resources/xenia/plugins/415607E6/plugins.toml index 1f2ccfe..2161957 100644 --- a/resources/xenia/plugins/415607E6/plugins.toml +++ b/resources/xenia/plugins/415607E6/plugins.toml @@ -2,8 +2,7 @@ title_name = "Call of Duty 4: Modern Warfare" title_id = "415607E6" #media_id = "2C8C0267" # Disc (USA, Europe): http://redump.org/disc/37074 -# TU4 - +# Retail TU4 SP [[plugin]] author = "mo" name = "iw3xe" @@ -11,9 +10,18 @@ file = "iw3xe.xex" hash = "B4B0A3571D5160E2" # default.xex is_enabled = true +# Retail TU4 MP [[plugin]] author = "mo" name = "iw3xe" file = "iw3xe.xex" hash = "F5F903E4F326EB10" # default_mp.xex is_enabled = true + +# Pre Alpha 328 MP +[[plugin]] +author = "mo" +name = "iw3xe" +file = "iw3xe.xex" +hash = "C0F9D9F3B048B4C4" # default_mp.xex +is_enabled = true diff --git a/src/game/game.cpp b/src/game/game.cpp index 25e53ac..3426527 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -4,6 +4,7 @@ #include "game.h" #include "mp_main.h" #include "sp_main.h" +#include "iw3-328/main.h" namespace game { @@ -21,6 +22,11 @@ namespace game xbox::show_notification(L"IW3xe sp"); sp::init(); } + else if (strncmp((char *)0x820019EC, "multiplayer", 11) == 0) + { + xbox::show_notification(L"IW3xe 328 MP"); + iw3_328::init(); + } else { xbox::show_notification(L"IW3xe unsupported executable"); diff --git a/src/game/iw3-328/cg_consolecmds.cpp b/src/game/iw3-328/cg_consolecmds.cpp new file mode 100644 index 0000000..f930702 --- /dev/null +++ b/src/game/iw3-328/cg_consolecmds.cpp @@ -0,0 +1,277 @@ +#pragma warning(push) +#pragma warning(disable : 4480) + +#include + +#include "..\..\detour.h" +#include "..\..\filesystem.h" +#include "..\..\xboxkrnl.h" + +namespace iw3_328 +{ + typedef void (*CG_InitConsoleCommands_t)(); + CG_InitConsoleCommands_t CG_InitConsoleCommands = reinterpret_cast(0x8211F608); + + struct cmd_function_s + { + cmd_function_s *next; + const char *name; + const char *autoCompleteDir; + const char *autoCompleteExt; + void (*function)(); + }; + + typedef void (*Cmd_AddCommandInternal_t)(const char *cmdName, void (*function)(), cmd_function_s *allocedCmd); + Cmd_AddCommandInternal_t Cmd_AddCommandInternal = reinterpret_cast(0x821FEA68); + + 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 (*Com_Printf_t)(conChannel_t channel, const char *fmt, ...); + Com_Printf_t Com_Printf = reinterpret_cast(0x821FFE08); + + typedef void (*Com_PrintError_t)(conChannel_t channel, const char *fmt, ...); + Com_PrintError_t Com_PrintError = reinterpret_cast(0x821FFFA0); + + struct RawFile + { + const char *name; + int len; + const char *buffer; + }; + + 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_CLIPMAP = 0xA, + ASSET_TYPE_CLIPMAP_PVS = 0xB, + ASSET_TYPE_COMWORLD = 0xC, + ASSET_TYPE_GAMEWORLD_SP = 0xD, + ASSET_TYPE_GAMEWORLD_MP = 0xE, + ASSET_TYPE_MAP_ENTS = 0xF, + ASSET_TYPE_GFXWORLD = 0x10, + ASSET_TYPE_LIGHT_DEF = 0x11, + ASSET_TYPE_UI_MAP = 0x12, + ASSET_TYPE_FONT = 0x13, + ASSET_TYPE_MENULIST = 0x14, + ASSET_TYPE_MENU = 0x15, + ASSET_TYPE_LOCALIZE_ENTRY = 0x16, + ASSET_TYPE_WEAPON = 0x17, + ASSET_TYPE_SNDDRIVER_GLOBALS = 0x18, + ASSET_TYPE_FX = 0x19, + ASSET_TYPE_IMPACT_FX = 0x1A, + ASSET_TYPE_AITYPE = 0x1B, + ASSET_TYPE_MPTYPE = 0x1C, + ASSET_TYPE_CHARACTER = 0x1D, + ASSET_TYPE_XMODELALIAS = 0x1E, + ASSET_TYPE_RAWFILE = 0x1F, + ASSET_TYPE_STRINGTABLE = 0x20, + ASSET_TYPE_COUNT = 0x21, + ASSET_TYPE_STRING = 0x21, + ASSET_TYPE_ASSETLIST = 0x22, + }; + + 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; + // 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; + }; + + typedef int (*DB_GetAllXAssetOfType_t)(XAssetType type, void *assets, int maxCount); + DB_GetAllXAssetOfType_t DB_GetAllXAssetOfType = reinterpret_cast(0x82265CA8); + + void Cmd_dump_rawfiles_f() + { + const int MAX_RAWFILES = 2048; + XAssetHeader assets[MAX_RAWFILES]; + int count = DB_GetAllXAssetOfType(ASSET_TYPE_RAWFILE, assets, MAX_RAWFILES); + // Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumping %d raw files to 'game::\\_iw3xe\\raw\\' %d\n", count); + for (int i = 0; i < count; i++) + { + auto rawfile = assets[i].rawfile; + 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:\\_iw3xe\\dump\\" + asset_name).c_str(), rawfile->buffer, rawfile->len); + } + Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumped %d raw files to 'game::\\_iw3xe\\dump\\'\n", count); + } + + struct cplane_s; + struct cStaticModel_s; + struct dmaterial_t; + struct cbrushside_t; + struct cNode_t; + struct cLeaf_t; + struct cLeafBrushNode_s; + struct CollisionBorder; + struct CollisionPartition; + struct CollisionAabbTree; + struct cmodel_t; + struct cbrush_t; + + struct MapEnts + { + const char *name; + char *entityString; + int numEntityChars; + }; + + struct clipMap_t + { + const char *name; + int planeCount; + cplane_s *planes; + unsigned int numStaticModels; + cStaticModel_s *staticModelList; + unsigned int numMaterials; + dmaterial_t *materials; + unsigned int numBrushSides; + cbrushside_t *brushsides; + unsigned int numBrushEdges; + unsigned __int8 *brushEdges; + unsigned int numNodes; + cNode_t *nodes; + unsigned int numLeafs; + cLeaf_t *leafs; + unsigned int leafbrushNodesCount; + cLeafBrushNode_s *leafbrushNodes; + unsigned int numLeafBrushes; + unsigned __int16 *leafbrushes; + unsigned int numLeafSurfaces; + unsigned int *leafsurfaces; + unsigned int vertCount; + float (*verts)[3]; + int triCount; + unsigned __int16 *triIndices; + unsigned __int8 *triEdgeIsWalkable; + int borderCount; + CollisionBorder *borders; + int partitionCount; + CollisionPartition *partitions; + int aabbTreeCount; + CollisionAabbTree *aabbTrees; + unsigned int numSubModels; + cmodel_t *cmodels; + unsigned __int16 numBrushes; + cbrush_t *brushes; + int numClusters; + int clusterBytes; + unsigned __int8 *visibility; + int vised; + MapEnts *mapEnts; + // cbrush_t *box_brush; + // cmodel_t box_model; + // unsigned __int16 dynEntCount[2]; + // DynEntityDef *dynEntDefList[2]; + // DynEntityPose *dynEntPoseList[2]; + // DynEntityClient *dynEntClientList[2]; + // DynEntityColl *dynEntCollList[2]; + // unsigned int checksum; + }; + + auto cm = reinterpret_cast(0x831C1AC8); + + void Cmd_dump_mapents_f() + { + if (!cm->name) + { + Com_PrintError(CON_CHANNEL_CONSOLEONLY, "No map loaded\n"); + return; + } + + // cm.name contains a string like this `maps/mp/mp_backlot.d3dsp` + std::string asset_name = cm->name; + asset_name += ".ents"; + std::replace(asset_name.begin(), asset_name.end(), '/', '\\'); // Replace forward slashes with backslashes + filesystem::write_file_to_disk(("game:\\_iw3xe\\dump\\" + asset_name).c_str(), cm->mapEnts->entityString, cm->mapEnts->numEntityChars); + Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumped map ents to 'game:\\_iw3xe\\dump\\%s'\n", cm->mapEnts->numEntityChars, asset_name.c_str()); + } + + // Detour CG_InitConsoleCommands_Detour; + + // void CG_InitConsoleCommands_Hook() + // { + // xbox::DbgPrint("CG_InitConsoleCommands_Hook\n"); + // CG_InitConsoleCommands_Detour.GetOriginal()(); + // // Add your custom command here + + // cmd_function_s *rawfilesdump_VAR = new cmd_function_s; + // Cmd_AddCommandInternal("rawfiledump", Cmd_rawfilesdump, rawfilesdump_VAR); + // } + + void init_cg_consolecmds() + { + // xbox::DbgPrint("Initializing CG_InitConsoleCommands detour...\n"); + // CG_InitConsoleCommands_Detour = Detour(CG_InitConsoleCommands, CG_InitConsoleCommands_Hook); + // CG_InitConsoleCommands_Detour.Install(); + + cmd_function_s *dump_mapents_VAR = new cmd_function_s; + Cmd_AddCommandInternal("dump_mapents", Cmd_dump_mapents_f, dump_mapents_VAR); + + cmd_function_s *dump_rawfiles_VAR = new cmd_function_s; + Cmd_AddCommandInternal("dump_rawfiles", Cmd_dump_rawfiles_f, dump_rawfiles_VAR); + } +} \ No newline at end of file diff --git a/src/game/iw3-328/cg_consolecmds.h b/src/game/iw3-328/cg_consolecmds.h new file mode 100644 index 0000000..796600e --- /dev/null +++ b/src/game/iw3-328/cg_consolecmds.h @@ -0,0 +1,10 @@ +#include + +#include "..\..\detour.h" +#include "..\..\filesystem.h" +#include "..\..\xboxkrnl.h" + +namespace iw3_328 +{ + void init_cg_consolecmds(); +} diff --git a/src/game/iw3-328/main.cpp b/src/game/iw3-328/main.cpp new file mode 100644 index 0000000..6ae7e27 --- /dev/null +++ b/src/game/iw3-328/main.cpp @@ -0,0 +1,12 @@ +#include "structs.h" +#include "cg_consolecmds.h" +#include "scr_parser.h" + +namespace iw3_328 +{ + void init() + { + init_cg_consolecmds(); + init_scr_parser(); + } +} diff --git a/src/game/iw3-328/main.h b/src/game/iw3-328/main.h new file mode 100644 index 0000000..fcbfddb --- /dev/null +++ b/src/game/iw3-328/main.h @@ -0,0 +1,8 @@ +#pragma once + +#include "structs.h" + +namespace iw3_328 +{ + void init(); +} diff --git a/src/game/iw3-328/scr_parser.cpp b/src/game/iw3-328/scr_parser.cpp new file mode 100644 index 0000000..3b31d31 --- /dev/null +++ b/src/game/iw3-328/scr_parser.cpp @@ -0,0 +1,57 @@ +#include + +#include "..\..\detour.h" +#include "..\..\filesystem.h" +#include "..\..\xboxkrnl.h" + +namespace iw3_328 +{ + typedef char *(*Scr_ReadFile_FastFile_t)(const char *filename, const char *extFilename, const char *codePos, bool archive); + + Scr_ReadFile_FastFile_t Scr_ReadFile_FastFile = reinterpret_cast(0x8221AFB8); + + Detour Scr_ReadFile_FastFile_Detour; + + 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); + + std::string raw_file_path = "game:\\iw3xe\\raw\\"; + raw_file_path += extFilename; + std::replace(raw_file_path.begin(), raw_file_path.end(), '/', '\\'); // Replace forward slashes with backslashes + if (filesystem::file_exists(raw_file_path)) + { + xbox::DbgPrint("Found raw file: %s\n", raw_file_path.c_str()); + // return ReadFileContents(raw_file_path); + std::string new_contents = filesystem::read_file_to_string(raw_file_path); + if (!new_contents.empty()) + { + + // Allocate new memory and copy the data + size_t new_size = new_contents.size() + 1; // Include null terminator + char *new_memory = static_cast(malloc(new_size)); + + if (new_memory) + { + memcpy(new_memory, new_contents.c_str(), new_size); // Copy with null terminator + + xbox::DbgPrint("Replaced contents from file: %s\n", raw_file_path.c_str()); + return new_memory; + } + else + { + xbox::DbgPrint("Failed to allocate memory for contents replacement.\n"); + } + } + } + + return Scr_ReadFile_FastFile_Detour.GetOriginal()(filename, extFilename, codePos, archive); + } + + void init_scr_parser() + { + xbox::DbgPrint("Initializing Scr_ReadFile_FastFile detour...\n"); + Scr_ReadFile_FastFile_Detour = Detour(Scr_ReadFile_FastFile, Scr_ReadFile_FastFile_Hook); + Scr_ReadFile_FastFile_Detour.Install(); + } +} \ No newline at end of file diff --git a/src/game/iw3-328/scr_parser.h b/src/game/iw3-328/scr_parser.h new file mode 100644 index 0000000..83357a9 --- /dev/null +++ b/src/game/iw3-328/scr_parser.h @@ -0,0 +1,4 @@ +namespace iw3_328 +{ + void init_scr_parser(); +} diff --git a/src/game/iw3-328/structs.h b/src/game/iw3-328/structs.h new file mode 100644 index 0000000..aea6d22 --- /dev/null +++ b/src/game/iw3-328/structs.h @@ -0,0 +1,6 @@ +#pragma once + +namespace iw3_328 +{ + +} \ No newline at end of file From 7e6421d8b7c9bbf06854f69b24ade48b34ec22c4 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Tue, 8 Apr 2025 15:33:04 +0100 Subject: [PATCH 2/4] add cmd `dump_images` --- src/game/iw3-328/cg_consolecmds.cpp | 383 +++++++++++++++++++++++++++- 1 file changed, 382 insertions(+), 1 deletion(-) diff --git a/src/game/iw3-328/cg_consolecmds.cpp b/src/game/iw3-328/cg_consolecmds.cpp index f930702..3bacd24 100644 --- a/src/game/iw3-328/cg_consolecmds.cpp +++ b/src/game/iw3-328/cg_consolecmds.cpp @@ -1,6 +1,10 @@ #pragma warning(push) #pragma warning(disable : 4480) +#include +#include +#include + #include #include "..\..\detour.h" @@ -60,6 +64,59 @@ namespace iw3_328 typedef void (*Com_PrintError_t)(conChannel_t channel, const char *fmt, ...); Com_PrintError_t Com_PrintError = reinterpret_cast(0x821FFFA0); + enum MapType : __int32 + { + MAPTYPE_NONE = 0x0, + MAPTYPE_INVALID1 = 0x1, + MAPTYPE_INVALID2 = 0x2, + MAPTYPE_2D = 0x3, + MAPTYPE_3D = 0x4, + MAPTYPE_CUBE = 0x5, + MAPTYPE_COUNT = 0x6, + }; + + struct CardMemory + { + int platform[1]; + }; + + struct GfxImageLoadDef; // Forward declaration + + union GfxTexture + { + D3DBaseTexture *basemap; + D3DTexture *map; + D3DVolumeTexture *volmap; + D3DCubeTexture *cubemap; + GfxImageLoadDef *loadDef; + }; + + struct GfxImageLoadDef + { + unsigned __int8 levelCount; + unsigned __int8 flags; + __int16 dimensions[3]; + int format; + GfxTexture texture; + }; + + struct GfxImage + { + MapType mapType; + GfxTexture texture; + unsigned __int8 semantic; + CardMemory cardMemory; + unsigned __int16 width; + unsigned __int16 height; + unsigned __int16 depth; + unsigned __int8 category; + unsigned __int8 *pixels; + unsigned int baseSize; + unsigned __int16 streamSlot; + bool streaming; + const char *name; + }; + struct RawFile { const char *name; @@ -117,7 +174,7 @@ namespace iw3_328 // MaterialPixelShader *pixelShader; // MaterialVertexShader *vertexShader; // MaterialTechniqueSet *techniqueSet; - // GfxImage *image; + GfxImage *image; // snd_alias_list_t *sound; // SndCurve *sndCurve; // clipMap_t *clipMap; @@ -250,6 +307,327 @@ namespace iw3_328 Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumped map ents to 'game:\\_iw3xe\\dump\\%s'\n", cm->mapEnts->numEntityChars, asset_name.c_str()); } + struct DDSHeader + { + uint32_t magic; + uint32_t size; + uint32_t flags; + uint32_t height; + uint32_t width; + uint32_t pitchOrLinearSize; + uint32_t depth; + uint32_t mipMapCount; + uint32_t reserved1[11]; + struct + { + uint32_t size; + uint32_t flags; + uint32_t fourCC; + uint32_t rgbBitCount; + uint32_t rBitMask; + uint32_t gBitMask; + uint32_t bBitMask; + uint32_t aBitMask; + } pixelFormat; + uint32_t caps; + uint32_t caps2; + uint32_t caps3; + uint32_t caps4; + uint32_t reserved2; + }; + + static_assert(sizeof(DDSHeader) == 128, ""); + + const uint32_t DDS_MAGIC = MAKEFOURCC('D', 'D', 'S', ' '); + const uint32_t DDS_HEADER_SIZE = 124; + const uint32_t DDS_PIXEL_FORMAT_SIZE = 32; + const uint32_t DDSD_CAPS = 0x1; + const uint32_t DDSD_HEIGHT = 0x2; + const uint32_t DDSD_WIDTH = 0x4; + const uint32_t DDSD_PIXELFORMAT = 0x1000; + const uint32_t DDSD_LINEARSIZE = 0x80000; + const uint32_t DDPF_FOURCC = 0x4; + const uint32_t DDPF_RGB = 0x40; + const uint32_t DDPF_ALPHAPIXELS = 0x1; + const uint32_t DDSCAPS_TEXTURE = 0x1000; + const uint32_t DDSCAPS_MIPMAP = 0x400000; + const uint32_t DDPF_LUMINANCE = 0x20000; + + // DDS Pixel Formats (FourCC Codes) + const uint32_t DXT1_FOURCC = MAKEFOURCC('D', 'X', 'T', '1'); + const uint32_t DXT3_FOURCC = MAKEFOURCC('D', 'X', 'T', '3'); + const uint32_t DXT5_FOURCC = MAKEFOURCC('D', 'X', 'T', '5'); + const uint32_t DXN_FOURCC = MAKEFOURCC('A', 'T', 'I', '2'); // (DXN / BC5) + + // Additional DDS Cubemap Flags + const uint32_t DDSCAPS2_CUBEMAP = 0x200; + const uint32_t DDSCAPS2_CUBEMAP_POSITIVEX = 0x400; + const uint32_t DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800; + const uint32_t DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000; + const uint32_t DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000; + const uint32_t DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000; + const uint32_t DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; + + void GPUEndianSwapTexture(std::vector &pixelData, GPUENDIAN endianType) + { + switch (endianType) + { + case GPUENDIAN_8IN16: + XGEndianSwapMemory(pixelData.data(), pixelData.data(), XGENDIAN_8IN16, 2, pixelData.size() / 2); + break; + case GPUENDIAN_8IN32: + XGEndianSwapMemory(pixelData.data(), pixelData.data(), XGENDIAN_8IN32, 4, pixelData.size() / 4); + break; + case GPUENDIAN_16IN32: + XGEndianSwapMemory(pixelData.data(), pixelData.data(), XGENDIAN_16IN32, 4, pixelData.size() / 4); + break; + } + } + + void Dump_Image(const GfxImage *image) + { + if (!image) + { + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Null GfxImage!\n"); + return; + } + + if (image->mapType != MAPTYPE_2D && image->mapType != MAPTYPE_CUBE) + { + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Unsupported map type %d!\n", image->mapType); + return; + } + + UINT BaseSize; + XGGetTextureLayout(image->texture.basemap, 0, &BaseSize, 0, 0, 0, 0, 0, 0, 0, 0); + + DDSHeader header; + memset(&header, 0, sizeof(DDSHeader)); + + header.magic = _byteswap_ulong(DDS_MAGIC); + header.size = _byteswap_ulong(DDS_HEADER_SIZE); + header.flags = _byteswap_ulong(DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE); + header.height = _byteswap_ulong(image->height); + header.width = _byteswap_ulong(image->width); + header.depth = _byteswap_ulong(image->depth); + header.mipMapCount = _byteswap_ulong(image->texture.basemap->GetLevelCount()); + + auto format = image->texture.basemap->Format.DataFormat; + switch (format) + { + case GPUTEXTUREFORMAT_DXT1: + header.pixelFormat.fourCC = _byteswap_ulong(DXT1_FOURCC); + header.pitchOrLinearSize = BaseSize; + break; + case GPUTEXTUREFORMAT_DXT2_3: + header.pixelFormat.fourCC = _byteswap_ulong(DXT3_FOURCC); + header.pitchOrLinearSize = BaseSize; + break; + case GPUTEXTUREFORMAT_DXT4_5: + header.pixelFormat.fourCC = _byteswap_ulong(DXT5_FOURCC); + header.pitchOrLinearSize = BaseSize; + break; + case GPUTEXTUREFORMAT_DXN: + header.pixelFormat.fourCC = _byteswap_ulong(DXN_FOURCC); + header.pitchOrLinearSize = BaseSize; + break; + case GPUTEXTUREFORMAT_8: + header.pixelFormat.flags = _byteswap_ulong(DDPF_LUMINANCE); + header.pixelFormat.rgbBitCount = _byteswap_ulong(8); + header.pixelFormat.rBitMask = _byteswap_ulong(0x000000FF); + header.pixelFormat.gBitMask = 0; + header.pixelFormat.bBitMask = 0; + header.pixelFormat.aBitMask = 0; + header.pitchOrLinearSize = BaseSize; + break; + case GPUTEXTUREFORMAT_8_8: + header.pixelFormat.flags = _byteswap_ulong(DDPF_LUMINANCE | DDPF_ALPHAPIXELS); + header.pixelFormat.rgbBitCount = _byteswap_ulong(16); + header.pixelFormat.rBitMask = _byteswap_ulong(0x000000FF); + header.pixelFormat.gBitMask = _byteswap_ulong(0x0000FF00); + header.pixelFormat.bBitMask = 0; + header.pixelFormat.aBitMask = 0; + header.pitchOrLinearSize = BaseSize; + break; + case GPUTEXTUREFORMAT_8_8_8_8: + header.pixelFormat.flags = _byteswap_ulong(DDPF_RGB | DDPF_ALPHAPIXELS); + header.pixelFormat.rgbBitCount = _byteswap_ulong(32); + header.pixelFormat.rBitMask = _byteswap_ulong(0x00FF0000); + header.pixelFormat.gBitMask = _byteswap_ulong(0x0000FF00); + header.pixelFormat.bBitMask = _byteswap_ulong(0x000000FF); + header.pixelFormat.aBitMask = _byteswap_ulong(0xFF000000); + header.pitchOrLinearSize = BaseSize; + break; + default: + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Unsupported texture format %d!\n", format); + return; + } + + // Set texture capabilities + header.caps = _byteswap_ulong(DDSCAPS_TEXTURE | DDSCAPS_MIPMAP); + + // Handle Cubemaps + if (image->mapType == MAPTYPE_CUBE) + { + header.caps2 = _byteswap_ulong(DDSCAPS2_CUBEMAP | + DDSCAPS2_CUBEMAP_POSITIVEX | DDSCAPS2_CUBEMAP_NEGATIVEX | + DDSCAPS2_CUBEMAP_POSITIVEY | DDSCAPS2_CUBEMAP_NEGATIVEY | + DDSCAPS2_CUBEMAP_POSITIVEZ | DDSCAPS2_CUBEMAP_NEGATIVEZ); + } + + std::string filename = "game:\\_iw3xe\\dump\\images\\"; + std::string sanitized_name = image->name; + + // Remove invalid characters + sanitized_name.erase(std::remove_if(sanitized_name.begin(), sanitized_name.end(), [](char c) + { return c == '*'; }), + sanitized_name.end()); + + filename += sanitized_name + ".dds"; + + std::ofstream file(filename, std::ios::binary); + if (!file) + { + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Failed to open file: %s\n", filename.c_str()); + return; + } + + if (image->mapType == MAPTYPE_CUBE) + { + file.write(reinterpret_cast(&header), sizeof(DDSHeader)); + + unsigned int face_size = 0; + unsigned int rowPitch = 0; + const GPUTEXTUREFORMAT format = static_cast(image->texture.basemap->Format.DataFormat); + + switch (format) + { + case GPUTEXTUREFORMAT_DXT1: + face_size = (image->width / 4) * (image->height / 4) * 8; + rowPitch = (image->width / 4) * 8; // 8 bytes per 4x4 block + break; + case GPUTEXTUREFORMAT_8_8_8_8: + face_size = image->width * image->height * 4; + rowPitch = image->width * 4; // 4 bytes per pixel + break; + default: + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Unsupported cube map format %d!\n", format); + return; + } + + // TODO: handle mip levels per face for cubemaps + for (int i = 0; i < 6; i++) + { + unsigned char *face_pixels = image->pixels + (i * face_size); // Offset for each face + + std::vector swappedFace(face_pixels, face_pixels + face_size); + GPUEndianSwapTexture(swappedFace, static_cast(image->texture.basemap->Format.Endian)); + + // Create buffer for linear texture data + std::vector linearFace(face_size); + + // Convert tiled texture to linear layout using XGUntileTextureLevel + XGUntileTextureLevel( + image->width, // Width + image->height, // Height + 0, // Level (base level) + static_cast(format), // GpuFormat + 0, // Flags (no special flags) + linearFace.data(), // pDestination (linear output) + rowPitch, // RowPitch + nullptr, // pPoint (no offset) + swappedFace.data(), // pSource (tiled input) + nullptr // pRect (entire texture) + ); + + file.write(reinterpret_cast(linearFace.data()), linearFace.size()); + } + + file.close(); + } + else if (image->mapType == MAPTYPE_2D) + { + // TODO: write mip levels + file.write(reinterpret_cast(&header), sizeof(DDSHeader)); + + std::vector pixelData(image->pixels, image->pixels + image->baseSize); + + GPUEndianSwapTexture(pixelData, static_cast(image->texture.basemap->Format.Endian)); + + // Create a linear data buffer to hold the untiled texture + std::vector linearData(image->baseSize); + + // Calculate row pitch based on format + UINT rowPitch; + auto format = image->texture.basemap->Format.DataFormat; + + switch (format) + { + case GPUTEXTUREFORMAT_DXT1: + case GPUTEXTUREFORMAT_DXT2_3: + case GPUTEXTUREFORMAT_DXT4_5: + case GPUTEXTUREFORMAT_DXN: + // Block compressed formats use 4x4 blocks + rowPitch = ((image->width + 3) / 4) * (format == GPUTEXTUREFORMAT_DXT1 ? 8 : 16); + break; + case GPUTEXTUREFORMAT_8: + rowPitch = image->width; + break; + case GPUTEXTUREFORMAT_8_8: + rowPitch = image->width * 2; + break; + case GPUTEXTUREFORMAT_8_8_8_8: + rowPitch = image->width * 4; + break; + default: + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Unsupported texture format %d!\n", format); + return; + } + + xbox::DbgPrint("Image_Dump: rowPitch=%d\n", rowPitch); + + // Call XGUntileTextureLevel to convert the tiled texture to linear format + XGUntileTextureLevel( + image->width, // Width + image->height, // Height + 0, // Level (base level 0) + static_cast(format), // GpuFormat + XGTILE_NONPACKED, // Flags (no special flags) + linearData.data(), // pDestination + rowPitch, // RowPitch (calculated based on format) + nullptr, // pPoint (no offset) + pixelData.data(), // pSource + nullptr // pRect (entire texture) + ); + + file.write(reinterpret_cast(linearData.data()), linearData.size()); + + file.close(); + } + else + { + Com_PrintError(CON_CHANNEL_ERROR, "Image_Dump: Unsupported map type %d!\n", image->mapType); + return; + } + } + + void Cmd_dump_images_f() + { + CreateDirectoryA("game:\\_iw3xe", nullptr); + CreateDirectoryA("game:\\_iw3xe\\dump", nullptr); + CreateDirectoryA("game:\\_iw3xe\\dump\\images", nullptr); + + const int MAX = 2048; + XAssetHeader assets[MAX]; + int count = DB_GetAllXAssetOfType(ASSET_TYPE_IMAGE, assets, MAX); + + for (int i = 0; i < count; i++) + { + Dump_Image(assets[i].image); + } + Com_Printf(CON_CHANNEL_CONSOLEONLY, "Dumped %d rimages to 'game:\\_iw3xe\\dump\\images'\n", count); + } + // Detour CG_InitConsoleCommands_Detour; // void CG_InitConsoleCommands_Hook() @@ -268,6 +646,9 @@ namespace iw3_328 // CG_InitConsoleCommands_Detour = Detour(CG_InitConsoleCommands, CG_InitConsoleCommands_Hook); // CG_InitConsoleCommands_Detour.Install(); + cmd_function_s *dump_images_VAR = new cmd_function_s; + Cmd_AddCommandInternal("dump_images", Cmd_dump_images_f, dump_images_VAR); + cmd_function_s *dump_mapents_VAR = new cmd_function_s; Cmd_AddCommandInternal("dump_mapents", Cmd_dump_mapents_f, dump_mapents_VAR); From 0ab7f7aad16326bfe42235b3e37792ece2dfeeeb Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 10 Apr 2025 15:48:22 +0100 Subject: [PATCH 3/4] fix patch in gsc loader 328 --- src/game/iw3-328/scr_parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/iw3-328/scr_parser.cpp b/src/game/iw3-328/scr_parser.cpp index 3b31d31..5d631db 100644 --- a/src/game/iw3-328/scr_parser.cpp +++ b/src/game/iw3-328/scr_parser.cpp @@ -16,7 +16,7 @@ namespace iw3_328 { xbox::DbgPrint("Scr_ReadFile_FastFile_Hook extFilename=%s \n", extFilename); - std::string raw_file_path = "game:\\iw3xe\\raw\\"; + std::string raw_file_path = "game:\\_iw3xe\\raw\\"; raw_file_path += extFilename; std::replace(raw_file_path.begin(), raw_file_path.end(), '/', '\\'); // Replace forward slashes with backslashes if (filesystem::file_exists(raw_file_path)) From 30d0f4579de215df069dd5665ee27d0ab01c864f Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Thu, 10 Apr 2025 15:50:11 +0100 Subject: [PATCH 4/4] add pc fps mode 328 --- src/game/iw3-328/main.cpp | 230 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/src/game/iw3-328/main.cpp b/src/game/iw3-328/main.cpp index 6ae7e27..2a8923f 100644 --- a/src/game/iw3-328/main.cpp +++ b/src/game/iw3-328/main.cpp @@ -4,8 +4,238 @@ namespace iw3_328 { + struct DvarValueStringBuf + { + const char *pad; + char string[12]; + }; + + union DvarValue + { + bool enabled; + int integer; + unsigned int unsignedInt; + float value; + float vector[4]; + const char *string; + DvarValueStringBuf stringBuf; + unsigned __int8 color[4]; + }; + + union DvarLimits + { + struct + { + int stringCount; + const char **strings; + } enumeration; + + struct + { + int min; + int max; + } integer; + + struct + { + float min; + float max; + } value; + + struct + { + float min; + float max; + } vector; + }; + + struct dvar_s + { + const char *name; + const char *description; + unsigned __int16 flags; + unsigned __int8 type; + bool modified; + DvarValue current; + DvarValue latched; + DvarValue reset; + DvarLimits domain; + dvar_s *next; + dvar_s *hashNext; + }; + + typedef dvar_s *(*Dvar_FindMalleableVar_t)(const char *dvarName); + // typedef bool (*Dvar_GetBool_t)(const char *dvarName); + typedef dvar_s *(*Dvar_RegisterBool_t)(const char *dvarName, bool value, unsigned __int16 flags, const char *description); + // typedef dvar_s *(*Dvar_RegisterColor_t)(const char *dvarName, double r, double g, double b, double a, unsigned __int16 flags, const char *description); + // typedef dvar_s *(*Dvar_RegisterEnum_t)(const char *dvarName, const char **valueList, unsigned __int16 defaultIndex, unsigned __int16 flags, const char *description); + typedef dvar_s *(*Dvar_RegisterInt_t)(const char *dvarName, int value, int min, int max, unsigned __int16 flags, const char *description); + + Dvar_FindMalleableVar_t Dvar_FindMalleableVar = reinterpret_cast(0x82269CF8); + // Dvar_GetBool_t Dvar_GetBool = reinterpret_cast(0x82269EA8); + Dvar_RegisterBool_t Dvar_RegisterBool = reinterpret_cast(0x8226AF38); + // Dvar_RegisterColor_t Dvar_RegisterColor = reinterpret_cast(0x821D4D98); + // Dvar_RegisterEnum_t Dvar_RegisterEnum = reinterpret_cast(0x821D4F88); + Dvar_RegisterInt_t Dvar_RegisterInt = reinterpret_cast(0x821D37C8); + + typedef void (*Sys_SnapVector_t)(float *result); + Sys_SnapVector_t Sys_SnapVector = reinterpret_cast(0x822A7E18); + + typedef void (*CG_DrawActive_t)(int localClientNum); + CG_DrawActive_t CG_DrawActive = reinterpret_cast(0x82316020); + + dvar_s *pc_mp_fps_mode = nullptr; + + Detour Sys_SnapVector_Detour; + + void Sys_SnapVector_Hook(float *v) + { + // static dvar_s *pm_fps_pc_mode = Dvar_FindMalleableVar("pm_fps_mode"); + if (pc_mp_fps_mode->current.enabled) + { + // Use __frnd for round-to-nearest-even behavior + v[0] = (float)__frnd((double)v[0]); + v[1] = (float)__frnd((double)v[1]); + v[2] = (float)__frnd((double)v[2]); + } + else + { + Sys_SnapVector_Detour.GetOriginal()(v); + } + } + + struct __declspec(align(4)) usercmd_s + { + int serverTime; + int buttons; + unsigned __int8 weapon; + unsigned __int8 offHandIndex; + int angles[3]; + char forwardmove; + char rightmove; + float meleeChargeYaw; + unsigned __int8 meleeChargeDist; + }; + + struct playerState_s + { + int commandTime; + }; + + struct __declspec(align(4)) pmove_t + { + playerState_s *ps; + usercmd_s cmd; + usercmd_s oldcmd; + int tracemask; + int numtouch; + int touchents[32]; + float mins[3]; + float maxs[3]; + float xyspeed; + int proneChange; + float maxSprintTimeMultiplier; + bool mantleStarted; + float mantleEndPos[3]; + int mantleDuration; + int viewChangeTime; + float viewChange; + unsigned __int8 handler; + }; + + typedef void (*PmoveSingle_t)(pmove_t *pm); + PmoveSingle_t PmoveSingle = reinterpret_cast(0x8210D6B8); + + typedef void (*Pmove_t)(pmove_t *pm); + Pmove_t Pmove = reinterpret_cast(0x8210E0E0); + + Detour Pmove_Detour; + + void Pmove_Hook(pmove_t *pm) + { + // usercmd_s *p_cmd; // r31 + // playerState_s *ps; // r27 + // int serverTime; // r29 + // int commandTime; // r11 + // int v6; // r11 + // usercmd_s *v7; // r11 + // usercmd_s *p_oldcmd; // r10 + // int v9; // ctr + // int v10; // r9 + + // p_cmd = &pm->cmd; + // ps = pm->ps; + // serverTime = pm->cmd.serverTime; + // commandTime = pm->ps->commandTime; + // if (serverTime >= commandTime) + // { + // if (serverTime > commandTime + 1000) + // ps->commandTime = serverTime - 1000; + // pm->numtouch = 0; + // while (ps->commandTime != serverTime) + // { + // v6 = serverTime - ps->commandTime; + // if (v6 > 66) + // v6 = 66; + // p_cmd->serverTime = ps->commandTime + v6; + // PmoveSingle(pm); + // v7 = p_cmd; + // p_oldcmd = &pm->oldcmd; + // v9 = 9; + // do + // { + // v10 = v7->serverTime; + // v7 = (usercmd_s *)((char *)v7 + 4); + // p_oldcmd->serverTime = v10; + // p_oldcmd = (usercmd_s *)((char *)p_oldcmd + 4); + // --v9; + // } while (v9); + // } + // } + + static dvar_s *com_maxfps = Dvar_FindMalleableVar("com_maxfps"); + + int msec = 0; + int cur_msec = 1000 / com_maxfps->current.integer; + + // if (pm_fixed->current.enabled == false) + // cur_msec = 66; + // else + pm->cmd.serverTime = ((pm->cmd.serverTime + (cur_msec < 2 ? 2 : cur_msec) - 1) / cur_msec) * cur_msec; + + int finalTime = pm->cmd.serverTime; + + if (finalTime < pm->ps->commandTime) + { + return; // should not happen + } + + if (finalTime > pm->ps->commandTime + 1000) + pm->ps->commandTime = finalTime - 1000; + pm->numtouch = 0; + + while (pm->ps->commandTime != finalTime) + { + msec = finalTime - pm->ps->commandTime; + + if (msec > cur_msec) + msec = cur_msec; + + pm->cmd.serverTime = msec + pm->ps->commandTime; + PmoveSingle(pm); + memcpy(&pm->oldcmd, &pm->cmd, sizeof(pm->oldcmd)); + } + } + void init() { + Sys_SnapVector_Detour = Detour(Sys_SnapVector, Sys_SnapVector_Hook); + Sys_SnapVector_Detour.Install(); + + Pmove_Detour = Detour(Pmove, Pmove_Hook); + Pmove_Detour.Install(); + + pc_mp_fps_mode = Dvar_RegisterBool("pc_mp_fps_mode", true, 0, ""); init_cg_consolecmds(); init_scr_parser(); }