From 60f337f46fa2dbcfea4192594ee441a3f0373682 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Sat, 13 Dec 2025 19:50:28 +0000 Subject: [PATCH 1/2] feat(IW4 MP): load SP maps in MP --- README.md | 29 + codxe.vcxproj | 1 + .../_codxe/internal/maps/af_caves.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/af_caves.gsc | 3 + .../_codxe/internal/maps/af_chase.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/af_chase.gsc | 3 + .../_codxe/internal/maps/airport.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/airport.gsc | 3 + .../_codxe/internal/maps/boneyard.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/boneyard.gsc | 3 + .../internal/maps/cliffhanger.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/cliffhanger.gsc | 3 + .../internal/maps/co_hunted.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/co_hunted.gsc | 3 + .../internal/maps/contingency.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/contingency.gsc | 3 + .../internal/maps/createfx/af_caves_fx.gsc | 1 + .../internal/maps/createfx/af_chase_fx.gsc | 1 + .../_codxe/internal/maps/createfx/airport.gsc | 1 + .../internal/maps/createfx/boneyard_fx.gsc | 1 + .../internal/maps/createfx/cliffhanger_fx.gsc | 1 + .../internal/maps/createfx/co_hunted_fx.gsc | 1 + .../internal/maps/createfx/contingency_fx.gsc | 1 + .../maps/createfx/dc_whitehouse_fx.gsc | 1 + .../internal/maps/createfx/dcburning_fx.gsc | 1 + .../internal/maps/createfx/dcemp_fx.gsc | 1 + .../internal/maps/createfx/estate_fx.gsc | 1 + .../internal/maps/createfx/gulag_fx.gsc | 1 + .../internal/maps/createfx/oilrig_fx.gsc | 1 + .../internal/maps/createfx/roadkill_fx.gsc | 1 + .../internal/maps/createfx/so_bridge.gsc | 1 + .../internal/maps/createfx/so_ghillies_fx.gsc | 1 + .../internal/maps/dc_whitehouse.d3dbsp.ents | 23 + .../_codxe/internal/maps/dc_whitehouse.gsc | 3 + .../internal/maps/dcburning.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/dcburning.gsc | 3 + .../_codxe/internal/maps/dcemp.d3dbsp.ents | 23 + resources/iw4/_codxe/internal/maps/dcemp.gsc | 3 + .../_codxe/internal/maps/estate.d3dbsp.ents | 23 + resources/iw4/_codxe/internal/maps/estate.gsc | 3 + .../_codxe/internal/maps/gulag.d3dbsp.ents | 23 + resources/iw4/_codxe/internal/maps/gulag.gsc | 3 + .../_codxe/internal/maps/oilrig.d3dbsp.ents | 23 + resources/iw4/_codxe/internal/maps/oilrig.gsc | 3 + .../_codxe/internal/maps/roadkill.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/roadkill.gsc | 3 + .../internal/maps/so_bridge.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/so_bridge.gsc | 3 + .../internal/maps/so_ghillies.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/so_ghillies.gsc | 3 + .../_codxe/internal/maps/trainer.d3dbsp.ents | 23 + .../iw4/_codxe/internal/maps/trainer.gsc | 3 + .../codjumper/maps/mp/gametypes/cj_mp.gsc | 16 + .../mods/codjumper/maps/mp/gametypes/menu.gsc | 42 ++ ...uty - Modern Warfare 2 MP (TU6).patch.toml | 10 + src/game/iw4/mp/components/mpsp.cpp | 626 ++++++++++++++++++ src/game/iw4/mp/components/mpsp.h | 22 + src/game/iw4/mp/main.cpp | 2 + src/game/iw4/mp/structs.h | 43 ++ 59 files changed, 1249 insertions(+) create mode 100644 resources/iw4/_codxe/internal/maps/af_caves.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/af_caves.gsc create mode 100644 resources/iw4/_codxe/internal/maps/af_chase.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/af_chase.gsc create mode 100644 resources/iw4/_codxe/internal/maps/airport.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/airport.gsc create mode 100644 resources/iw4/_codxe/internal/maps/boneyard.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/boneyard.gsc create mode 100644 resources/iw4/_codxe/internal/maps/cliffhanger.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/cliffhanger.gsc create mode 100644 resources/iw4/_codxe/internal/maps/co_hunted.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/co_hunted.gsc create mode 100644 resources/iw4/_codxe/internal/maps/contingency.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/contingency.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/af_caves_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/af_chase_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/airport.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/boneyard_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/cliffhanger_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/co_hunted_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/contingency_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/dc_whitehouse_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/dcburning_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/dcemp_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/estate_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/gulag_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/oilrig_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/roadkill_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/so_bridge.gsc create mode 100644 resources/iw4/_codxe/internal/maps/createfx/so_ghillies_fx.gsc create mode 100644 resources/iw4/_codxe/internal/maps/dc_whitehouse.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/dc_whitehouse.gsc create mode 100644 resources/iw4/_codxe/internal/maps/dcburning.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/dcburning.gsc create mode 100644 resources/iw4/_codxe/internal/maps/dcemp.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/dcemp.gsc create mode 100644 resources/iw4/_codxe/internal/maps/estate.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/estate.gsc create mode 100644 resources/iw4/_codxe/internal/maps/gulag.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/gulag.gsc create mode 100644 resources/iw4/_codxe/internal/maps/oilrig.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/oilrig.gsc create mode 100644 resources/iw4/_codxe/internal/maps/roadkill.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/roadkill.gsc create mode 100644 resources/iw4/_codxe/internal/maps/so_bridge.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/so_bridge.gsc create mode 100644 resources/iw4/_codxe/internal/maps/so_ghillies.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/so_ghillies.gsc create mode 100644 resources/iw4/_codxe/internal/maps/trainer.d3dbsp.ents create mode 100644 resources/iw4/_codxe/internal/maps/trainer.gsc create mode 100644 src/game/iw4/mp/components/mpsp.cpp create mode 100644 src/game/iw4/mp/components/mpsp.h diff --git a/README.md b/README.md index 606ed50..cb100ce 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,35 @@ Loading single player maps in multiplayer is a best effort approach. Lots of thi NOTE: Xenia requires the [patch]() to increase memory installed. +## Call of Duty: Modern Warfare 2 + +Loading single player maps in multiplayer is a best effort approach. Lots of things are broken such as missing FX, player models, crashes etc. + +| Name | Xbox 360 | Xenia | +| ---------------- | :------: | :---: | +| af_caves.ff | | 🟢 | +| af_chase.ff | | 🟢 | +| airport.ff | | 🟢 | +| arcadia.ff | | 🔴 | +| boneyard.ff | | 🟢 | +| cliffhanger.ff | | 🟢 | +| co_hunted.ff | | 🟢 | +| contingency.ff | | 🟢 | +| dc_whitehouse.ff | | 🟢 | +| dcburning.ff | | 🔴 | +| dcemp.ff | | 🟢 | +| ending.ff | | 🔴 | +| estate.ff | | 🟢 | +| favela.ff | | 🔴 | +| favela_escape.ff | | 🔴 | +| gulag.ff | | 🔴 | +| invasion.ff | | 🔴 | +| oilrig.ff | | 🟢 | +| roadkill.ff | | 🔴 | +| so_bridge.ff | | 🟢 | +| so_ghillies.ff | | 🟢 | +| trainer.ff | | 🟢 | + ## Features ### GSC Loader diff --git a/codxe.vcxproj b/codxe.vcxproj index b554b8d..567da16 100644 --- a/codxe.vcxproj +++ b/codxe.vcxproj @@ -115,6 +115,7 @@ + diff --git a/resources/iw4/_codxe/internal/maps/af_caves.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/af_caves.d3dbsp.ents new file mode 100644 index 0000000..4113fb5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/af_caves.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "4993.7 14416.9 -1315.6" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} +{ +"origin" "4993.7 14416.9 -1315.6" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "4993.7 14416.9 -1315.6" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "4993.7 14416.9 -1315.6" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} diff --git a/resources/iw4/_codxe/internal/maps/af_caves.gsc b/resources/iw4/_codxe/internal/maps/af_caves.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/af_caves.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/af_chase.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/af_chase.d3dbsp.ents new file mode 100644 index 0000000..38fd32f --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/af_chase.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-19902 1654 296" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} +{ +"origin" "-19902 1654 296" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-19902 1654 296" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-19902 1654 296" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} diff --git a/resources/iw4/_codxe/internal/maps/af_chase.gsc b/resources/iw4/_codxe/internal/maps/af_chase.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/af_chase.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/airport.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/airport.d3dbsp.ents new file mode 100644 index 0000000..107f10e --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/airport.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "6528 2312 56" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "6528 2312 56" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "6528 2312 56" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "6528 2312 56" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/airport.gsc b/resources/iw4/_codxe/internal/maps/airport.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/airport.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/boneyard.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/boneyard.d3dbsp.ents new file mode 100644 index 0000000..a3a284f --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/boneyard.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-5945 -4781 66.5" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-5945 -4781 66.5" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-5945 -4781 66.5" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-5945 -4781 66.5" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/boneyard.gsc b/resources/iw4/_codxe/internal/maps/boneyard.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/boneyard.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/cliffhanger.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/cliffhanger.d3dbsp.ents new file mode 100644 index 0000000..3406cfd --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/cliffhanger.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "363.4 -29122.6 196.8" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} +{ +"origin" "363.4 -29122.6 196.8" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "363.4 -29122.6 196.8" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "363.4 -29122.6 196.8" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} diff --git a/resources/iw4/_codxe/internal/maps/cliffhanger.gsc b/resources/iw4/_codxe/internal/maps/cliffhanger.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/cliffhanger.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/co_hunted.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/co_hunted.d3dbsp.ents new file mode 100644 index 0000000..a818bb7 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/co_hunted.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "2241.2 -8541.8 303.3" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "2241.2 -8541.8 303.3" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "2241.2 -8541.8 303.3" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "2241.2 -8541.8 303.3" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/co_hunted.gsc b/resources/iw4/_codxe/internal/maps/co_hunted.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/co_hunted.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/contingency.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/contingency.d3dbsp.ents new file mode 100644 index 0000000..71ad642 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/contingency.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-38783 -15530.3 101" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} +{ +"origin" "-38783 -15530.3 101" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-38783 -15530.3 101" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-38783 -15530.3 101" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} diff --git a/resources/iw4/_codxe/internal/maps/contingency.gsc b/resources/iw4/_codxe/internal/maps/contingency.gsc new file mode 100644 index 0000000..54dc4c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/contingency.gsc @@ -0,0 +1,3 @@ +main() +{ +} diff --git a/resources/iw4/_codxe/internal/maps/createfx/af_caves_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/af_caves_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/af_caves_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/af_chase_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/af_chase_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/af_chase_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/airport.gsc b/resources/iw4/_codxe/internal/maps/createfx/airport.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/airport.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/boneyard_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/boneyard_fx.gsc new file mode 100644 index 0000000..dd5fa3d --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/boneyard_fx.gsc @@ -0,0 +1 @@ +main(){} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/createfx/cliffhanger_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/cliffhanger_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/cliffhanger_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/co_hunted_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/co_hunted_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/co_hunted_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/contingency_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/contingency_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/contingency_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/dc_whitehouse_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/dc_whitehouse_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/dc_whitehouse_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/dcburning_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/dcburning_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/dcburning_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/dcemp_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/dcemp_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/dcemp_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/estate_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/estate_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/estate_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/gulag_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/gulag_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/gulag_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/oilrig_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/oilrig_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/oilrig_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/roadkill_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/roadkill_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/roadkill_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/so_bridge.gsc b/resources/iw4/_codxe/internal/maps/createfx/so_bridge.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/so_bridge.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/createfx/so_ghillies_fx.gsc b/resources/iw4/_codxe/internal/maps/createfx/so_ghillies_fx.gsc new file mode 100644 index 0000000..05437c5 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/createfx/so_ghillies_fx.gsc @@ -0,0 +1 @@ +main(){} diff --git a/resources/iw4/_codxe/internal/maps/dc_whitehouse.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/dc_whitehouse.d3dbsp.ents new file mode 100644 index 0000000..326aa43 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/dc_whitehouse.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-8792 5404 -647" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-8792 5404 -647" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-8792 5404 -647" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-8792 5404 -647" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/dc_whitehouse.gsc b/resources/iw4/_codxe/internal/maps/dc_whitehouse.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/dc_whitehouse.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/dcburning.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/dcburning.d3dbsp.ents new file mode 100644 index 0000000..6fd34c0 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/dcburning.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-22553.8 -1855.8 -664" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-22553.8 -1855.8 -664" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-22553.8 -1855.8 -664" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-22553.8 -1855.8 -664" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/dcburning.gsc b/resources/iw4/_codxe/internal/maps/dcburning.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/dcburning.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/dcemp.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/dcemp.d3dbsp.ents new file mode 100644 index 0000000..bb074a9 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/dcemp.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/dcemp.gsc b/resources/iw4/_codxe/internal/maps/dcemp.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/dcemp.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/estate.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/estate.d3dbsp.ents new file mode 100644 index 0000000..bb074a9 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/estate.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-47840 7672 -374" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/estate.gsc b/resources/iw4/_codxe/internal/maps/estate.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/estate.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/gulag.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/gulag.d3dbsp.ents new file mode 100644 index 0000000..d0de643 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/gulag.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-3646.4 1622.2 124" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-3646.4 1622.2 124" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-3646.4 1622.2 124" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-3646.4 1622.2 124" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/gulag.gsc b/resources/iw4/_codxe/internal/maps/gulag.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/gulag.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/oilrig.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/oilrig.d3dbsp.ents new file mode 100644 index 0000000..731ae65 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/oilrig.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "22096.5 16.9 -3224" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "22096.5 16.9 -3224" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "22096.5 16.9 -3224" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "22096.5 16.9 -3224" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/oilrig.gsc b/resources/iw4/_codxe/internal/maps/oilrig.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/oilrig.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/roadkill.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/roadkill.d3dbsp.ents new file mode 100644 index 0000000..b5bf66b --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/roadkill.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-1433 -4562 18" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-1433 -4562 18" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-1433 -4562 18" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-1433 -4562 18" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/roadkill.gsc b/resources/iw4/_codxe/internal/maps/roadkill.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/roadkill.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/so_bridge.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/so_bridge.d3dbsp.ents new file mode 100644 index 0000000..e4cfe73 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/so_bridge.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "10408 29272 -179.3" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "10408 29272 -179.3" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "10408 29272 -179.3" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "10408 29272 -179.3" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/so_bridge.gsc b/resources/iw4/_codxe/internal/maps/so_bridge.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/so_bridge.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/so_ghillies.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/so_ghillies.d3dbsp.ents new file mode 100644 index 0000000..4d00d72 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/so_ghillies.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-34217 -8837 171" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-34217 -8837 171" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-34217 -8837 171" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} +{ +"origin" "-34217 -8837 171" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} diff --git a/resources/iw4/_codxe/internal/maps/so_ghillies.gsc b/resources/iw4/_codxe/internal/maps/so_ghillies.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/so_ghillies.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/internal/maps/trainer.d3dbsp.ents b/resources/iw4/_codxe/internal/maps/trainer.d3dbsp.ents new file mode 100644 index 0000000..50922ac --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/trainer.d3dbsp.ents @@ -0,0 +1,23 @@ +{ +"classname" "worldspawn" +} +{ +"origin" "-2589.9 5206.3 -23.1" +"angles" "0 30 0" +"classname" "mp_global_intermission" +} +{ +"origin" "-2589.9 5206.3 -23.1" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_allies_start" +} +{ +"origin" "-2589.9 5206.3 -23.1" +"angles" "0 30 0" +"classname" "mp_tdm_spawn_axis_start" +} +{ +"origin" "-2589.9 5206.3 -23.1" +"angles" "0 30 0" +"classname" "mp_tdm_spawn" +} diff --git a/resources/iw4/_codxe/internal/maps/trainer.gsc b/resources/iw4/_codxe/internal/maps/trainer.gsc new file mode 100644 index 0000000..c2d7806 --- /dev/null +++ b/resources/iw4/_codxe/internal/maps/trainer.gsc @@ -0,0 +1,3 @@ +main() +{ +} \ No newline at end of file diff --git a/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/cj_mp.gsc b/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/cj_mp.gsc index dc09542..57e0bd3 100644 --- a/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/cj_mp.gsc +++ b/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/cj_mp.gsc @@ -3,6 +3,11 @@ init() { + precachemodel("viewmodel_base_viewhands"); + + // Unless this is set then SP maps spawn with no weapons + // https://github.com/shit-ware/IW4/blob/master/common_scripts/utility.gsc#L2080 + level.isSP = false; DeleteUnwantedEntities(); @@ -59,6 +64,11 @@ onPlayerConnect() } } +is_sp_mapname() +{ + return getsubstr(getdvar("mapname"), 0, 2) != "mp_"; +} + onPlayerSpawned() { self endon("disconnect"); @@ -75,6 +85,12 @@ onPlayerSpawned() self thread MonitorButtons(); self thread MonitorPlayerCommands(); self thread maps\mp\gametypes\cj_hud::StartHUD(); + + // On singleplayer maps use the base viewhands included in common_mp + if (is_sp_mapname()) + { + self setViewmodel("viewmodel_base_viewhands"); + } } } diff --git a/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/menu.gsc b/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/menu.gsc index 95fff44..76aef3a 100644 --- a/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/menu.gsc +++ b/resources/iw4/_codxe/mods/codjumper/maps/mp/gametypes/menu.gsc @@ -11,6 +11,7 @@ init() level.THEMES = get_themes(); level.MAPS = get_maps(); + level.MAPS_SP = get_maps_sp(); } /** @@ -84,6 +85,36 @@ get_maps() return maps; } +get_maps_sp() +{ + maps = []; + // Alphabetically sorted by value + maps["af_caves"] = "af_caves"; + maps["af_chase"] = "af_chase"; + maps["airport"] = "airport"; + maps["arcadia"] = "arcadia"; + maps["boneyard"] = "boneyard"; + maps["cliffhanger"] = "cliffhanger"; + maps["co_hunted"] = "co_hunted"; + maps["contingency"] = "contingency"; + maps["dc_whitehouse"] = "dc_whitehouse"; + maps["dcburning"] = "dcburning"; + maps["dcemp"] = "dcemp"; + maps["ending"] = "ending"; + maps["estate"] = "estate"; + maps["favela"] = "favela"; + maps["favela_escape"] = "favela_escape"; + maps["gulag"] = "gulag"; + maps["invasion"] = "invasion"; + maps["oilrig"] = "oilrig"; + maps["roadkill"] = "roadkill"; + maps["so_bridge"] = "so_bridge"; + maps["so_ghillies"] = "so_ghillies"; + maps["trainer"] = "trainer"; + + return maps; +} + /** * Add a menu to the menuOptions array. * @param menuKey The key to identify the menu. @@ -390,6 +421,17 @@ generateMenuOptions() self addMenuOption("host_menu_maps", label, ::changeMap, mapname); } + // Map selector SP + self addMenuOption("main", "Maps SP", ::menuAction, "CHANGE_MENU", "host_menu_maps_sp"); + self addMenu("host_menu_maps_sp", "main"); + maps = getarraykeys(level.MAPS_SP); + for (i = 0; i < maps.size; i++) + { + mapname = maps[i]; + label = level.MAPS_SP[mapname]; + self addMenuOption("host_menu_maps_sp", label, ::changeMap, mapname); + } + self addMenuOption("main", "Themes", ::menuAction, "CHANGE_MENU", "theme_menu"); self addMenu("theme_menu", "main"); themes = getarraykeys(level.THEMES); diff --git a/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml b/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml index 00801d4..ecfd593 100644 --- a/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml +++ b/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml @@ -25,3 +25,13 @@ hash = "A3FFB7C11B688FE5" # default_mp.xex [[patch.be16]] address = 0x823C3E36 value = 0x0000 # 2 -> 0 + +# 0x1C50 + +[[patch.be16]] +address = 0x823129FA +value = 0x1E00 + +[[patch.be16]] +address = 0x82312A1E +value = 0x1E00 diff --git a/src/game/iw4/mp/components/mpsp.cpp b/src/game/iw4/mp/components/mpsp.cpp new file mode 100644 index 0000000..4a0d1f9 --- /dev/null +++ b/src/game/iw4/mp/components/mpsp.cpp @@ -0,0 +1,626 @@ + + +#include "pch.h" +#include "mpsp.h" +#include "unordered_map" + +namespace iw4 +{ +namespace mp +{ +bool mpsp::is_sp_map = false; + +bool is_mp_fastfile(const char *name) +{ + if (!name) + return false; + + const size_t len = std::strlen(name); + + // starts with "mp_" + if (len >= 3 && std::strncmp(name, "mp_", 3) == 0) + return true; + + // ends with "_mp" + if (len >= 3 && std::strcmp(name + len - 3, "_mp") == 0) + return true; + + return false; +} + +struct internal_state; + +struct Sys_File +{ + void *handle; + int startOffset; +}; + +struct DBFile +{ + Sys_File handle; + char name[64]; +}; + +struct XBlock +{ + unsigned __int8 *data; + unsigned int size; +}; + +struct XZoneMemory +{ + XBlock blocks[6]; +}; + +struct XZone +{ + DBFile file; + int flags; + int allocType; + XZoneMemory mem; +}; + +struct _OVERLAPPED +{ + unsigned int Internal; + unsigned int InternalHigh; + unsigned int Offset; + unsigned int OffsetHigh; + void *hEvent; +}; + +/* 9458 */ +struct z_stream_s +{ + unsigned __int8 *next_in; + unsigned int avail_in; + unsigned int total_in; + unsigned __int8 *next_out; + unsigned int avail_out; + unsigned int total_out; + char *msg; + internal_state *state; + unsigned __int8 *(__fastcall *zalloc)(unsigned __int8 *, unsigned int, unsigned int); + void(__fastcall *zfree)(unsigned __int8 *, unsigned __int8 *); + unsigned __int8 *opaque; + int data_type; +}; + +/* 10286 */ +struct DB_LoadData +{ + DBFile *file; + int outstandingRead; + unsigned __int8 *fileBuffer; + unsigned int readSize; + unsigned int completedReadSize; + unsigned int offset; + unsigned __int8 *start_in; + _OVERLAPPED overlapped; + unsigned int readError; + z_stream_s stream; + unsigned int lookaheadReadSize; + unsigned int lookaheadOffset; + unsigned int lookaheadClearAvailIn; +}; + +typedef int (*DB_GetXAssetSizeHandlerFunc)(); +DB_GetXAssetSizeHandlerFunc *DB_GetXAssetSizeHandler = reinterpret_cast(0x82442490); +void **DB_XAssetPool = reinterpret_cast(0x82442828); +const char **g_assetNames = reinterpret_cast(0x82442298); +int *g_poolSize = reinterpret_cast(0x82442588); +const DB_LoadData *g_load = reinterpret_cast(0x82678600); +const unsigned int *g_zoneIndex = reinterpret_cast(0x827ADAE4); +const XZone *g_zones = reinterpret_cast(0x829D8048); +GameWorldMp *gameWorldMp = reinterpret_cast(0x82DFD010); + +typedef void (*CL_ConsolePrint_t)(int localClientNum, int channel, const char *txt, unsigned int duration, + unsigned int pixelWidth, int flags); +CL_ConsolePrint_t CL_ConsolePrint = reinterpret_cast(0x821754B8); + +typedef int (*Com_sprintf_t)(char *dest, unsigned int size, const char *fmt, ...); +Com_sprintf_t Com_sprintf = reinterpret_cast(0x82315F20); + +typedef XAssetHeader *(*DB_FindXAssetHeader_t)(XAssetType type, const char *name); +DB_FindXAssetHeader_t DB_FindXAssetHeader = reinterpret_cast(0x821E25B0); + +typedef const char *(*DB_GetXAssetName_t)(const XAsset *asset); +DB_GetXAssetName_t DB_GetXAssetName = reinterpret_cast(0x821AEFD8); + +typedef void (*DB_SetXAssetName_t)(XAsset *asset, const char *name); +DB_SetXAssetName_t DB_SetXAssetName = reinterpret_cast(0x821AEFF8); + +typedef void (*DB_LoadXFile_t)(XZoneMemory *zoneMem, DBFile *file); +DB_LoadXFile_t DB_LoadXFile = reinterpret_cast(0x821AFCB8); + +typedef XAssetEntryPoolEntry *(*DB_LinkXAssetEntry1_t)(XAssetType type, XAssetHeader *header); +// DB_LinkXAssetEntry1_t DB_LinkXAssetEntry1 = reinterpret_cast(0x821DE528); + +Detour CL_ConsolePrint_Detour; +void CL_ConsolePrint_Hook(int localClientNum, int channel, const char *txt, unsigned int duration, + unsigned int pixelWidth, int flags) +{ + DbgPrint("[CL_ConsolePrint] %s", txt); + + CL_ConsolePrint_Detour.GetOriginal()(localClientNum, channel, txt, duration, pixelWidth, flags); +} + +namespace FS +{ + +/* + * Normalizes a file path by replacing forward slashes with backslashes, + * removing duplicate backslashes, and trimming trailing backslashes (except for root paths). + */ +std::string NormalizePath(const std::string &path) +{ + std::string result = path; + std::replace(result.begin(), result.end(), '/', '\\'); + + size_t pos = 0; + while ((pos = result.find("\\\\", pos)) != std::string::npos) + { + result.erase(pos, 1); + } + + if (!result.empty() && result[result.length() - 1] == '\\') + { + size_t root_end = result.find_first_of('\\'); + if (root_end != result.length() - 1) + { + result.erase(result.length() - 1); + } + } + + return result; +} + +bool WriteFile(const std::string &path, const char *data, int size) +{ + const std::string normalized = NormalizePath(path); + + // Create directories if they do not exist + size_t pos = 0; + while ((pos = normalized.find('\\', pos)) != std::string::npos) + { + std::string dir = normalized.substr(0, pos); + CreateDirectoryA(dir.c_str(), NULL); + pos++; + } + + std::ofstream file(normalized.c_str(), std::ios::binary); + if (!file.is_open()) + { + DbgPrint("[IO] Failed to open file for writing: %s\n", normalized.c_str()); + return false; + } + + file.write(reinterpret_cast(data), size); + return file.good(); +} + +std::string ReadTextFile(const std::string &path) +{ + const std::string normalized = NormalizePath(path); + std::ifstream file(normalized, std::ios::binary); + if (!file) + { + DbgPrint("ReadTextFile: Failed to open file: %s\n", normalized.c_str()); + return ""; + } + + // Get file size and pre-allocate string + file.seekg(0, std::ios::end); + size_t size = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + std::string content(size, '\0'); + file.read(&content[0], size); + + return content; +} + +std::vector ReadBinaryFile(const std::string &path) +{ + const std::string normalized = NormalizePath(path); + std::ifstream file(normalized, std::ios::binary); + if (!file) + { + DbgPrint("[IO] ReadBinaryFile: Failed to open file: %s\n", normalized.c_str()); + return std::vector(); + } + + // Get file size and pre-allocate vector + file.seekg(0, std::ios::end); + size_t size = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + std::vector buffer(size); + file.read(reinterpret_cast(buffer.data()), size); + + if (!file.good() && !file.eof()) + { + DbgPrint("[IO] ReadBinaryFile: Error reading file: %s\n", normalized.c_str()); + return std::vector(); + } + + return buffer; +} + +} // namespace FS + +namespace Asset +{ + +std::string get_dump_dir() +{ + return "game:\\_dump"; +} + +std::string get_load_dir() +{ + return "game:\\_codxe\\internal"; +} +namespace MapEnts_ +{ + +std::string mapents_buffer; + +void dump(MapEnts *asset) +{ + std::string buffer; + + if (!asset || !asset->name || asset->name[0] == '\0') + { + return; + } + + // Don't attempt to dump referenced assets + if (asset->name[0] == ',') + { + return; + } + + DbgPrint("Dumping mapents asset %s from fastfile %s\n", asset->name, g_load->file->name); + + const std::string filename = Asset::get_dump_dir() + "\\" + asset->name + ".ents"; + FS::WriteFile(filename, asset->entityString, asset->numEntityChars - 1); +} + +void override_(MapEnts *asset) +{ + // _load\maps\jeepride.d3dbsp.ents + const std::string filename = Asset::get_load_dir() + "\\" + asset->name + ".ents"; + const std::string buffer = FS::ReadTextFile(filename); + + if (buffer.empty()) + { + return; + } + + if (!mapents_buffer.empty()) + { + mapents_buffer.clear(); + } + + DbgPrint("Overiding mapents asset %s from fastfile %s\n", asset->name, g_load->file->name); + + mapents_buffer.assign(buffer); + + asset->entityString = const_cast(mapents_buffer.c_str()); + asset->numEntityChars = mapents_buffer.length(); +} + +} // namespace MapEnts_ + +namespace RawFile_ +{ +std::unordered_map> rawfile_buffers; + +void override_(RawFile *asset) +{ + /* + NOTE: + Some RawFile assets in fastfiles are compressed, but the engine will + accept an uncompressed override. Setting: + + asset->compressedLen = 0; + + forces the loader to skip decompression and treat our buffer as plain + uncompressed data. After that, setting asset->len and asset->buffer + is enough for the engine to load the override correctly. + */ + + if (!asset || !asset->name || asset->name[0] == '\0') + { + return; + } + + const std::string filename = Asset::get_load_dir() + "\\" + asset->name; + const std::string buffer = FS::ReadTextFile(filename); + + if (buffer.empty()) + { + return; + } + + // NOTE: It seems that the engine will handle us giving back uncompressed strings (tested at least for .gsc files!) + + DbgPrint("Overriding rawfile asset %s from fastfile %s\n", asset->name, g_load->file->name); + + auto itr = rawfile_buffers.find(asset->name); + if (itr != rawfile_buffers.end()) + { + rawfile_buffers.erase(itr); + } + rawfile_buffers[asset->name] = make_unique(); + itr = rawfile_buffers.find(asset->name); + auto rawfile_buffer = itr->second.get(); + + rawfile_buffer->assign(buffer); + + asset->compressedLen = 0; // Force the engine to treat it as an uncompressed buffer! + asset->len = rawfile_buffer->length(); + asset->buffer = rawfile_buffer->data(); +} + +} // namespace RawFile_ + +} // namespace Asset + +// "default", +// "void", +// "void", +// "fx10", +// "void", +// "$default", +// "null.hlsl", +// "default", +// "$white", +// "null", +// "default", +// "null", +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// "light_dynamic", +// &byte_82006A5F, +// "fonts/consolefont", +// "ui/default.menu", +// "default_menu", +// "CGAME_UNKNOWN", +// "defaultweapon_mp", +// &byte_82006A5F, +// "misc/missing_fx", +// "default", +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// &byte_82006A5F, +// "codescripts/$default", +// "mp/defaultStringTable.csv", +// "DEFAULT_LB", +// "mp/defaultStructuredData.def", +// "defaulttracer", +// "defaultvehicle", +// &byte_82006A5F + +Detour DB_LinkXAssetEntry1_Detour; +XAssetEntryPoolEntry *DB_LinkXAssetEntry1_Hook(XAssetType type, XAssetHeader *header) +{ + + static const char *last_fastfile = nullptr; + static int asset_counter = 0; + + const char *current_fastfile = g_load->file->name; + + // Reset counter when fastfile changes + if (last_fastfile == nullptr || strcmp(last_fastfile, current_fastfile) != 0) + { + last_fastfile = current_fastfile; + asset_counter = 0; + } + + XAsset xasset; + xasset.type = type; + xasset.header = *header; + + const int asset_type = type; + const char *asset_type_name = g_assetNames[asset_type]; + const char *asset_name = DB_GetXAssetName(&xasset); + const char *zone_name = g_zones[*g_zoneIndex].file.name; + + DbgPrint("[DB_LinkXAssetEntry1] [%04d] %-24s | %-16s (%d) | %s\n", asset_counter, zone_name, asset_type_name, + asset_type, asset_name); + + ++asset_counter; + // DbgPrint("[DB_LinkXAssetEntry1] returnaddr %p\n", _ReturnAddress()); + + // Before linking + switch (type) + { + case ASSET_TYPE_MAP_ENTS: + Asset::MapEnts_::dump(header->mapEnts); + Asset::MapEnts_::override_(header->mapEnts); + break; + case ASSET_TYPE_RAWFILE: + Asset::RawFile_::override_(header->rawfile); + break; + } + + // Ignore these assets to save on asset pool slots + // Hijacks the referenced asset mechanism so that DB_AllocXAssetEntry is never called + // Make some assumptions that these assets will not be used. + if (!is_mp_fastfile(g_load->file->name)) + { + switch (type) + { + case ASSET_TYPE_SOUND: + DB_SetXAssetName(&xasset, ",null"); + break; + case ASSET_TYPE_LOADED_SOUND: + DB_SetXAssetName(&xasset, ",null"); + break; + case ASSET_TYPE_FX: + DB_SetXAssetName(&xasset, ",misc/missing_fx"); + break; + case ASSET_TYPE_XMODEL: + if (strncmp(asset_name, "viewmodel_", 10) == 0 && strncmp(asset_name, "viewmodel_base_viewhands", 24) != 0) + { + DbgPrint("referenced asset hack viewmodel_base_viewhands\n"); + DB_SetXAssetName(&xasset, ",viewmodel_base_viewhands"); + } + else if (strncmp(asset_name, "weapon_", 7) == 0) + { + DbgPrint("referenced asset hack ASSET_TYPE_XMODEL weapon_\n"); + DB_SetXAssetName(&xasset, ",void"); + } + break; + case ASSET_TYPE_MATERIAL: + if (strncmp(asset_name, "mc/mtl_weapon_", 14) == 0) + { + DbgPrint("referenced asset hack ASSET_TYPE_MATERIAL mc/mtl_weapon_\n"); + DB_SetXAssetName(&xasset, ",$default"); + } + if (strncmp(asset_name, "hud_", 4) == 0) + { + DbgPrint("referenced asset hack ASSET_TYPE_MATERIAL hud_\n"); + DB_SetXAssetName(&xasset, ",$default"); + } + break; + case ASSET_TYPE_GAMEWORLD_SP: + type = ASSET_TYPE_GAMEWORLD_MP; + header->gameWorldMp->g_glassData = header->gameWorldSp->g_glassData; + break; + case ASSET_TYPE_LOCALIZE_ENTRY: + DB_SetXAssetName(&xasset, ",CGAME_UNKNOWN"); + } + } + + XAssetEntryPoolEntry *entry = DB_LinkXAssetEntry1_Detour.GetOriginal()(type, header); + + return entry; +} + +void DB_ReallocXAssetPool(XAssetType type, unsigned int newSize) +{ + void *pool_entry = malloc(newSize * DB_GetXAssetSizeHandler[type]()); + DB_XAssetPool[type] = pool_entry; + g_poolSize[type] = newSize; +} + +Detour Com_sprintf_Detour; +int Com_sprintf_Hook(char *dest, unsigned int size, const char *fmt...) +{ + // Do original logic + va_list va; + va_start(va, fmt); + int result = _vsnprintf(dest, size, fmt, va); + va_end(va); + + if (size > 0) + dest[size - 1] = '\0'; + + // Rewrites + + // [mpsp] + const void *returnaddr = _ReturnAddress(); + if (returnaddr == (void *)0x822F6AD8) // Com_GetBspFilename + { + // Format is always "maps/mp/.d3dbsp" + const char *mapname = dest + 8; // skip "maps/mp/" + const bool isMp = (strncmp(mapname, "mp_", 3) == 0); + + if (!isMp) + { + // Rewrite to "maps/.d3dbsp" + char *dst = dest + 5; // after "maps/" + const char *src = dest + 8; // after "maps/mp/" + memmove(dst, src, strlen(src) + 1); + } + } + + // [mpsp] + if (returnaddr == (void *)0x8224EDA0) // GScr_LoadLevelScript + { + // "maps/mp/%s" + const char *mapname = dest + 8; // skip "maps/mp/" + const bool isMp = (strncmp(mapname, "mp_", 3) == 0); + if (!isMp) + { + // Rewrite to "maps/%s" + char *dst = dest + 5; // after "maps/" + const char *src = dest + 8; // after "maps/mp/" + memmove(dst, src, strlen(src) + 1); + } + } + + return result; +} + +void DisableFastfileAuthCheck() +{ + // The games requires fastfiles to be signed in MP but it has the code to load + // unsigned in the executable. Disables the check for auth + *(volatile uint32_t *)0x821B0978 = 0x60000000; +} + +void Patch_ImageCache_OOM() +{ + // ImageCache_AllocMemory + // 0x823DF628: bne cr6, loc_823DF6CC -> b loc_823DF6CC + // Original: 0x409E00A4 + // Patched: 0x480000A4 (unconditional relative branch +0xA4) + *(volatile uint32_t *)0x823DF628 = 0x480000A4; +} + +DWORD WINAPI ThreadProc(LPVOID param) +{ + + DbgPrint("Thread Started!\n"); + + Sleep(2000); + Cbuf_AddText(0, "meminfo\n"); + // Cbuf_AddText(0, "set developer 1\n"); + // Cbuf_AddText(0, "set developer_script 1\n"); + Cbuf_AddText(0, "set ui_mapname contingency\n"); + + // Sleep(3500); + + // Cbuf_AddText(0, "set loc_warnings 0\n"); // Disable unlocalized warnings + // Cbuf_AddText(0, "set ui_mapname trainer\n"); + + return 0; +} + +mpsp::mpsp() +{ + DisableFastfileAuthCheck(); + // Patch_ImageCache_OOM(); + + CL_ConsolePrint_Detour = Detour(CL_ConsolePrint, CL_ConsolePrint_Hook); + CL_ConsolePrint_Detour.Install(); + + DB_LinkXAssetEntry1_Detour = Detour(DB_LinkXAssetEntry1, DB_LinkXAssetEntry1_Hook); + DB_LinkXAssetEntry1_Detour.Install(); + + // Rewrite some strings on the fly + Com_sprintf_Detour = Detour(Com_sprintf, Com_sprintf_Hook); + Com_sprintf_Detour.Install(); + + // Start a thread to not block the main thread + ExCreateThread(nullptr, 0, nullptr, nullptr, &ThreadProc, nullptr, EX_CREATE_FLAG_TITLE_EXEC); +} + +mpsp::~mpsp() +{ +} + +} // namespace mp +} // namespace iw4 diff --git a/src/game/iw4/mp/components/mpsp.h b/src/game/iw4/mp/components/mpsp.h new file mode 100644 index 0000000..8f6a17c --- /dev/null +++ b/src/game/iw4/mp/components/mpsp.h @@ -0,0 +1,22 @@ +#pragma once + +#include "pch.h" + +namespace iw4 +{ +namespace mp +{ +class mpsp : public Module +{ + public: + mpsp(); + ~mpsp(); + const char *get_name() override + { + return "mpsp"; + }; + static bool is_sp_map; + // static bool is_sp_mapname(const std::string &name); +}; +} // namespace mp +} // namespace iw4 diff --git a/src/game/iw4/mp/main.cpp b/src/game/iw4/mp/main.cpp index 58559ee..ab58613 100644 --- a/src/game/iw4/mp/main.cpp +++ b/src/game/iw4/mp/main.cpp @@ -5,6 +5,7 @@ #include "components/events.h" #include "components/g_client_fields.h" #include "components/g_scr_main.h" +#include "components/mpsp.h" #include "components/mr.h" #include "components/patches.h" #include "components/scr_parser.h" @@ -26,6 +27,7 @@ IW4_MP_Plugin::IW4_MP_Plugin() RegisterModule(new Console()); RegisterModule(new g_client_fields()); RegisterModule(new g_scr_main()); + RegisterModule(new mpsp()); RegisterModule(new MovementRecorder()); RegisterModule(new patches()); RegisterModule(new scr_parser()); diff --git a/src/game/iw4/mp/structs.h b/src/game/iw4/mp/structs.h index fe8b733..4f1335f 100644 --- a/src/game/iw4/mp/structs.h +++ b/src/game/iw4/mp/structs.h @@ -1216,6 +1216,46 @@ struct __declspec(align(64)) clipMap_t unsigned int checksum; }; +struct pathnode_t; +struct pathbasenode_t; +struct pathnode_tree_t; +struct VehicleTrackSegment; +struct G_GlassData; + +struct PathData +{ + unsigned int nodeCount; + pathnode_t *nodes; + pathbasenode_t *basenodes; + unsigned int chainNodeCount; + unsigned __int16 *chainNodeForNode; + unsigned __int16 *nodeForChainNode; + int visBytes; + unsigned __int8 *pathVis; + int nodeTreeCount; + pathnode_tree_t *nodeTree; +}; + +struct VehicleTrack +{ + VehicleTrackSegment *segments; + unsigned int segmentCount; +}; + +struct GameWorldSp +{ + const char *name; + PathData path; + VehicleTrack vehicleTrack; + G_GlassData *g_glassData; +}; + +struct GameWorldMp +{ + const char *name; + G_GlassData *g_glassData; +}; + struct RawFile { const char *name; @@ -1227,6 +1267,9 @@ struct RawFile union XAssetHeader { clipMap_t *clipMap; + GameWorldSp *gameWorldSp; + GameWorldMp *gameWorldMp; + MapEnts *mapEnts; RawFile *rawfile; void *data; }; From bd31ad3b68b1f6617a2821f17179fcf7deb32ec4 Mon Sep 17 00:00:00 2001 From: Michael Oliver Date: Wed, 17 Dec 2025 10:04:01 +0000 Subject: [PATCH 2/2] cleanup --- README.md | 54 +++++----- ...uty - Modern Warfare 2 MP (TU6).patch.toml | 18 ++-- src/game/iw4/mp/components/mpsp.cpp | 98 ++----------------- 3 files changed, 49 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index cb100ce..3103e95 100644 --- a/README.md +++ b/README.md @@ -72,30 +72,36 @@ NOTE: Xenia requires the [patch]() to increase memory installed. ## Features diff --git a/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml b/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml index ecfd593..95a421d 100644 --- a/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml +++ b/resources/xenia/patches/41560817 - Call of Duty - Modern Warfare 2 MP (TU6).patch.toml @@ -26,12 +26,16 @@ hash = "A3FFB7C11B688FE5" # default_mp.xex address = 0x823C3E36 value = 0x0000 # 2 -> 0 -# 0x1C50 +[[patch]] + name = "Increase memory allocation" + desc = "Patches PMem_Init to allocate additional memory." + author = "mo" + is_enabled = true -[[patch.be16]] -address = 0x823129FA -value = 0x1E00 + [[patch.be16]] + address = 0x823129FA + value = 0x1E00 -[[patch.be16]] -address = 0x82312A1E -value = 0x1E00 + [[patch.be16]] + address = 0x82312A1E + value = 0x1E00 diff --git a/src/game/iw4/mp/components/mpsp.cpp b/src/game/iw4/mp/components/mpsp.cpp index 4a0d1f9..db31b0d 100644 --- a/src/game/iw4/mp/components/mpsp.cpp +++ b/src/game/iw4/mp/components/mpsp.cpp @@ -369,78 +369,14 @@ void override_(RawFile *asset) } // namespace Asset -// "default", -// "void", -// "void", -// "fx10", -// "void", -// "$default", -// "null.hlsl", -// "default", -// "$white", -// "null", -// "default", -// "null", -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// "light_dynamic", -// &byte_82006A5F, -// "fonts/consolefont", -// "ui/default.menu", -// "default_menu", -// "CGAME_UNKNOWN", -// "defaultweapon_mp", -// &byte_82006A5F, -// "misc/missing_fx", -// "default", -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// &byte_82006A5F, -// "codescripts/$default", -// "mp/defaultStringTable.csv", -// "DEFAULT_LB", -// "mp/defaultStructuredData.def", -// "defaulttracer", -// "defaultvehicle", -// &byte_82006A5F - Detour DB_LinkXAssetEntry1_Detour; XAssetEntryPoolEntry *DB_LinkXAssetEntry1_Hook(XAssetType type, XAssetHeader *header) { - - static const char *last_fastfile = nullptr; - static int asset_counter = 0; - - const char *current_fastfile = g_load->file->name; - - // Reset counter when fastfile changes - if (last_fastfile == nullptr || strcmp(last_fastfile, current_fastfile) != 0) - { - last_fastfile = current_fastfile; - asset_counter = 0; - } - XAsset xasset; xasset.type = type; xasset.header = *header; - const int asset_type = type; - const char *asset_type_name = g_assetNames[asset_type]; const char *asset_name = DB_GetXAssetName(&xasset); - const char *zone_name = g_zones[*g_zoneIndex].file.name; - - DbgPrint("[DB_LinkXAssetEntry1] [%04d] %-24s | %-16s (%d) | %s\n", asset_counter, zone_name, asset_type_name, - asset_type, asset_name); - - ++asset_counter; - // DbgPrint("[DB_LinkXAssetEntry1] returnaddr %p\n", _ReturnAddress()); // Before linking switch (type) @@ -454,9 +390,9 @@ XAssetEntryPoolEntry *DB_LinkXAssetEntry1_Hook(XAssetType type, XAssetHeader *he break; } - // Ignore these assets to save on asset pool slots - // Hijacks the referenced asset mechanism so that DB_AllocXAssetEntry is never called - // Make some assumptions that these assets will not be used. + // Hijacks the referenced asset mechanism so that DB_AllocXAssetEntry + // is never called thus never increasing the asset pool for those assets. + // Make the assumption that these assets will not be used. if (!is_mp_fastfile(g_load->file->name)) { switch (type) @@ -580,42 +516,24 @@ void Patch_ImageCache_OOM() *(volatile uint32_t *)0x823DF628 = 0x480000A4; } -DWORD WINAPI ThreadProc(LPVOID param) -{ - - DbgPrint("Thread Started!\n"); - - Sleep(2000); - Cbuf_AddText(0, "meminfo\n"); - // Cbuf_AddText(0, "set developer 1\n"); - // Cbuf_AddText(0, "set developer_script 1\n"); - Cbuf_AddText(0, "set ui_mapname contingency\n"); - - // Sleep(3500); - - // Cbuf_AddText(0, "set loc_warnings 0\n"); // Disable unlocalized warnings - // Cbuf_AddText(0, "set ui_mapname trainer\n"); - - return 0; -} - mpsp::mpsp() { DisableFastfileAuthCheck(); - // Patch_ImageCache_OOM(); + Patch_ImageCache_OOM(); +// Debug-only: intercept internal console printing to also forward output to stdout +#ifndef NDEBUG CL_ConsolePrint_Detour = Detour(CL_ConsolePrint, CL_ConsolePrint_Hook); CL_ConsolePrint_Detour.Install(); +#endif + // Modify some assets before linking DB_LinkXAssetEntry1_Detour = Detour(DB_LinkXAssetEntry1, DB_LinkXAssetEntry1_Hook); DB_LinkXAssetEntry1_Detour.Install(); // Rewrite some strings on the fly Com_sprintf_Detour = Detour(Com_sprintf, Com_sprintf_Hook); Com_sprintf_Detour.Install(); - - // Start a thread to not block the main thread - ExCreateThread(nullptr, 0, nullptr, nullptr, &ThreadProc, nullptr, EX_CREATE_FLAG_TITLE_EXEC); } mpsp::~mpsp()