Skip to content

Commit 720b8ec

Browse files
ImAciidzThisAMJ
authored andcommitted
feat: support for 2011 Portal 2
Minimum effort to make things work on the April 27th, 2011 build of the game. LP hud is broken (I'm guessing the field for the Portal count was added with the CM update?). TAS tools won't work due to SteamControllerMove not existing at all, there's also a crash around line 384 or so of TasPlayer.cpp regardless when trying to play a TAS that I didn't care enough to figure out. SAR's strategy for injecting data into demos won't work either, as this version of the game doesn't save the radial menu cursor position to the demo. Otherwise, I was able to play a full AMC run with bill, record/play demos, render a demo, and do other things without issues. I cannot totally verify the integrity of the game is intact (when it comes to potentially incorrect vtable offsets or the like causing wrong functions to be hooked), but I will leave that to someone more familiar with the codebase/features to look into. Anyways, this was a fun way to spend like 5 entire afternoons/evenings.
1 parent e103239 commit 720b8ec

File tree

16 files changed

+377
-23
lines changed

16 files changed

+377
-23
lines changed

src/Features/ConfigPlus.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ static const char *gameName() {
395395
case SourceGame_ThinkingWithTimeMachine: return "twtm";
396396
case SourceGame_PortalReloaded: return "reloaded";
397397
case SourceGame_Portal2: return Game::IsSpeedrunMod() ? "srm" : "portal2";
398+
case SourceGame_Portal2_2011: return "portal2";
398399
case SourceGame_INFRA: return "infra";
399400
case SourceGame_BeginnersGuide: return "guide";
400401
case SourceGame_StanleyParable: return "stanley";

src/Features/Hud/CheatWarn.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static Cheat g_cheats[] = {
5858

5959
{ +[]() {
6060
auto map = engine->GetCurrentMapName();
61-
if (!sar.game->Is(SourceGame_Portal2)) return false;
61+
if (!sar.game->Is(SourceGame_Portal2 | SourceGame_Portal2_2011)) return false;
6262
if (map == "sp_a2_bts5") return false;
6363
if (engine->GetMapIndex(map) == -1) return false;
6464
return sv_allow_mobile_portals.GetBool();

src/Features/Hud/LPHud.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,20 @@ static int getCurrentCount() {
3939
// Get the current total portal count as reported by the the players'
4040
// m_StatsThisLevel values
4141
static int getStatsCount() {
42-
int total = 0;
42+
if (!sar.game->Is(SourceGame_Portal2_2011)) {
43+
int total = 0;
44+
45+
int slots = engine->GetMaxClients() >= 2 ? 2 : 1;
46+
for (int slot = 0; slot < slots; ++slot) {
47+
ClientEnt *player = client->GetPlayer(slot + 1);
48+
if (!player) continue;
49+
total += player->field<int>("iNumPortalsPlaced");
50+
}
4351

44-
int slots = engine->GetMaxClients() >= 2 ? 2 : 1;
45-
for (int slot = 0; slot < slots; ++slot) {
46-
ClientEnt *player = client->GetPlayer(slot + 1);
47-
if (!player) continue;
48-
total += player->field<int>("iNumPortalsPlaced");
52+
return total;
53+
} else {
54+
return 0;
4955
}
50-
51-
return total;
5256
}
5357

5458
// Calculate an up-to-date total portal count using the player stats and

src/Features/Speedrun/SpeedrunTimer.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ ON_EVENT(SESSION_START) {
214214
if (!sar_speedrun_skip_cutscenes.GetBool()) return;
215215
if (engine->IsOrange()) return;
216216
switch (sar.game->GetVersion()) {
217+
case SourceGame_Portal2_2011:
217218
case SourceGame_Portal2:
218219
if (Game::IsSpeedrunMod()) return;
219220

@@ -335,7 +336,7 @@ int SpeedrunTimer::GetSegmentTicks() {
335336
int ticks = 0;
336337
ticks += g_speedrun.saved;
337338
if (!g_speedrun.isPaused && !g_speedrun.inCoopPause && (!engine->demoplayer->IsPlaying() || engine->demoplayer->IsPlaybackFixReady())) {
338-
if (sar_speedrun_skip_cutscenes.GetBool() && sar.game->GetVersion() == SourceGame_Portal2 && !Game::IsSpeedrunMod()) {
339+
if (sar_speedrun_skip_cutscenes.GetBool() && sar.game->Is(SourceGame_Portal2 | SourceGame_Portal2_2011) && !Game::IsSpeedrunMod()) {
339340
if (g_speedrun.lastMap == "sp_a2_bts6") return 3112;
340341
else if (g_speedrun.lastMap == "sp_a3_00") return 4666;
341342
}

src/Game.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
#include "Command.hpp"
44
#include "Modules/Engine.hpp"
55
#include "Utils.hpp"
6+
#include "Interface.hpp"
67

78
#include <cstring>
89
#include <string>
910

1011
#include "Games/Portal2.hpp"
12+
#include "Games/Portal2_2011.hpp"
1113
#include "Games/ApertureTag.hpp"
1214
#include "Games/PortalStoriesMel.hpp"
1315
#include "Games/PortalReloaded.hpp"
@@ -76,7 +78,13 @@ Game *Game::CreateNew() {
7678
modDir = GetModDir(TARGET_MOD);
7779

7880
if (Utils::ICompare(modDir, Portal2::ModDir())) {
79-
return new Portal2();
81+
82+
void *engineClient = Interface::GetPtr(MODULE("engine"), "VEngineClient015", false);
83+
if (engineClient) {
84+
return new Portal2();
85+
} else {
86+
return new Portal2_2011();
87+
}
8088
}
8189

8290
if (Utils::ICompare(modDir, PortalStoriesMel::ModDir())) {
@@ -105,6 +113,7 @@ std::string Game::VersionToString(int version) {
105113
auto games = std::string("");
106114
while (version > 0) {
107115
HAS_GAME_FLAG(SourceGame_Portal2, "Portal 2")
116+
HAS_GAME_FLAG(SourceGame_Portal2_2011, "Portal 2")
108117
HAS_GAME_FLAG(SourceGame_ApertureTag, "Aperture Tag")
109118
HAS_GAME_FLAG(SourceGame_PortalStoriesMel, "Portal Stories: Mel")
110119
HAS_GAME_FLAG(SourceGame_ThinkingWithTimeMachine, "Thinking with Time Machine")

src/Game.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ enum SourceGameVersion {
66
SourceGame_Unknown = 0,
77

88
SourceGame_Portal2 = (1 << 0),
9+
SourceGame_Portal2_2011 = (1 << 1),
910
SourceGame_ApertureTag = (1 << 5),
1011
SourceGame_PortalStoriesMel = (1 << 6),
1112
SourceGame_ThinkingWithTimeMachine = (1 << 7),

src/Games/Portal2_2011.cpp

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#include "Portal2_2011.hpp"
2+
3+
#include "Game.hpp"
4+
#include "Offsets.hpp"
5+
6+
Portal2_2011::Portal2_2011() {
7+
this->version = SourceGame_Portal2_2011;
8+
Game::maps = {
9+
{"sp_a1_intro1", "Container Ride", "62761"},
10+
{"sp_a1_intro2", "Portal Carousel", "62758"},
11+
{"sp_a1_intro3", "Portal Gun", "47458"},
12+
{"sp_a1_intro4", "Smooth Jazz", "47455"},
13+
{"sp_a1_intro5", "Cube Momentum", "47452"},
14+
{"sp_a1_intro6", "Future Starter", "47106"},
15+
{"sp_a1_intro7", "Secret Panel", "62763"},
16+
{"sp_a1_wakeup", "Wakeup", "62759"},
17+
{"sp_a2_intro", "Incinerator", "47735"},
18+
{"sp_a2_laser_intro", "Laser Intro", "62765"},
19+
{"sp_a2_laser_stairs", "Laser Stairs", "47736"},
20+
{"sp_a2_dual_lasers", "Dual Lasers", "47738"},
21+
{"sp_a2_laser_over_goo", "Laser Over Goo", "47742"},
22+
{"sp_a2_catapult_intro", "Catapult Intro", "62767"},
23+
{"sp_a2_trust_fling", "Trust Fling", "47744"},
24+
{"sp_a2_pit_flings", "Pit Flings", "47465"},
25+
{"sp_a2_fizzler_intro", "Fizzler Intro", "47746"},
26+
{"sp_a2_sphere_peek", "Ceiling Catapult", "47748"},
27+
{"sp_a2_ricochet", "Ricochet", "47751"},
28+
{"sp_a2_bridge_intro", "Bridge Intro", "47752"},
29+
{"sp_a2_bridge_the_gap", "Bridge The Gap", "47755"},
30+
{"sp_a2_turret_intro", "Turret Intro", "47756"},
31+
{"sp_a2_laser_relays", "Laser Relays", "47759"},
32+
{"sp_a2_turret_blocker", "Turret Blocker", "47760"},
33+
{"sp_a2_laser_vs_turret", "Laser vs Turret", "47763"},
34+
{"sp_a2_pull_the_rug", "Pull the Rug", "47764"},
35+
{"sp_a2_column_blocker", "Column Blocker", "47766"},
36+
{"sp_a2_laser_chaining", "Laser Chaining", "47768"},
37+
{"sp_a2_triple_laser", "Triple Laser", "47770"},
38+
{"sp_a2_bts1", "Jailbreak", "47773"},
39+
{"sp_a2_bts2", "Escape", "47774"},
40+
{"sp_a2_bts3", "Turret Factory", "47776"},
41+
{"sp_a2_bts4", "Turret Sabotage", "47779"},
42+
{"sp_a2_bts5", "Neurotoxin Sabotage", "47780"},
43+
{"sp_a2_bts6", "Tube Ride", ""},
44+
{"sp_a2_core", "Core", "62771"},
45+
{"sp_a3_00", "Long Fall", ""},
46+
{"sp_a3_01", "Underground", "47783"},
47+
{"sp_a3_03", "Cave Johnson", "47784"},
48+
{"sp_a3_jump_intro", "Repulsion Intro", "47787"},
49+
{"sp_a3_bomb_flings", "Bomb Flings", "47468"},
50+
{"sp_a3_crazy_box", "Crazy Box", "47469"},
51+
{"sp_a3_transition01", "PotatOS", "47472"},
52+
{"sp_a3_speed_ramp", "Propulsion Intro", "47791"},
53+
{"sp_a3_speed_flings", "Propulsion Flings", "47793"},
54+
{"sp_a3_portal_intro", "Conversion Intro", "47795"},
55+
{"sp_a3_end", "Three Gels", "47798"},
56+
{"sp_a4_intro", "Test", "88350"},
57+
{"sp_a4_tb_intro", "Funnel Intro", "47800"},
58+
{"sp_a4_tb_trust_drop", "Ceiling Button", "47802"},
59+
{"sp_a4_tb_wall_button", "Wall Button", "47804"},
60+
{"sp_a4_tb_polarity", "Polarity", "47806"},
61+
{"sp_a4_tb_catch", "Funnel Catch", "47808"},
62+
{"sp_a4_stop_the_box", "Stop the Box", "47811"},
63+
{"sp_a4_laser_catapult", "Laser Catapult", "47813"},
64+
{"sp_a4_laser_platform", "Laser Platform", "47815"},
65+
{"sp_a4_speed_tb_catch", "Propulsion Catch", "47817"},
66+
{"sp_a4_jump_polarity", "Repulsion Polarity", "47819"},
67+
{"sp_a4_finale1", "Finale 1", "62776"},
68+
{"sp_a4_finale2", "Finale 2", "47821"},
69+
{"sp_a4_finale3", "Finale 3", "47824"},
70+
{"sp_a4_finale4", "Finale 4", "47456"},
71+
{"mp_coop_start", "Calibration", ""},
72+
{"mp_coop_lobby_2", "Lobby (pre-DLC)", ""},
73+
{"mp_coop_doors", "Doors", "47741"},
74+
{"mp_coop_race_2", "Buttons", "47825"},
75+
{"mp_coop_laser_2", "Lasers", "47828"},
76+
{"mp_coop_rat_maze", "Rat Maze", "47829"},
77+
{"mp_coop_laser_crusher", "Laser Crusher", "45467"},
78+
{"mp_coop_teambts", "Behind the Scenes", "46362"},
79+
{"mp_coop_fling_3", "Flings", "47831"},
80+
{"mp_coop_infinifling_train", "Infinifling", "47833"},
81+
{"mp_coop_come_along", "Team Retrieval", "47835"},
82+
{"mp_coop_fling_1", "Vertical Flings", "47837"},
83+
{"mp_coop_catapult_1", "Catapults", "47840"},
84+
{"mp_coop_multifling_1", "Multifling", "47841"},
85+
{"mp_coop_fling_crushers", "Fling Crushers", "47844"},
86+
{"mp_coop_fan", "Industrial Fan", "47845"},
87+
{"mp_coop_wall_intro", "Cooperative Bridges", "47848"},
88+
{"mp_coop_wall_2", "Bridge Swap", "47849"},
89+
{"mp_coop_catapult_wall_intro", "Fling Block", "47854"},
90+
{"mp_coop_wall_block", "Catapult Block", "47856"},
91+
{"mp_coop_catapult_2", "Bridge Fling", "47858"},
92+
{"mp_coop_turret_walls", "Turret Walls", "47861"},
93+
{"mp_coop_turret_ball", "Turret Assassin", "52642"},
94+
{"mp_coop_wall_5", "Bridge Testing", "52660"},
95+
{"mp_coop_tbeam_redirect", "Cooperative Funnels", "52662"},
96+
{"mp_coop_tbeam_drill", "Funnel Drill", "52663"},
97+
{"mp_coop_tbeam_catch_grind_1", "Funnel Catch Coop", "52665"},
98+
{"mp_coop_tbeam_laser_1", "Funnel Laser", "52667"},
99+
{"mp_coop_tbeam_polarity", "Cooperative Polarity", "52671"},
100+
{"mp_coop_tbeam_polarity2", "Funnel Hop", "52687"},
101+
{"mp_coop_tbeam_polarity3", "Advanced Polarity", "52689"},
102+
{"mp_coop_tbeam_maze", "Funnel Maze", "52691"},
103+
{"mp_coop_tbeam_end", "Turret Warehouse", "52777"},
104+
{"mp_coop_paint_come_along", "Repulsion Jumps", "52694"},
105+
{"mp_coop_paint_redirect", "Double Bounce", "52711"},
106+
{"mp_coop_paint_bridge", "Bridge Repulsion", "52714"},
107+
{"mp_coop_paint_walljumps", "Wall Repulsion", "52715"},
108+
{"mp_coop_paint_speed_fling", "Propulsion Crushers", "52717"},
109+
{"mp_coop_paint_red_racer", "Turret Ninja", "52735"},
110+
{"mp_coop_paint_speed_catch", "Propulsion Retrieval", "52738"},
111+
{"mp_coop_paint_longjump_intro", "Vault Entrance", "52740"},
112+
};
113+
114+
Game::mapNames = {};
115+
for (const auto &map : Game::maps) {
116+
Game::mapNames.push_back(map.fileName);
117+
}
118+
119+
Game::achievements = {
120+
{"ACH.SURVIVE_CONTAINER_RIDE", "Wake Up Call", false},
121+
{"ACH.WAKE_UP", "You Monster", false},
122+
{"ACH.LASER", "Undiscouraged", false},
123+
{"ACH.BRIDGE", "Bridge Over Troubling Water", false},
124+
{"ACH.BREAK_OUT", "SaBOTour", false},
125+
{"ACH.STALEMATE_ASSOCIATE", "Stalemate Associate", false},
126+
{"ACH.ADDICTED_TO_SPUDS", "Tater Tote", false},
127+
{"ACH.BLUE_GEL", "Vertically Unchallenged", false},
128+
{"ACH.ORANGE_GEL", "Stranger Than Friction", false},
129+
{"ACH.WHITE_GEL", "White Out", false},
130+
{"ACH.TRACTOR_BEAM", "Tunnel of Funnel", false},
131+
{"ACH.TRIVIAL_TEST", "Dual Pit Experiment", false},
132+
{"ACH.WHEATLEY_TRIES_TO", "The Part Where He Kills You", false},
133+
{"ACH.SHOOT_THE_MOON", "Lunacy", false},
134+
{"ACH.BOX_HOLE_IN_ONE", "Drop Box", false},
135+
{"ACH.SPEED_RUN_LEVEL", "Overclocker", false},
136+
{"ACH.COMPLIANT", "Pit Boss", false},
137+
{"ACH.SAVE_CUBE", "Preservation of Mass", false},
138+
{"ACH.LAUNCH_TURRET", "Pturretdactyl", false},
139+
{"ACH.CLEAN_UP", "Final Transmission", false},
140+
{"ACH.REENTER_TEST_CHAMBERS", "Good Listener", false},
141+
{"ACH.NOT_THE_DROID", "Scanned Alone", false},
142+
{"ACH.SAVE_REDEMPTION_TURRET", "No Hard Feelings", false},
143+
{"ACH.CATCH_CRAZY_BOX", "Schrodinger's Catch", false},
144+
{"ACH.NO_BOAT", "Ship Overboard", false},
145+
{"ACH.A3_DOORS", "Door Prize", false},
146+
{"ACH.PORTRAIT", "Portrait of a Lady", false},
147+
{"ACH.DEFIANT", "You Made Your Point", false},
148+
{"ACH.BREAK_MONITORS", "Smash TV", false},
149+
{"ACH.HI_FIVE_YOUR_PARTNER", "High Five", true},
150+
{"ACH.TEAM_BUILDING", "Team Building", true},
151+
{"ACH.MASS_AND_VELOCITY", "Confidence Building", true},
152+
{"ACH.HUG_NAME", "Bridge Building", true},
153+
{"ACH.EXCURSION_FUNNELS", "Obstacle Building", true},
154+
{"ACH.NEW_BLOOD", "You Saved Science", true},
155+
{"ACH.NICE_CATCH", "Iron Grip", true},
156+
{"ACH.TAUNTS", "Gesticul-8", true},
157+
{"ACH.YOU_MONSTER", "Can't Touch This", true},
158+
{"ACH.PARTNER_DROP", "Empty Gesture", true},
159+
{"ACH.PARTY_OF_THREE", "Party of Three", true},
160+
{"ACH.PORTAL_TAUNT", "Narbacular Drop", true},
161+
{"ACH.TEACHER", "Professor Portal", true},
162+
{"ACH.WITH_STYLE", "Air Show", true},
163+
{"ACH.LIMITED_PORTALS", "Portal Conservation Society", true},
164+
{"ACH.FOUR_PORTALS", "Four Ring Circus", true},
165+
{"ACH.SPEED_RUN_COOP", "Triple Crown", true},
166+
{"ACH.STAYING_ALIVE", "Still Alive", true},
167+
{"ACH.TAUNT_CAMERA", "Asking for Trouble", true},
168+
{"ACH.ROCK_CRUSHES_ROBOT", "Rock Portal Scissors", true},
169+
{"ACH.SPREAD_THE_LOVE", "Friends List With Benefits", true}
170+
};
171+
}
172+
void Portal2_2011::LoadOffsets() {
173+
using namespace Offsets;
174+
175+
#include "Offsets/Portal 2 4554.hpp"
176+
}
177+
const char *Portal2_2011::Version() {
178+
return "Portal 2 4554";
179+
}
180+
const float Portal2_2011::Tickrate() {
181+
return 60;
182+
}
183+
const char *Portal2_2011::ModDir() {
184+
return "portal2";
185+
}

src/Games/Portal2_2011.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
#include "Game.hpp"
3+
4+
class Portal2_2011 : public Game {
5+
public:
6+
Portal2_2011();
7+
void LoadOffsets() override;
8+
const char *Version() override;
9+
const float Tickrate() override;
10+
11+
static const char *ModDir();
12+
};

src/Interface.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void Interface::Delete(Interface *ptr) {
7373
ptr = nullptr;
7474
}
7575
}
76-
void *Interface::GetPtr(const char *filename, const char *interfaceSymbol) {
76+
void *Interface::GetPtr(const char *filename, const char *interfaceSymbol, bool shouldTryLowerVersion) {
7777
auto handle = Memory::GetModuleHandleByName(filename);
7878
if (!handle) {
7979
console->DevWarning("SAR: Failed to open module %s!\n", filename);
@@ -92,8 +92,17 @@ void *Interface::GetPtr(const char *filename, const char *interfaceSymbol) {
9292
void *fn = CreateInterface(interfaceSymbol, &ret);
9393

9494
if (ret) {
95-
auto CreateInterfaceInternal = Memory::Read((uintptr_t)CreateInterface + Offsets::CreateInterfaceInternal);
96-
auto s_pInterfaceRegs = Memory::DerefDeref<InterfaceReg *>(CreateInterfaceInternal + Offsets::s_pInterfaceRegs);
95+
uintptr_t CreateInterfaceInternal;
96+
InterfaceReg *s_pInterfaceRegs;
97+
// hacky: if offsets aren't initialized then just hardcode the offsets
98+
// (this is currently only for differentiating 2011/latest p2)
99+
if (Offsets::CreateInterfaceInternal && Offsets::s_pInterfaceRegs) {
100+
CreateInterfaceInternal = Memory::Read((uintptr_t)CreateInterface + Offsets::CreateInterfaceInternal);
101+
s_pInterfaceRegs = Memory::DerefDeref<InterfaceReg *>(CreateInterfaceInternal + Offsets::s_pInterfaceRegs);
102+
} else {
103+
CreateInterfaceInternal = Memory::Read((uintptr_t)CreateInterface + 5);
104+
s_pInterfaceRegs = Memory::DerefDeref<InterfaceReg *>(CreateInterfaceInternal + 6);
105+
}
97106

98107
if (!CreateInterfaceInternal || !s_pInterfaceRegs) {
99108
console->DevWarning("SAR: Failed to find interface with symbol %s in %s! (old method failed)\n", interfaceSymbol, filename);
@@ -108,7 +117,7 @@ void *Interface::GetPtr(const char *filename, const char *interfaceSymbol) {
108117
}
109118
}
110119

111-
if (!fn) {
120+
if (!fn && shouldTryLowerVersion) {
112121
// Interface012 -> Interface0
113122
// Try to get any version of the interface
114123
if (interfaceSymbol[std::strlen(interfaceSymbol) - 3] >= '0' && interfaceSymbol[std::strlen(interfaceSymbol) - 3] <= '9') {

src/Interface.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class Interface {
6161
static Interface *Create(void *ptr, bool copyVtable = true, bool autoHook = true);
6262
static Interface *Create(const char *filename, const char *interfaceSymbol, bool copyVtable = true, bool autoHook = true);
6363
static void Delete(Interface *ptr);
64-
static void *GetPtr(const char *filename, const char *interfaceSymbol);
64+
static void *GetPtr(const char *filename, const char *interfaceSymbol, bool shouldTryLowerVersion = true);
6565

6666
template <typename T = void *>
6767
static T Get(const char *filename, const char *interfaceSymbol) {

0 commit comments

Comments
 (0)