Skip to content

Commit 0b21108

Browse files
authored
Rewrite frame CRC generation (Phobos-developers#1369)
Reimplements game's frame CRC generation function, only change logic-wise is that it now excludes `AnimClass` and `ParticleClass` objects that are purely visual.
1 parent 6208b42 commit 0b21108

4 files changed

Lines changed: 137 additions & 1 deletion

File tree

CREDITS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ This page lists all the individual contributions to the project by their author.
291291
- Wall overlay unit sell exploit fix
292292
- Fix vehicles disguised as trees incorrectly displaying veterancy insignia when they shouldn't
293293
- GapGen + SpySat desync fix
294+
- Frame CRC generation rewrite
294295
- **Morton (MortonPL)**:
295296
- `XDrawOffset` for animations
296297
- Shield passthrough & absorption

docs/Fixed-or-Improved-Logics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho
324324
- Enabled playing ingame movie in non-campaign modes (i.e. trigger action `100 Play Sidebar Movie...` and `117 Play Sidebar Movie and pause...`).
325325
- `ElectricAssault` weapons can now auto acquire allies' overpowerable defenses.
326326
- Fixed the issue that the time for units in the area guard mission to reacquire targets after eliminating the target is significantly longer than that in other missions.
327+
- Purely visual animations and particles are no longer included in frame CRC generation and are thus exempt from any sync checks between players in multiplayer games.
327328

328329
## Fixes / interactions with other extensions
329330

docs/Whats-New.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ Vanilla fixes:
652652
- Enabled playing ingame movie in non-campaign modes (i.e. trigger action `100 Play Sidebar Movie...` and `117 Play Sidebar Movie and pause...`) (by TaranDahl)
653653
- `ElectricAssault` weapons can now auto acquire allies' overpowerable defenses (by Ollerus)
654654
- Fixed the issue that the time for units in the area guard mission to reacquire targets after eliminating the target is significantly longer than that in other missions (by TaranDahl)
655+
- Purely visual animations and particles excluded from sync checks (by Starkku)
655656
656657
Phobos fixes:
657658
- Fixed the bug that `AllowAirstrike=no` cannot completely prevent air strikes from being launched against it (by NetsuNegi)

src/Misc/SyncLogging.cpp

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "SyncLogging.h"
22

33
#include <Helpers/Macro.h>
4+
#include <EventClass.h>
5+
#include <Ext/AnimType/Body.h>
46
#include <Utilities/Debug.h>
57
#include <Utilities/GeneralUtils.h>
68
#include <Utilities/AresHelper.h>
@@ -20,7 +22,6 @@ SyncLogEventBuffer<TargetChangeSyncLogEvent, DestinationChanges_Size> SyncLogger
2022
SyncLogEventBuffer<MissionOverrideSyncLogEvent, MissionOverrides_Size> SyncLogger::MissionOverrides;
2123
SyncLogEventBuffer<AnimCreationSyncLogEvent, AnimCreations_Size> SyncLogger::AnimCreations;
2224

23-
2425
void __forceinline MakeCallerRelative(unsigned int& caller)
2526
{
2627
// B for Bobos
@@ -541,3 +542,135 @@ DEFINE_HOOK(0x7013A0, TechnoClass_OverrideMission_SyncLog, 0x5)
541542

542543
return 0;
543544
}
545+
546+
#pragma region FrameCRC
547+
548+
class ObjectFake : public ObjectClass
549+
{
550+
public:
551+
inline bool _IsCRCHashable();
552+
};
553+
554+
bool ObjectFake::_IsCRCHashable()
555+
{
556+
auto const rtti = this->WhatAmI();
557+
558+
if (rtti == AbstractType::Anim)
559+
{
560+
// Game creates animation from [General] -> MoveFlash with this UniqueID
561+
// in FootClass::Active_Click_With() (0x4D7D50) - this is local-client only code
562+
// which is why these animations have to be ignored in sync check.
563+
if (this->UniqueID == -2)
564+
return false;
565+
566+
auto const pAnim = reinterpret_cast<AnimClass*>(this);
567+
auto pType = pAnim->Type;
568+
569+
while (pType)
570+
{
571+
// If animation type has logic that affects game simulation, don't ignore.
572+
if (pType->Damage != 0.0 || pType->Bouncer || pType->IsMeteor || pType->IsTiberium || pType->TiberiumChainReaction
573+
|| pType->IsAnimatedTiberium || pType->MakeInfantry != -1 || AnimTypeExt::ExtMap.Find(pType)->CreateUnitType.get())
574+
{
575+
return true;
576+
}
577+
578+
// Check anim's Next type recursively until not present.
579+
pType = pType->Next;
580+
}
581+
582+
return false;
583+
}
584+
else if (rtti == AbstractType::Particle)
585+
{
586+
auto const pParticle = reinterpret_cast<ParticleClass*>(this);
587+
auto pType = pParticle->Type;
588+
589+
// If particle type deals damage don't ignore.
590+
if (pType->Damage)
591+
return true;
592+
593+
// Check particle's NextParticle type recursively until not present.
594+
int index = pType->NextParticle;
595+
596+
while (index != -1)
597+
{
598+
pType = ParticleTypeClass::Array[index];
599+
600+
if (pType->Damage)
601+
return true;
602+
603+
index = pType->NextParticle;
604+
}
605+
606+
return false;
607+
}
608+
609+
return true;
610+
}
611+
612+
static void AddCRC(DWORD* crc, unsigned int val)
613+
{
614+
*crc = val + (*crc >> 31) + (*crc << 1);
615+
}
616+
617+
static int GetCoordHash(CoordStruct location)
618+
{
619+
return location.X / 10 + ((location.Y / 10) << 16);
620+
}
621+
622+
static void ComputeGameCRC()
623+
{
624+
EventClass::CurrentFrameCRC = 0;
625+
626+
for (auto const pInf : InfantryClass::Array)
627+
{
628+
int primaryFacing = pInf->PrimaryFacing.Current().GetValue<8>();
629+
AddCRC(&EventClass::CurrentFrameCRC, GetCoordHash(pInf->Location) + primaryFacing);
630+
}
631+
632+
for (auto const pUnit : UnitClass::Array)
633+
{
634+
int primaryFacing = pUnit->PrimaryFacing.Current().GetValue<8>();
635+
int secondaryFacing = pUnit->SecondaryFacing.Current().GetValue<8>();
636+
AddCRC(&EventClass::CurrentFrameCRC, GetCoordHash(pUnit->Location) + primaryFacing + secondaryFacing);
637+
}
638+
639+
for (auto const pBuilding : BuildingClass::Array)
640+
{
641+
int primaryFacing = pBuilding->PrimaryFacing.Current().GetValue<8>();
642+
AddCRC(&EventClass::CurrentFrameCRC, GetCoordHash(pBuilding->Location) + primaryFacing);
643+
}
644+
645+
for (auto const pHouse : HouseClass::Array)
646+
{
647+
AddCRC(&EventClass::CurrentFrameCRC, pHouse->MapIsClear);
648+
}
649+
650+
for (int i = 0; i < 5; i++)
651+
{
652+
auto const layer = DisplayClass::GetLayer((Layer)i);
653+
654+
for (auto const pObj : *layer)
655+
{
656+
if (((ObjectFake*)pObj)->_IsCRCHashable())
657+
AddCRC(&EventClass::CurrentFrameCRC, GetCoordHash(pObj->Location) + (int)pObj->WhatAmI());
658+
}
659+
}
660+
661+
LogicClass const& logic = LogicClass::Instance;
662+
663+
for (auto const pObj : logic)
664+
{
665+
if (((ObjectFake*)pObj)->_IsCRCHashable())
666+
AddCRC(&EventClass::CurrentFrameCRC, GetCoordHash(pObj->Location) + (int)pObj->WhatAmI());
667+
}
668+
669+
AddCRC(&EventClass::CurrentFrameCRC, ScenarioClass::Instance->Random.Random());
670+
Game::LogFrameCRC(Unsorted::CurrentFrame % 256);
671+
}
672+
673+
DEFINE_FUNCTION_JUMP(CALL, 0x64731C, ComputeGameCRC);
674+
DEFINE_FUNCTION_JUMP(CALL, 0x647684, ComputeGameCRC);
675+
676+
#pragma endregion

0 commit comments

Comments
 (0)