diff --git a/iw3xe.vcxproj b/iw3xe.vcxproj
index 1f74455..7f8fd45 100644
--- a/iw3xe.vcxproj
+++ b/iw3xe.vcxproj
@@ -75,6 +75,9 @@
+
+
+
@@ -85,5 +88,9 @@
+
+
+
+
diff --git a/mods/253-codjumper/maps/mp/gametypes/cj.gsc b/mods/253-codjumper/maps/mp/gametypes/cj.gsc
new file mode 100644
index 0000000..bce0cc5
--- /dev/null
+++ b/mods/253-codjumper/maps/mp/gametypes/cj.gsc
@@ -0,0 +1,219 @@
+// #include common_scripts\utility;
+// #include maps\mp\gametypes\_hud_util;
+
+init()
+{
+ setDvar("scr_game_forceuav", 0); // Disable compass
+
+ setDvar("player_sprintUnlimited", 1); // Unlimited sprint
+ setDvar("jump_slowdownEnable", 0); // Disable jump slowdown
+
+ setDvar("bg_fallDamageMaxHeight", 9999); // Disable fall damage
+ setDvar("bg_fallDamageMinHeight", 9998); // Disable fall damage
+
+ setDvar("scr_war_timelimit", 0); // Disable time limit
+
+ setDvar("scr_hardpoint_allowuav", 0); // Disable UAV hardpoint and waypoint
+
+ setDvar("sv_cheats", 1); // Enable cheats
+
+ level thread onPlayerConnect();
+}
+
+onPlayerConnect()
+{
+ for (;;)
+ {
+ level waittill("connecting", player);
+
+ player thread onPlayerSpawned();
+ }
+}
+
+onPlayerSpawned()
+{
+ self endon("disconnect");
+ for (;;)
+ {
+ self waittill("spawned_player");
+
+ self thread cj_setup_loadout();
+ self thread replenish_ammo();
+ self thread watch_buttons();
+ self thread set_client_dvars();
+
+ self Cbuf_ExecuteBuffer("bind BUTTON_LSHLDR toggle pm_fixed_fps 333 250 125");
+ self Cbuf_ExecuteBuffer("bind BUTTON_RSHLDR toggle pm_fixed_fps 125 250 333");
+ self Cbuf_ExecuteBuffer("bind DPAD_DOWN +frag");
+ self Cbuf_ExecuteBuffer("bind DPAD_RIGHT +smoke");
+
+ self Cbuf_ExecuteBuffer("bind DPAD_RIGHT +smoke");
+ }
+}
+
+cj_setup_loadout()
+{
+ self TakeAllWeapons();
+
+ wait 0.05;
+
+ // self GiveWeapon("deserteagle_mp");
+ self Cbuf_ExecuteBuffer("give deserteagle_mp");
+ self GiveWeapon("mp5_mp");
+ self GiveWeapon("rpg_mp");
+ self SetActionSlot(3, "weapon", "rpg_mp");
+
+ wait 0.05;
+ self SwitchToWeapon("deserteagle_mp");
+}
+
+set_client_dvars()
+{
+ self endon("disconnect");
+
+ self setClientDvar("fx_enable", 0); // Disable effects
+ self setClientDvar("ui_hud_hardcore", 1); // Hardcore HUD
+ self setClientDvar("cg_drawCrosshair", 0); // Disable crosshair
+
+ // FPS
+ // self setClientDvar("r_vsync", 0);
+ // self setClientDvar("cg_drawFPS", 1);
+ // self setClientDvar("com_maxfps", 333);
+
+ // Fixed FPS
+ self setClientDvar("pm_fixed_fps_enable", 1);
+ self setClientDvar("pm_fixed_fps", 250);
+
+ self setClientDvar("cg_fov", 70);
+
+ self setClientDvar("cg_draw2D", 0); // Disable 2D HUD
+}
+
+/**
+ * Constantly replace the players ammo.
+ */
+replenish_ammo()
+{
+ self endon("end_respawn");
+ self endon("disconnect");
+
+ for (;;)
+ {
+ currentWeapon = self getCurrentWeapon(); // undefined if the player is mantling or on a ladder
+ if (isdefined(currentWeapon))
+ self giveMaxAmmo(currentWeapon);
+ wait 1;
+ }
+}
+
+watch_buttons()
+{
+ self endon("end_respawn");
+ self endon("disconnect");
+
+ for (;;)
+ {
+
+ if (self button_pressed("frag"))
+ {
+ self toggle_ufo();
+ wait 0.25;
+ }
+ else if (self button_pressed_twice("melee"))
+ {
+ self savePos(0);
+ wait 0.25;
+ }
+ else if (self button_pressed("smoke"))
+ {
+ self loadPos(0);
+ wait 0.25;
+ }
+
+ wait 0.05;
+ }
+}
+
+/**
+ * Check if a button is pressed.
+ */
+button_pressed(button)
+{
+ switch (ToLower(button))
+ {
+ case "frag":
+ return self fragbuttonpressed();
+ case "melee":
+ return self meleebuttonpressed();
+ case "smoke":
+ return self secondaryoffhandbuttonpressed();
+ default:
+ self iprintln("^1Unknown button " + button);
+ return false;
+ }
+}
+
+/**
+ * Check if a button is pressed twice within 500ms.
+ */
+button_pressed_twice(button)
+{
+ if (self button_pressed(button))
+ {
+ // Wait for the button to be released after the first press
+ while (self button_pressed(button))
+ {
+ wait 0.05;
+ }
+
+ // Now, wait for a second press within 500ms
+ for (elapsed_time = 0; elapsed_time < 0.5; elapsed_time += 0.05)
+ {
+ if (self button_pressed(button))
+ {
+ // Ensure it was released before this second press
+ return true;
+ }
+
+ wait 0.05;
+ }
+ }
+ return false;
+}
+
+
+savePos(i)
+{
+ if (!self isOnGround())
+ return;
+
+ self.cj["settings"]["rpg_switched"] = false;
+ self.cj["saves"]["org"][i] = self.origin;
+ self.cj["saves"]["ang"][i] = self getPlayerAngles();
+}
+
+loadPos(i)
+{
+ self freezecontrols(true);
+ wait 0.05;
+
+ self setPlayerAngles(self.cj["saves"]["ang"][i]);
+ self setOrigin(self.cj["saves"]["org"][i]);
+
+ wait 0.05;
+ self freezecontrols(false);
+}
+
+toggle_ufo()
+{
+ if (self.sessionstate == "playing")
+ {
+ self allowSpectateTeam("freelook", true);
+ self.sessionstate = "spectator";
+ }
+ else
+ {
+ self allowSpectateTeam("freelook", false);
+ self.sessionstate = "playing";
+ }
+}
diff --git a/mods/253-codjumper/maps/mp/gametypes/war.gsc b/mods/253-codjumper/maps/mp/gametypes/war.gsc
new file mode 100644
index 0000000..8157c42
--- /dev/null
+++ b/mods/253-codjumper/maps/mp/gametypes/war.gsc
@@ -0,0 +1,164 @@
+#include maps\mp\_utility;
+#include maps\mp\gametypes\_hud_util;
+/*
+ War
+ Objective: Score points for your team by eliminating players on the opposing team
+ Map ends: When one team reaches the score limit, or time limit is reached
+ Respawning: No wait / Near teammates
+
+ Level requirements
+ ------------------
+ Spawnpoints:
+ classname mp_tdm_spawn
+ All players spawn from these. The spawnpoint chosen is dependent on the current locations of teammates and enemies
+ at the time of spawn. Players generally spawn behind their teammates relative to the direction of enemies.
+
+ Spectator Spawnpoints:
+ classname mp_global_intermission
+ Spectators spawn from these and intermission is viewed from these positions.
+ Atleast one is required, any more and they are randomly chosen between.
+
+ Level script requirements
+ -------------------------
+ Team Definitions:
+ game["allies"] = "marines";
+ game["axis"] = "opfor";
+ This sets the nationalities of the teams. Allies can be american, british, or russian. Axis can be german.
+
+ If using minefields or exploders:
+ maps\mp\_load::main();
+
+ Optional level script settings
+ ------------------------------
+ Soldier Type and Variation:
+ game["american_soldiertype"] = "normandy";
+ game["german_soldiertype"] = "normandy";
+ This sets what character models are used for each nationality on a particular map.
+
+ Valid settings:
+ american_soldiertype normandy
+ british_soldiertype normandy, africa
+ russian_soldiertype coats, padded
+ german_soldiertype normandy, africa, winterlight, winterdark
+*/
+
+/*QUAKED mp_tdm_spawn (0.0 0.0 1.0) (-16 -16 0) (16 16 72)
+Players spawn away from enemies and near their team at one of these positions.*/
+
+/*QUAKED mp_tdm_spawn_axis_start (0.5 0.0 1.0) (-16 -16 0) (16 16 72)
+Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/
+
+/*QUAKED mp_tdm_spawn_allies_start (0.0 0.5 1.0) (-16 -16 0) (16 16 72)
+Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/
+
+main()
+{
+ if(getdvar("mapname") == "mp_background")
+ return;
+
+ maps\mp\gametypes\_globallogic::init();
+ maps\mp\gametypes\_callbacksetup::SetupCallbacks();
+ maps\mp\gametypes\_globallogic::SetupCallbacks();
+
+ maps\mp\gametypes\_globallogic::registerTimeLimitDvar( "war", 30, 0, 1440 );
+ maps\mp\gametypes\_globallogic::registerScoreLimitDvar( "war", 100, 0, 500 );
+ maps\mp\gametypes\_globallogic::registerRoundLimitDvar( "war", 1, 0, 10 );
+ maps\mp\gametypes\_globallogic::registerNumLivesDvar( "war", 0, 0, 10 );
+
+ level.teamBased = true;
+ level.onStartGameType = ::onStartGameType;
+ level.onSpawnPlayer = ::onSpawnPlayer;
+
+ maps\mp\gametypes\cj::init();
+}
+
+onStartGameType()
+{
+ //thread maps\mp\gametypes\_hardpoints::init();
+ //thread maps\mp\_helicopter::init();
+
+ setClientNameMode("auto_change");
+
+ maps\mp\gametypes\_globallogic::setObjectiveText( "allies", &"OBJECTIVES_WAR" );
+ maps\mp\gametypes\_globallogic::setObjectiveText( "axis", &"OBJECTIVES_WAR" );
+
+ if ( level.splitscreen )
+ {
+ maps\mp\gametypes\_globallogic::setObjectiveScoreText( "allies", &"OBJECTIVES_WAR" );
+ maps\mp\gametypes\_globallogic::setObjectiveScoreText( "axis", &"OBJECTIVES_WAR" );
+ }
+ else
+ {
+ maps\mp\gametypes\_globallogic::setObjectiveScoreText( "allies", &"OBJECTIVES_WAR_SCORE" );
+ maps\mp\gametypes\_globallogic::setObjectiveScoreText( "axis", &"OBJECTIVES_WAR_SCORE" );
+ }
+ maps\mp\gametypes\_globallogic::setObjectiveHintText( "allies", &"OBJECTIVES_WAR_HINT" );
+ maps\mp\gametypes\_globallogic::setObjectiveHintText( "axis", &"OBJECTIVES_WAR_HINT" );
+
+ level.spawnMins = ( 0, 0, 0 );
+ level.spawnMaxs = ( 0, 0, 0 );
+ maps\mp\gametypes\_spawnlogic::addSpawnPoints( "allies", "mp_tdm_spawn" );
+ maps\mp\gametypes\_spawnlogic::addSpawnPoints( "axis", "mp_tdm_spawn" );
+
+ level.mapCenter = maps\mp\gametypes\_spawnlogic::findBoxCenter( level.spawnMins, level.spawnMaxs );
+ setMapCenter( level.mapCenter );
+
+ level.spawnpoints = getentarray("mp_tdm_spawn", "classname");
+
+ allowed[0] = "dm";
+ allowed[1] = "war";
+ allowed[2] = "sd";
+ allowed[3] = "dom";
+ allowed[4] = "sab";
+ allowed[5] = "hq";
+ allowed[6] = "bombzone";
+ allowed[7] = "blocker";
+ allowed[8] = "hardpoint";
+ maps\mp\gametypes\_gameobjects::main(allowed);
+
+ thread updateGametypeDvars();
+
+}
+
+onSpawnPlayer()
+{
+ self.usingObj = undefined;
+
+ if ( level.xboxlive )
+ {
+ spawns = self getstat( 2000 ) + 1;
+ self setstat( 2000, spawns );
+ }
+
+ if ( level.inGracePeriod )
+ {
+ spawnPoints = getentarray("mp_tdm_spawn_" + self.pers["team"] + "_start", "classname");
+
+ if ( !spawnPoints.size )
+ spawnPoints = getentarray("mp_sab_spawn_" + self.pers["team"] + "_start", "classname");
+
+ if ( !spawnPoints.size )
+ {
+ spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( self.pers["team"] );
+ spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_NearTeam( spawnPoints );
+ }
+ else
+ {
+ spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_Random( spawnPoints );
+ }
+ }
+ else
+ {
+ spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( self.pers["team"] );
+ spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_NearTeam( spawnPoints );
+ }
+
+ self spawn( spawnPoint.origin, spawnPoint.angles );
+}
+
+
+updateGametypeDvars()
+{
+}
+
+
diff --git a/resources/xenia/plugins/415607E6/plugins.toml b/resources/xenia/plugins/415607E6/plugins.toml
index 1f2ccfe..f783817 100644
--- a/resources/xenia/plugins/415607E6/plugins.toml
+++ b/resources/xenia/plugins/415607E6/plugins.toml
@@ -17,3 +17,11 @@ name = "iw3xe"
file = "iw3xe.xex"
hash = "F5F903E4F326EB10" # default_mp.xex
is_enabled = true
+
+# Pre Alpha 253 MP
+[[plugin]]
+author = "mo"
+name = "iw3xe"
+file = "iw3xe.xex"
+hash = "60FD640B7A28C69D" # default_mp.xex
+is_enabled = true
diff --git a/src/game/game.cpp b/src/game/game.cpp
index 25e53ac..96bdb33 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_253/main.h"
namespace game
{
@@ -21,6 +22,11 @@ namespace game
xbox::show_notification(L"IW3xe sp");
sp::init();
}
+ else if (strncmp((char *)0x8203A0A0, "multiplayer", 11) == 0)
+ {
+ xbox::show_notification(L"IW3xe 253 MP");
+ iw3_253::init();
+ }
else
{
xbox::show_notification(L"IW3xe unsupported executable");
diff --git a/src/game/iw3_253/cg_consolecmds.cpp b/src/game/iw3_253/cg_consolecmds.cpp
new file mode 100644
index 0000000..c9db25f
--- /dev/null
+++ b/src/game/iw3_253/cg_consolecmds.cpp
@@ -0,0 +1,639 @@
+#pragma warning(push)
+#pragma warning(disable : 4480)
+
+#include
+#include
+#include
+
+#include
+
+#include "..\..\detour.h"
+#include "..\..\filesystem.h"
+#include "..\..\xboxkrnl.h"
+
+namespace iw3_253
+{
+ 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(0x821843F0);
+
+ 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(0x82184A78);
+
+ typedef void (*Com_PrintError_t)(conChannel_t channel, const char *fmt, ...);
+ Com_PrintError_t Com_PrintError = reinterpret_cast(0x82184BE8);
+
+ 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_FastFile_t)(XAssetType type, void *assets, int maxCount);
+ DB_GetAllXAssetOfType_FastFile_t DB_GetAllXAssetOfType_FastFile = reinterpret_cast(0x82124C30);
+
+ void Cmd_dump_rawfiles_f()
+ {
+ const int MAX_RAWFILES = 2048;
+ XAssetHeader assets[MAX_RAWFILES];
+ int count = DB_GetAllXAssetOfType_FastFile(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[4];
+ // DynEntityDef *dynEntDefList[2];
+ // DynEntityClient *dynEntClientList[2];
+ // DynEntityServer *dynEntServerList[2];
+ // DynEntityColl *dynEntCollList[4];
+ // unsigned int checksum;
+ };
+
+ auto cm = reinterpret_cast(0x836CDB08);
+
+ 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_FastFile(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);
+ }
+
+ void init_cg_consolecmds()
+ {
+ 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_253/cg_consolecmds.h b/src/game/iw3_253/cg_consolecmds.h
new file mode 100644
index 0000000..5954e24
--- /dev/null
+++ b/src/game/iw3_253/cg_consolecmds.h
@@ -0,0 +1,10 @@
+#include
+
+#include "..\..\detour.h"
+#include "..\..\filesystem.h"
+#include "..\..\xboxkrnl.h"
+
+namespace iw3_253
+{
+ void init_cg_consolecmds();
+}
diff --git a/src/game/iw3_253/main.cpp b/src/game/iw3_253/main.cpp
new file mode 100644
index 0000000..2d81ab7
--- /dev/null
+++ b/src/game/iw3_253/main.cpp
@@ -0,0 +1,247 @@
+#include
+
+#include "cg_consolecmds.h"
+#include "scr_parser.h"
+#include "structs.h"
+#include "..\..\detour.h"
+
+extern "C" void DbgPrint(const char *format, ...);
+
+namespace iw3_253
+{
+ typedef dvar_s *(*Dvar_FindMalleableVar_t)(const char *dvarName);
+ Dvar_FindMalleableVar_t Dvar_FindMalleableVar = reinterpret_cast(0x821E0780);
+
+ typedef dvar_s *(*Dvar_RegisterBool_t)(const char *dvarName, bool value, unsigned __int16 flags, const char *description);
+ Dvar_RegisterBool_t Dvar_RegisterBool = reinterpret_cast(0x821E1718);
+
+ typedef dvar_s *(*Dvar_RegisterInt_t)(const char *dvarName, int value, int min, int max, unsigned __int16 flags, const char *description);
+ Dvar_RegisterInt_t Dvar_RegisterInt = reinterpret_cast(0x821E1788);
+
+ typedef void (*PM_FoliageSounds_t)(pmove_t *pm);
+ typedef void (*Pmove_t)(pmove_t *pm);
+ typedef void (*PmoveSingle_t)(pmove_t *pm);
+
+ PM_FoliageSounds_t PM_FoliageSounds = reinterpret_cast(0x820B27F8);
+ Pmove_t Pmove = reinterpret_cast(0x820B4F48);
+ PmoveSingle_t PmoveSingle = reinterpret_cast(0x820B4440);
+
+ typedef void (*CG_DrawActive_t)(int localClientNum);
+ CG_DrawActive_t CG_DrawActive = reinterpret_cast(0x820CA770);
+
+ typedef int (*R_RegisterFont_t)(const char *name);
+ R_RegisterFont_t R_RegisterFont = reinterpret_cast(0x8216EC00);
+
+ typedef void (*R_AddCmdDrawText_t)(const char *text, int maxChars, Font_s *font, double x, double y, double xScale, double yScale, double rotation, const float *color, int style);
+ R_AddCmdDrawText_t R_AddCmdDrawText = (R_AddCmdDrawText_t)0x8228F348;
+
+ typedef bool (*Cmd_ExecFromFastFile_t)(int localClientNum, int controllerIndex, const char *filename);
+ Cmd_ExecFromFastFile_t Cmd_ExecFromFastFile = reinterpret_cast(0x8223AF40);
+
+ typedef void (*Cbuf_AddText_t)(int localClientNum, const char *text);
+ Cbuf_AddText_t Cbuf_AddText = reinterpret_cast(0x82183458);
+
+ typedef void (*Cbuf_ExecuteBuffer_t)(int localClientNum, const char *buffer);
+ Cbuf_ExecuteBuffer_t Cbuf_ExecuteBuffer = reinterpret_cast(0x821838D0);
+
+ struct scr_entref_t
+ {
+ unsigned __int16 entnum;
+ unsigned __int16 classnum;
+ };
+
+ typedef void (*BuiltinMethod)(scr_entref_t);
+ typedef BuiltinMethod (*Scr_GetMethod_t)(const char **pName, int *type);
+ Scr_GetMethod_t Scr_GetMethod = reinterpret_cast(0x82167998);
+
+ typedef char *(*Scr_GetString_t)(unsigned int index);
+ Scr_GetString_t Scr_GetString = reinterpret_cast(0x821AC760);
+
+ typedef void (*PM_UpdateSprint_t)(pmove_t *pm, const pml_t *pml);
+ PM_UpdateSprint_t PM_UpdateSprint = reinterpret_cast(0x820ADD50);
+
+ ScreenPlacement &scrPlaceFullUnsafe = *reinterpret_cast(0x831ACDB8);
+
+ cgMedia_t &cgMedia = *reinterpret_cast(0x82848250);
+
+ Detour PM_UpdateSprint_Detour;
+
+ void PM_UpdateSprint_Hook(pmove_t *pm, const pml_t *pml)
+ {
+ DbgPrint("PM_UpdateSprint_Hook\n");
+ PM_UpdateSprint_Detour.GetOriginal()(pm, pml);
+
+ // Works but prevents bouncing and mantling
+ // pm->ps->pm_flags &= ~0x1401Cu;
+
+ // pm->ps->sprintState.lastSprintEnd = 0;
+ }
+
+ Detour Pmove_Detour;
+
+ // https://github.com/kejjjjj/iw3sptool/blob/17b669233a1ad086deed867469dc9530b84c20e6/iw3sptool/bg/bg_pmove.cpp#L11
+ void Pmove_Hook(pmove_t *pm)
+ {
+ static dvar_s *pm_fixed_fps_enable = Dvar_FindMalleableVar("pm_fixed_fps_enable");
+ static dvar_s *pm_fixed_fps = Dvar_FindMalleableVar("pm_fixed_fps");
+
+ if (!pm_fixed_fps_enable->current.enabled)
+ return Pmove_Detour.GetOriginal()(pm);
+
+ int msec = 0;
+ int cur_msec = 1000 / pm_fixed_fps->current.integer;
+
+ 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));
+ }
+ }
+
+ bool g_pm_foliage_ran_this_frame = false;
+
+ void Sys_SnapVector_RoundToNearestEven(float *v)
+ {
+ // 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]);
+ }
+
+ Detour PmoveSingle_Detour;
+
+ void PmoveSingle_Hook(pmove_t *pm)
+ {
+ DbgPrint("PmoveSingle_Hook\n");
+ g_pm_foliage_ran_this_frame = false;
+ PmoveSingle_Detour.GetOriginal()(pm);
+ if (g_pm_foliage_ran_this_frame)
+ {
+ DbgPrint("Sys_SnapVector_RoundToNearestEven\n");
+ Sys_SnapVector_RoundToNearestEven(pm->ps->velocity);
+ }
+
+ g_pm_foliage_ran_this_frame = false;
+ }
+
+ Detour PM_FoliageSounds_Detour;
+
+ void PM_FoliageSounds_Hook(pmove_t *pm)
+ {
+ DbgPrint("PM_FoliageSounds_Hook\n");
+ PM_FoliageSounds_Detour.GetOriginal()(pm);
+ g_pm_foliage_ran_this_frame = true;
+ }
+
+ void DrawFixedFPS()
+ {
+ static dvar_s *pm_fixed_fps = Dvar_FindMalleableVar("pm_fixed_fps");
+
+ char buff[16];
+ sprintf_s(buff, "%d", pm_fixed_fps->current.integer);
+
+ float color[4] = {1, 1, 1, 1};
+ float x = 620 * scrPlaceFullUnsafe.scaleVirtualToFull[0];
+ float y = 15 * scrPlaceFullUnsafe.scaleVirtualToFull[1];
+ R_AddCmdDrawText(buff, 16, cgMedia.bigDevFont, x, y, 1.0, 1.0, 0.0, color, 0);
+ }
+
+ Detour CG_DrawActive_Detour;
+
+ void CG_DrawActive_Hook(int localClientNum)
+ {
+ static dvar_s *pm_fixed_fps_enable = Dvar_FindMalleableVar("pm_fixed_fps_enable");
+
+ if (pm_fixed_fps_enable->current.enabled)
+ {
+ DrawFixedFPS();
+ }
+
+ CG_DrawActive_Detour.GetOriginal()(localClientNum);
+ }
+
+ void GScr_Cbuf_ExecuteBuffer(scr_entref_t entref)
+ {
+ auto text = Scr_GetString(0);
+ Cbuf_ExecuteBuffer(0, text);
+ }
+
+ Detour Scr_GetMethod_Detour;
+
+ BuiltinMethod Scr_GetMethod_Hook(const char **pName, int *type)
+ {
+ if (std::strcmp(*pName, "cbuf_executebuffer") == 0)
+ return reinterpret_cast(&GScr_Cbuf_ExecuteBuffer);
+
+ return Scr_GetMethod_Detour.GetOriginal()(pName, type);
+ }
+
+ void init()
+ {
+ Pmove_Detour = Detour(Pmove, Pmove_Hook);
+ Pmove_Detour.Install();
+
+ Dvar_RegisterBool("pm_fixed_fps_enable", false, 0, "Enable fixed FPS mode");
+ Dvar_RegisterInt("pm_fixed_fps", 250, 0, 1000, 0, "Fixed FPS value");
+
+ init_cg_consolecmds();
+ init_scr_parser();
+
+ PmoveSingle_Detour = Detour(PmoveSingle, PmoveSingle_Hook);
+ PmoveSingle_Detour.Install();
+
+ PM_FoliageSounds_Detour = Detour(PM_FoliageSounds, PM_FoliageSounds_Hook);
+ PM_FoliageSounds_Detour.Install();
+
+ // Nop the function that calls Sys_SnapVector
+ uintptr_t start = 0x820B4EB4;
+ uintptr_t end = 0x820B4F34;
+
+ for (uintptr_t addr = start; addr <= end; addr += 4)
+ {
+ *(uint32_t *)addr = 0x60000000; // PowerPC NOP
+ }
+
+ CG_DrawActive_Detour = Detour(CG_DrawActive, CG_DrawActive_Hook);
+ CG_DrawActive_Detour.Install();
+
+ Scr_GetMethod_Detour = Detour(Scr_GetMethod, Scr_GetMethod_Hook);
+ Scr_GetMethod_Detour.Install();
+
+ PM_UpdateSprint_Detour = Detour(PM_UpdateSprint, PM_UpdateSprint_Hook);
+ PM_UpdateSprint_Detour.Install();
+
+ // Remove read-only protection from DVARs
+
+ // .text:821E04F4 bl _Com_Printf__YAXHPBDZZ # Com_Printf(int,char const *,...)
+ // .text:821E04F8 lwz r3, 0x4C0+var_40(r1) # cookie
+ // .text:821E04FC bl __security_check_cookie
+ // .text:821E0500 addi r1, r1, 0x4C0
+ // .text:821E0504 b __restgprlr_27
+
+ // DVARS
+ // Patch read-only check to always succeed
+ *(unsigned int *)0x821E04E0 = 0x48000028; // b loc_821E0508
+ // Patch write protection check to always succeed
+ *(unsigned int *)0x821E0510 = 0x48000028; // b loc_821E0538
+ }
+}
diff --git a/src/game/iw3_253/main.h b/src/game/iw3_253/main.h
new file mode 100644
index 0000000..ed7ee46
--- /dev/null
+++ b/src/game/iw3_253/main.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "structs.h"
+
+namespace iw3_253
+{
+ void init();
+}
diff --git a/src/game/iw3_253/scr_parser.cpp b/src/game/iw3_253/scr_parser.cpp
new file mode 100644
index 0000000..5a192ca
--- /dev/null
+++ b/src/game/iw3_253/scr_parser.cpp
@@ -0,0 +1,56 @@
+#include
+
+#include "..\..\detour.h"
+#include "..\..\filesystem.h"
+#include "..\..\xboxkrnl.h"
+
+namespace iw3_253
+{
+ typedef char *(*Scr_AddSourceBuffer_t)(const char *filename, const char *extFilename, const char *codePos, bool archive);
+
+ Scr_AddSourceBuffer_t Scr_AddSourceBuffer = reinterpret_cast(0x8219DDA8);
+
+ Detour Scr_AddSourceBuffer_Detour;
+
+ char *Scr_AddSourceBuffer_Hook(const char *filename, const char *extFilename, const char *codePos, bool archive)
+ {
+ xbox::DbgPrint("Scr_AddSourceBuffer_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());
+ 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_AddSourceBuffer_Detour.GetOriginal()(filename, extFilename, codePos, archive);
+ }
+
+ void init_scr_parser()
+ {
+ xbox::DbgPrint("Initializing Scr_AddSourceBuffer detour...\n");
+ Scr_AddSourceBuffer_Detour = Detour(Scr_AddSourceBuffer, Scr_AddSourceBuffer_Hook);
+ Scr_AddSourceBuffer_Detour.Install();
+ }
+}
\ No newline at end of file
diff --git a/src/game/iw3_253/scr_parser.h b/src/game/iw3_253/scr_parser.h
new file mode 100644
index 0000000..22addef
--- /dev/null
+++ b/src/game/iw3_253/scr_parser.h
@@ -0,0 +1,4 @@
+namespace iw3_253
+{
+ void init_scr_parser();
+}
diff --git a/src/game/iw3_253/structs.h b/src/game/iw3_253/structs.h
new file mode 100644
index 0000000..03c4bdd
--- /dev/null
+++ b/src/game/iw3_253/structs.h
@@ -0,0 +1,425 @@
+#pragma warning(disable : 4480)
+
+namespace iw3_253
+{
+ 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;
+ };
+
+ enum OffhandSecondaryClass : __int32
+ {
+ PLAYER_OFFHAND_SECONDARY_SMOKE = 0x0,
+ PLAYER_OFFHAND_SECONDARY_FLASH = 0x1,
+ PLAYER_OFFHAND_SECONDARIES_TOTAL = 0x2,
+ };
+
+ enum ViewLockTypes : __int32
+ {
+ PLAYERVIEWLOCK_NONE = 0x0,
+ PLAYERVIEWLOCK_FULL = 0x1,
+ PLAYERVIEWLOCK_WEAPONJITTER = 0x2,
+ PLAYERVIEWLOCKCOUNT = 0x3,
+ };
+
+ struct SprintState
+ {
+ int sprintButtonUpRequired;
+ int sprintDelay;
+ int lastSprintStart;
+ int lastSprintEnd;
+ int sprintStartMaxLength;
+ };
+
+ struct MantleState
+ {
+ float yaw;
+ int timer;
+ int transIndex;
+ int flags;
+ };
+
+ enum ActionSlotType : __int32
+ {
+ ACTIONSLOTTYPE_DONOTHING = 0x0,
+ ACTIONSLOTTYPE_SPECIFYWEAPON = 0x1,
+ ACTIONSLOTTYPE_ALTWEAPONTOGGLE = 0x2,
+ ACTIONSLOTTYPE_NIGHTVISION = 0x3,
+ ACTIONSLOTTYPECOUNT = 0x4,
+ };
+
+ struct ActionSlotParam_SpecifyWeapon
+ {
+ unsigned int index;
+ };
+
+ struct ActionSlotParam
+ {
+ ActionSlotParam_SpecifyWeapon specifyWeapon;
+ };
+
+ enum objectiveState_t : __int32
+ {
+ OBJST_EMPTY = 0x0,
+ OBJST_ACTIVE = 0x1,
+ OBJST_INVISIBLE = 0x2,
+ OBJST_DONE = 0x3,
+ OBJST_CURRENT = 0x4,
+ OBJST_FAILED = 0x5,
+ OBJST_NUMSTATES = 0x6,
+ };
+
+ struct objective_t
+ {
+ objectiveState_t state;
+ float origin[3];
+ int entNum;
+ int teamNum;
+ int icon;
+ };
+
+ struct playerState_s
+ {
+ int commandTime;
+ int pm_type;
+ int bobCycle;
+ int pm_flags;
+ int weapFlags;
+ int otherFlags;
+ int pm_time;
+ float origin[3];
+ float velocity[3];
+ float oldVelocity[2];
+ int weaponTime;
+ int weaponDelay;
+ int grenadeTimeLeft;
+ int throwBackGrenadeOwner;
+ int throwBackGrenadeTimeLeft;
+ int weaponRestrictKickTime;
+ int foliageSoundTime;
+ int gravity;
+ float leanf;
+ int speed;
+ float delta_angles[3];
+ int groundEntityNum;
+ float vLadderVec[3];
+ int jumpTime;
+ float jumpOriginZ;
+ int legsTimer;
+ int legsAnim;
+ int torsoTimer;
+ int torsoAnim;
+ int legsAnimDuration;
+ int torsoAnimDuration;
+ int damageTimer;
+ int damageDuration;
+ int flinchYawAnim;
+ int movementDir;
+ int eFlags;
+ int eventSequence;
+ int events[4];
+ unsigned int eventParms[4];
+ int oldEventSequence;
+ int clientNum;
+ int offHandIndex;
+ OffhandSecondaryClass offhandSecondary;
+ unsigned int weapon;
+ int weaponstate;
+ unsigned int weaponShotCount;
+ float fWeaponPosFrac;
+ int adsDelayTime;
+ int spreadOverride;
+ int spreadOverrideState;
+ int viewmodelIndex;
+ float viewangles[3];
+ int viewHeightTarget;
+ float viewHeightCurrent;
+ int viewHeightLerpTime;
+ int viewHeightLerpTarget;
+ int viewHeightLerpDown;
+ float viewHeightLerpPosAdj;
+ float viewAngleClampBase[2];
+ float viewAngleClampRange[2];
+ int damageEvent;
+ int damageYaw;
+ int damagePitch;
+ int damageCount;
+ int stats[5];
+ int ammo[128];
+ int ammoclip[128];
+ unsigned int weapons[4];
+ unsigned int weaponold[4];
+ unsigned int weaponrechamber[4];
+ float proneDirection;
+ float proneDirectionPitch;
+ float proneTorsoPitch;
+ ViewLockTypes viewlocked;
+ int viewlocked_entNum;
+ int cursorHint;
+ int cursorHintString;
+ int cursorHintEntIndex;
+ int iCompassPlayerInfo;
+ int radarEnabled;
+ int locationSelectionInfo;
+ SprintState sprintState;
+ float fTorsoPitch;
+ float fWaistPitch;
+ float holdBreathScale;
+ int holdBreathTimer;
+ MantleState mantleState;
+ float meleeChargeYaw;
+ int meleeChargeDist;
+ ActionSlotType actionSlotType[4];
+ ActionSlotParam actionSlotParam[4];
+ int entityEventSequence;
+ int weapAnim;
+ float aimSpreadScale;
+ int shellshockIndex;
+ int shellshockTime;
+ int shellshockDuration;
+ float dofNearStart;
+ float dofNearEnd;
+ float dofFarStart;
+ float dofFarEnd;
+ float dofNearBlur;
+ float dofFarBlur;
+ objective_t objective[16];
+ int deltaTime;
+ // playerState_s::hud;
+ };
+
+ 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;
+ unsigned __int8 handler;
+ };
+
+ 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;
+ };
+
+ struct ScreenPlacement
+ {
+ float scaleVirtualToReal[2];
+ float scaleVirtualToFull[2];
+ float scaleRealToVirtual[2];
+ float virtualScreenOffsetX;
+ float virtualViewableMin[2];
+ float virtualViewableMax[2];
+ float realViewportSize[2];
+ float realViewableMin[2];
+ float realViewableMax[2];
+ float subScreenLeft;
+ };
+
+ struct Material;
+ struct Font_s;
+
+ struct cgMedia_t
+ {
+ Material *whiteMaterial;
+ Material *teamStatusBar;
+ Material *balloonMaterial;
+ Material *connectionMaterial;
+ Material *youInKillCamMaterial;
+ Material *tracerMaterial;
+ Material *laserMaterial;
+ Material *laserLightMaterial;
+ Material *lagometerMaterial;
+ Material *hintMaterials[133];
+ Material *stanceMaterials[4];
+ Material *objectiveMaterials[1];
+ Material *friendMaterials[2];
+ Material *damageMaterial;
+ Material *mantleHint;
+ Font_s *smallDevFont;
+ Font_s *bigDevFont;
+ // snd_alias_list_t *landDmgSound;
+ // snd_alias_list_t *grenadeExplodeSound[29];
+ // snd_alias_list_t *rocketExplodeSound[29];
+ // snd_alias_list_t *bulletHitSmallSound[29];
+ // snd_alias_list_t *bulletHitLargeSound[29];
+ // snd_alias_list_t *bulletHitAPSound[29];
+ // snd_alias_list_t *shotgunHitSound[29];
+ // snd_alias_list_t *stepRunSound[29];
+ // snd_alias_list_t *stepRunSoundPlayer[29];
+ // snd_alias_list_t *stepWalkSound[29];
+ // snd_alias_list_t *stepWalkSoundPlayer[29];
+ // snd_alias_list_t *stepProneSound[29];
+ // snd_alias_list_t *stepProneSoundPlayer[29];
+ // snd_alias_list_t *landSound[29];
+ // snd_alias_list_t *landSoundPlayer[29];
+ // snd_alias_list_t *runningEquipmentSound;
+ // snd_alias_list_t *runningEquipmentSoundPlayer;
+ // snd_alias_list_t *walkingEquipmentSound;
+ // snd_alias_list_t *walkingEquipmentSoundPlayer;
+ // snd_alias_list_t *foliageMovement;
+ // snd_alias_list_t *bulletWhizby;
+ // snd_alias_list_t *meleeSwingLarge;
+ // snd_alias_list_t *meleeSwingSmall;
+ // snd_alias_list_t *meleeHit;
+ // snd_alias_list_t *meleeHitOther;
+ // snd_alias_list_t *nightVisionOn;
+ // snd_alias_list_t *nightVisionOff;
+ // snd_alias_list_t *playerSprintGasp;
+ // snd_alias_list_t *playerHeartBeatSound;
+ // snd_alias_list_t *playerBreathInSound;
+ // snd_alias_list_t *playerBreathOutSound;
+ // snd_alias_list_t *playerBreathGaspSound;
+ // snd_alias_list_t *playerSwapOffhand;
+ // snd_alias_list_t *physCollisionSound[50][29];
+ // Material *compassping_friendlyfiring;
+ // Material *compassping_friendlyyelling;
+ // Material *compassping_enemy;
+ // Material *compassping_enemyfiring;
+ // Material *compassping_enemyyelling;
+ // Material *compassping_grenade;
+ // Material *compassping_explosion;
+ // Material *compass_radarline;
+ // Material *compass_helicopter_friendly;
+ // Material *compass_helicopter_enemy;
+ // Material *compass_plane_friendly;
+ // Material *compass_plane_enemy;
+ // Material *grenadeIconFrag;
+ // Material *grenadeIconFlash;
+ // Material *grenadeIconThrowBack;
+ // Material *grenadePointer;
+ // FxImpactTable *fx;
+ // const FxEffectDef *fxNoBloodFleshHit;
+ // const FxEffectDef *heliDustEffect;
+ // const FxEffectDef *heliWaterEffect;
+ // const FxEffectDef *helicopterLightSmoke;
+ // const FxEffectDef *helicopterHeavySmoke;
+ // const FxEffectDef *helicopterOnFire;
+ // const FxEffectDef *jetAfterburner;
+ // const FxEffectDef *fxVehicleTireDust;
+ // XModel *nightVisionGoggles;
+ // Material *nightVisionOverlay;
+ // Material *hudIconNVG;
+ // Material *hudDpadArrow;
+ // Material *ammoCounterBullet;
+ // Material *ammoCounterBeltBullet;
+ // Material *ammoCounterRifleBullet;
+ // Material *ammoCounterRocket;
+ // Material *ammoCounterShotgunShell;
+ };
+
+ enum TraceHitType : __int32
+ {
+ TRACE_HITTYPE_NONE = 0x0,
+ TRACE_HITTYPE_ENTITY = 0x1,
+ TRACE_HITTYPE_DYNENT_MODEL = 0x2,
+ TRACE_HITTYPE_DYNENT_BRUSH = 0x3,
+ };
+
+ struct __declspec(align(2)) trace_t
+ {
+ float fraction;
+ float normal[3];
+ int surfaceFlags;
+ int contents;
+ const char *material;
+ TraceHitType hitType;
+ unsigned __int16 hitId;
+ unsigned __int16 modelIndex;
+ unsigned __int16 partName;
+ unsigned __int16 partGroup;
+ bool allsolid;
+ bool startsolid;
+ bool walkable;
+ };
+
+ struct pml_t
+ {
+ float forward[3];
+ float right[3];
+ float up[3];
+ float frametime;
+ int msec;
+ int walking;
+ int groundPlane;
+ int almostGroundPlane;
+ trace_t groundTrace;
+ float impactSpeed;
+ float previous_origin[3];
+ float previous_velocity[3];
+ };
+
+}
\ No newline at end of file