Skip to content

Commit

Permalink
MP Force Resync button (#10281)
Browse files Browse the repository at this point in the history
Also fixes #10439
  • Loading branch information
seroperson committed Nov 18, 2023
1 parent 3eeecc7 commit 91e6f0a
Show file tree
Hide file tree
Showing 16 changed files with 194 additions and 15 deletions.
3 changes: 3 additions & 0 deletions (1) Community Patch/Core Files/Core Changes/CoreChanges.sql
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ UPDATE CustomModOptions SET Value = 0 WHERE Name = 'ALTERNATE_ASSYRIA_TRAIT';
-- Activates Active Diplomacy in DLL for Multiplayer trade deals between Human and AI
UPDATE CustomModOptions SET Value = 1 WHERE Name = 'ACTIVE_DIPLOMACY';

-- Activates functions which depend on working with closed .exe code
UPDATE CustomModOptions SET Value = 1 WHERE Name = 'EXE_HACKING';

-- For some reason the Firaxis DLL hardcoded "City-State Territory = Friendly Territory" to Greece's CityStateFriendshipModifier bonus, despite having a separate, functional trait column for this ability
-- The Community Patch unhardcodes it and assigns the ability to Greece for identical functionality
UPDATE Traits
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@
<Row Class="4" Name="PILLAGE_PERMANENT_IMPROVEMENTS" Value="0"/>
<!-- This adds the ability for AI players to contact human players in multiplayer with trade deals -->
<Row Class="5" Name="ACTIVE_DIPLOMACY" Value="0"/>
<!-- Activates functions which depend on working with closed .exe code -->
<Row Class="5" Name="EXE_HACKING" Value="0"/>
<!-- Grants Celts maximum up to 3 unimproved adjacent forests for faith -->
<Row Class="4" Name="ALTERNATE_CELTS" Value="0"/>
<!-- Restricts a Team from passing into the next era before they have found all techs of their current era -->
Expand Down
10 changes: 10 additions & 0 deletions (1) Community Patch/Core Files/Text/CoreText_en_US.xml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@
<Text>{1_Num} extra {2_IconString} from [ICON_CITIZEN] Population in Empire</Text>
</Row>

<!-- Tooltip for Force Resync button -->
<Row Tag="TXT_KEY_MP_FORCE_RESYNC_TITLE">
<Text>Schedule a force resync</Text>
</Row>

<!-- Host sends this chat message when scheduled force resync -->
<Row Tag="TXT_KEY_VP_MP_WARNING_RESYNC_SCHEDULED">
<Text>I scheduled a force resync. Next turn the session will be reloaded.</Text>
</Row>

<!-- Multiplayer Desync Warning -->
<Row Tag="TXT_KEY_VP_MP_WARNING_DESYNC">
<Text>My client has desynced, undefined behavior may occur. Please open a bug report on GitHub: https://github.com/LoneGazebo/Community-Patch-DLL/issues</Text>
Expand Down
Binary file added (3a) EUI Compatibility Files/EUI/DC45_Generic.dds
Binary file not shown.
13 changes: 13 additions & 0 deletions (3a) EUI Compatibility Files/LUA/DiploCorner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,19 @@ if( Game.IsGameMultiPlayer() ) then
end
end

function OnForceResyncButton()
Game.SetExeWantForceResyncValue(1);
Controls.ForceResyncButton:SetDisabled(true);
end
Controls.ForceResyncButton:RegisterCallback(Mouse.eLClick, OnForceResyncButton);

function UpdateForceResyncButton()
local bShowForceResync = Game.IsExeWantForceResyncAvailable();
Controls.ForceResyncButton:SetHide( not bShowForceResync );
Controls.ForceResyncButton:SetDisabled(false);
end
GameEvents.PlayerDoTurn.Add(UpdateForceResyncButton);
Events.MultiplayerGamePlayerUpdated.Add(UpdateForceResyncButton);

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions (3a) EUI Compatibility Files/LUA/DiploCorner.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
<Box Anchor="R,T" Size="512,128" Color="0,0,0,0">
<!-- Info Buttons Stack -->
<Stack Anchor="R,T" StackGrowth="Left" ID="DiploCornerStack" Padding="0" >
<Button ID="ForceResyncButton" ConsumeMouse="1" Anchor="C,T" Size="45,45" Texture="DC45_Generic.dds" ToolTip="TXT_KEY_MP_FORCE_RESYNC_TITLE" Hidden="1" >
<Label Anchor="C,C" Offset="0,4" Font="TwCenMT22" FontStyle="Stroke" ID="ForceResyncLabel" Hidden="1"/>
</Button>
<!--=======================================================================================================================-->
<!-- Diplo Button -->
<!--=======================================================================================================================-->
Expand Down
8 changes: 8 additions & 0 deletions CvGameCoreDLLUtil/include/CvEnums.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ enum CLOSED_ENUM eCiv5GameCoreMPoolType
c_eCiv5GameplayDLL = c_eMPoolTypeUserStart + 0x100, // Adding 0x100 because we don't want to collide with the app's IDs
};

enum CLOSED_ENUM CvBinType
{
BIN_UNKNOWN = -1,
BIN_TABLET,
BIN_DX11,
BIN_DX9
};

enum CLOSED_ENUM ArmyType
{
ARMY_TYPE_ANY = -1,
Expand Down
1 change: 1 addition & 0 deletions CvGameCoreDLL_Expansion2/CustomMods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ int CustomMods::getOption(const string& sOption, int defValue) {
MOD_OPT_CACHE(EVENTS_RED_COMBAT_RESULT);
MOD_OPT_CACHE(EVENTS_RED_COMBAT_ENDED);
MOD_OPT_CACHE(ACTIVE_DIPLOMACY);
MOD_OPT_CACHE(EXE_HACKING);
MOD_OPT_CACHE(API_ACHIEVEMENTS);

MOD_OPT_CACHE(ISKA_HERITAGE);
Expand Down
4 changes: 4 additions & 0 deletions CvGameCoreDLL_Expansion2/CustomMods.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
//END MULTIPLAYER INSTRUCTIONS
////////////////////////////////////////

// Activates functions which depend on working with closed .exe code
#define MOD_EXE_HACKING gCustomMods.isEXE_HACKING()

// Enables not showing, during pregame, the details of the civs that have not been met by the local player, i.e. as it doesn't during the game
#define MOD_KEEP_CIVS_UNKNOWN_PREGAME (true)

Expand Down Expand Up @@ -1531,6 +1534,7 @@ class CustomMods {
MOD_OPT_DECL(EVENTS_RED_COMBAT_RESULT);
MOD_OPT_DECL(EVENTS_RED_COMBAT_ENDED);
MOD_OPT_DECL(ACTIVE_DIPLOMACY);
MOD_OPT_DECL(EXE_HACKING);
MOD_OPT_DECL(API_ACHIEVEMENTS);

MOD_OPT_DECL(ISKA_HERITAGE);
Expand Down
89 changes: 88 additions & 1 deletion CvGameCoreDLL_Expansion2/CvDllGame.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* -------------------------------------------------------------------------------------------------------
© 1991-2012 Take-Two Interactive Software and its subsidiaries. Developed by Firaxis Games.
© 1991-2012 Take-Two Interactive Software and its subsidiaries. Developed by Firaxis Games.
Sid Meier's Civilization V, Civ, Civilization, 2K Games, Firaxis Games, Take-Two Interactive Software
and their respective logos are all trademarks of Take-Two interactive Software, Inc.
All other marks and trademarks are the property of their respective owners.
Expand Down Expand Up @@ -143,6 +143,10 @@ void CvDllGame::CycleUnits(bool bClear, bool bForward, bool bWorkers)
void CvDllGame::DoGameStarted()
{
m_pGame->DoGameStarted();

if (MOD_EXE_HACKING) {
InitExeStuff();
}
}
//------------------------------------------------------------------------------
void CvDllGame::EndTurnTimerReset()
Expand Down Expand Up @@ -581,4 +585,87 @@ bool CvDllGame::GetGreatWorkAudio(int GreatWorkIndex, char* strSound, int length
void CvDllGame::SetLastTurnAICivsProcessed()
{
m_pGame->SetLastTurnAICivsProcessed();
}
//------------------------------------------------------------------------------
bool endsWith(const char* str, const char* ending) {
size_t str_len = strlen(str), ending_len = strlen(ending);
return str_len >= ending_len && !strcmp(str + str_len - ending_len, ending);
}
void CvDllGame::InitExeStuff() {
// Here we are going to do some hacking on .exe code
//
// civ5 exes are CEG-protected (some kind of steam out-of-box protection).
// It disallows you to patch .exe directly, but still we are able to patch it in runtime
// and do everything we want (I'm not sure, but it looks like so).
//
// Reversing .exe is pretty simple because .exe files are not obfuscated in any way,
// so you can just load them into ghidra/ida/etc, find the addresses you need and then
// patch them here.
// (!) Moreover, civ5 linux executable is compiled with all the namings preserved,
// so things become even more easier because everything what you need is:
// - Find address in linux executable.
// - Map it somehow to .exe address (orient by string constants,
// similar code instructions and so on).
// - Patch it here.
//
// todo: support dx9, tablet (?) binaries
// todo: some modern way of patching
// todo: code caves
CvBinType binType;

char moduleName[1024];
if (!GetModuleFileName(NULL, moduleName, sizeof(moduleName))) {
// todo: log error (GetLastError)
binType = BIN_UNKNOWN;
}
else {
if (endsWith(moduleName, "CivilizationV.exe"))
binType = BIN_DX9;
else if (endsWith(moduleName, "CivilizationV_DX11.exe"))
binType = BIN_DX11;
else if (endsWith(moduleName, "CivilizationV_Tablet.exe"))
binType = BIN_TABLET;
else {
// todo: log moduleName
binType = BIN_UNKNOWN;
}
}

m_pGame->SetExeBinType(binType);

if (binType == BIN_DX11) {
DWORD baseAddr = (DWORD) GetModuleHandleA(0);
DWORD headersOffset = 0x400000;
DWORD totalOffset = baseAddr - headersOffset;

int* s_wantForceResync = reinterpret_cast<int*>(0x02dd2f68 + totalOffset);

m_pGame->SetExeWantForceResyncPointer(s_wantForceResync);
}

/*{
// the very basic example of how to fill something with NOPs
DWORD old_protect;
DWORD hookLocation = 0x51e031;
DWORD hookResultAddress = hookLocation + totalOffset;
if (VirtualProtect((void*)(hookResultAddress), 16, PAGE_EXECUTE_READWRITE, &old_protect)) {
*(unsigned char*)(hookResultAddress) = 0x90;
*(unsigned char*)(hookResultAddress + 1) = 0x90;
*(unsigned char*)(hookResultAddress + 2) = 0x90;
*(unsigned char*)(hookResultAddress + 3) = 0x90;
*(unsigned char*)(hookResultAddress + 4) = 0x90;
*(unsigned char*)(hookResultAddress + 5) = 0x90;
*(unsigned char*)(hookResultAddress + 6) = 0x90;
*(unsigned char*)(hookResultAddress + 7) = 0x90;
*(unsigned char*)(hookResultAddress + 8) = 0x90;
*(unsigned char*)(hookResultAddress + 9) = 0x90;
*(unsigned char*)(hookResultAddress + 10) = 0x90;
*(unsigned char*)(hookResultAddress + 11) = 0x90;
*(unsigned char*)(hookResultAddress + 12) = 0x90;
*(unsigned char*)(hookResultAddress + 13) = 0x90;
*(unsigned char*)(hookResultAddress + 14) = 0x90;
*(unsigned char*)(hookResultAddress + 15) = 0x90;
VirtualProtect((void*)(hookResultAddress), 16, old_protect, &old_protect);
}
}*/
}
2 changes: 2 additions & 0 deletions CvGameCoreDLL_Expansion2/CvDllGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class CvDllGame : public ICvGame2
TeamTypes DLLCALL GetActiveTeam();
int DLLCALL GetGameTurn() const;

void InitExeStuff();

void DLLCALL ChangeNumGameTurnActive(int iChange, const char* why);
int DLLCALL CountHumanPlayersAlive() const;
int DLLCALL CountNumHumanGameTurnActive();
Expand Down
30 changes: 30 additions & 0 deletions CvGameCoreDLL_Expansion2/CvGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14914,3 +14914,33 @@ bool CvGame::isFirstActivationOfPlayersAfterLoad() const
{
return m_firstActivationOfPlayersAfterLoad;
}

// --------------------------------------------------------------------------------
// exe things

void CvGame::SetExeBinType(CvBinType eBinType)
{
m_eExeBinType = eBinType;
}
CvBinType CvGame::GetExeBinType() const
{
return m_eExeBinType;
}
bool CvGame::IsExeWantForceResyncAvailable()
{
return MOD_EXE_HACKING && m_eExeBinType == BIN_DX11 && isNetworkMultiPlayer() && gDLL->IsHost();
}
void CvGame::SetExeWantForceResyncValue(int value)
{
if (IsExeWantForceResyncAvailable()) {
*(int*)(s_iExeWantForceResync) = value;
if (value == 1) {
CvString strWarningText = GetLocalizedText("TXT_KEY_VP_MP_WARNING_RESYNC_SCHEDULED");
GC.getDLLIFace()->sendChat(strWarningText, CHATTARGET_ALL, NO_PLAYER);
}
}
}
void CvGame::SetExeWantForceResyncPointer(int* pointer)
{
s_iExeWantForceResync = pointer;
}
12 changes: 12 additions & 0 deletions CvGameCoreDLL_Expansion2/CvGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,21 @@ class CvGame

void NewCapitalFounded(int iPlotFoundValue);
int GetCityQualityReference() const;

// exe things
void SetExeBinType(CvBinType eBinType);
CvBinType GetExeBinType() const;

bool IsExeWantForceResyncAvailable();
void SetExeWantForceResyncValue(int value);
void SetExeWantForceResyncPointer(int* pointer);

protected:

// exe things
CvBinType m_eExeBinType;
int* s_iExeWantForceResync;

bool m_firstActivationOfPlayersAfterLoad;

//for MP RNG we split this into two parts - everybody agrees on the previous turn but for the current turn races are possible
Expand Down
14 changes: 1 addition & 13 deletions CvGameCoreDLL_Expansion2/CvPreGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -848,8 +848,8 @@ void closeInactiveSlots()
setSlotStatus(eID, SS_CLOSED);
}
setSlotClaim(eID, SLOTCLAIM_UNASSIGNED);
gDLL->sendPlayerInfo(eID);
}
gDLL->sendPlayerInfo(eID);
}
gDLL->EndSendBundle();
}
Expand Down Expand Up @@ -2881,18 +2881,6 @@ void onGameStarted()
iCounter++;
}
}

// Seventh loop: if we're the host in a multiplayer game, we need to communicate our changes to the other players.
// It is unknown if this code actually works.
if (gDLL->IsHost() && vPlayersChanged.size() > 0)
{
gDLL->BeginSendBundle();
for (std::vector<PlayerTypes>::iterator it = vPlayersChanged.begin(); it != vPlayersChanged.end(); it++)
{
gDLL->sendPlayerInfo(*it);
}
gDLL->EndSendBundle();
}
}

vector<MinorCivTypes> GetAvailableMinorCivTypes(vector<MinorCivTypes>& vCultured, vector<MinorCivTypes>& vMilitaristic, vector<MinorCivTypes>& vMaritime, vector<MinorCivTypes>& vMercantile, vector<MinorCivTypes>& vReligious)
Expand Down
15 changes: 14 additions & 1 deletion CvGameCoreDLL_Expansion2/Lua/CvLuaGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,9 @@ void CvLuaGame::RegisterMembers(lua_State* L)
Method(IsPitbossHost);
Method(IsHost);
Method(GetTimeStringForYear);

Method(SetExeWantForceResyncValue);
Method(IsExeWantForceResyncAvailable);
}
//------------------------------------------------------------------------------

Expand Down Expand Up @@ -4192,4 +4195,14 @@ int CvLuaGame::lGetTimeStringForYear(lua_State* L)

lua_pushstring(L, timeString.GetCString());
return 1;
}
}
int CvLuaGame::lSetExeWantForceResyncValue(lua_State* L)
{
int value = lua_tointeger(L, 1);
GC.getGame().SetExeWantForceResyncValue(value);
return 0;
}
int CvLuaGame::lIsExeWantForceResyncAvailable(lua_State* L)
{
return BasicLuaMethod(L, &CvGame::IsExeWantForceResyncAvailable);
}
3 changes: 3 additions & 0 deletions CvGameCoreDLL_Expansion2/Lua/CvLuaGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@ class CvLuaGame : public CvLuaStaticInstance<CvLuaGame, CvGame>
static int lIsPitbossHost(lua_State* L);
static int lIsHost(lua_State* L);
static int lGetTimeStringForYear(lua_State* L);

static int lIsExeWantForceResyncAvailable(lua_State* L);
static int lSetExeWantForceResyncValue(lua_State* L);
};

#endif //CVLUAGAME_H

0 comments on commit 91e6f0a

Please sign in to comment.