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..3bacd24
--- /dev/null
+++ b/src/game/iw3-328/cg_consolecmds.cpp
@@ -0,0 +1,658 @@
+#pragma warning(push)
+#pragma warning(disable : 4480)
+
+#include
+#include
+#include
+
+#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);
+
+ 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;
+ 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());
+ }
+
+ 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()
+ // {
+ // 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_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);
+
+ 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..2a8923f
--- /dev/null
+++ b/src/game/iw3-328/main.cpp
@@ -0,0 +1,242 @@
+#include "structs.h"
+#include "cg_consolecmds.h"
+#include "scr_parser.h"
+
+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();
+ }
+}
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..5d631db
--- /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