- Published on
Sea of Thieves Internal C++ Cheat
Introduction
Sea of Thieves (SoT) is a pirate PvP/PvE game with not a whole lot to do except grind out xp, so let's wreak some havoc.
Warning - this was all on a patch from 2019, do not use anything from this write-up, it will not work and will most likely result in a permanent ban.
I also rewrote this entire blog post from memory and backdated it, so there may be some things wrong whoops, feel free to contact me and lemme know!
What are we gonna do?
Since SoT is a bit of an endless grinding chore, we're gonna look at ways to speed it up. The biggest time sinks are:
- Sailing
- Players interrupting/preventing quests
- Gold hoarder maps
Sailing
Unfortunately Sea of Thieves has everything position/movement related server authorative, meaning we can't teleport around or increase our sail speed. Which means it looks like sailing manually is still our best bet. :(
Players interrupting/preventing quests
Here's where it starts to get a lot more fun, everyone loves a bit of PvP, but there's nothing more annoying than getting chased by a 4 man ship that's faster than a 2 man ship!
So let's read all the players inside the lobby. This is done by looking into the CrewService! This is easily done like this AthenaGameViewportClient->World->GameState->CrewService
.
You can the type of this CrewService
and also the FCrew
& APlayerState
dumped by an SDK dumper below. I've highlighted some interested values.
// Class Athena.CrewService
// 0x0270 (0x0680 - 0x0410)
class ACrewService : public AActor
{
public:
unsigned char UnknownData00[0x1C0]; // 0x0410(0x01C0) MISSED OFFSET
TArray<struct FCrew> Crews; // 0x05D0(0x0010) (Net, ZeroConstructor)
unsigned char UnknownData01[0xA0]; // 0x05E0(0x00A0) MISSED OFFSET
...
};
// ScriptStruct Athena.Crew
// 0x0080
struct FCrew
{
struct FGuid CrewId; // 0x0000(0x0010) (ZeroConstructor, IsPlainOldData)
struct FGuid SessionId; // 0x0010(0x0010) (ZeroConstructor, IsPlainOldData)
TArray<class APlayerState*> Players; // 0x0020(0x0010) (ZeroConstructor)
struct FCrewSessionTemplate CrewSessionTemplate; // 0x0030(0x0038)
struct FGuid LiveryID; // 0x0068(0x0010) (ZeroConstructor, IsPlainOldData)
unsigned char UnknownData00[0x8]; // 0x0078(0x0008) MISSED OFFSET
};
// Class Engine.PlayerState
// 0x0088 (0x0498 - 0x0410)
class APlayerState : public AInfo
{
public:
float Score; // 0x0410(0x0004) (BlueprintVisible, BlueprintReadOnly, ZeroConstructor, IsPlainOldData)
unsigned char Ping; // 0x0414(0x0001) (BlueprintVisible, BlueprintReadOnly, ZeroConstructor, IsPlainOldData)
unsigned char UnknownData00[0x3]; // 0x0415(0x0003) MISSED OFFSET
class FString PlayerName; // 0x0418(0x0010) (BlueprintVisible, BlueprintReadOnly, Net, ZeroConstructor)
unsigned char UnknownData01[0x10]; // 0x0428(0x0010) MISSED OFFSET
int PlayerId; // 0x0438(0x0004) (BlueprintVisible, BlueprintReadOnly, Net, ZeroConstructor, IsPlainOldData)
unsigned char bIsSpectator : 1; // 0x043C(0x0001) (BlueprintVisible, BlueprintReadOnly)
unsigned char bOnlySpectator : 1; // 0x043C(0x0001)
unsigned char bIsABot : 1; // 0x043C(0x0001) (BlueprintVisible, BlueprintReadOnly)
...
int StartTime; // 0x0440(0x0004) (ZeroConstructor, IsPlainOldData)
...
};
Now we can see every crew in the server, crews are 1:1 with ships, so any unique CrewId
is a new ship. We now know all the players in the server and how big their crews are, since they can be 1-4 players.
While that's all well and good, it doesn't help us avoid them, or even know where they are. To do that, we can utilise AthenaGameViewportClient->World->PersistentLevel
which from the UE4 docs describes as
Persistent level containing the world info, default brush and actors spawned during gameplay among other things
Now knowing all the actors spawned in the level, we can iterate through that array and to find a bunch of things. Some code is provided below to show how you'd iterate the list and find all the players.
for (AActor* actor : AthenaGameViewportClient->World->PersistentLevel->AActors) {
if (actor->IsA(AAthenaPlayerCharacter::StaticClass())) {
auto player = (*AAthenaPlayerCharacter)actor;
// have a look at what that AAthenaPlayerCharacter looks like below
}
}
AAthenaPlayerCharacter
dumped class
// Class Athena.AthenaPlayerCharacter
// 0x0D00 (0x1810 - 0x0B10)
class AAthenaPlayerCharacter : public AAthenaCharacter { }
public:
unsigned char UnknownData00[0xD0]; // 0x0B10(0x00D0) MISSED OFFSET
class USceneComponent* FirstPersonMeshOffsetComponent; // 0x0BE0(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class USkeletalMeshComponent* FirstPersonMesh; // 0x0BE8(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UAthenaCameraComponent* FirstPersonCamera; // 0x0BF0(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UAthenaCameraComponent* ThirdPersonCamera; // 0x0BF8(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UAthenaCameraComponent* SpringArmCamera; // 0x0C00(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UAthenaSpringArmComponent* SpringArm; // 0x0C08(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UChildActorComponent* LocalCloudDome; // 0x0C10(0x0008) (BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, InstancedReference, IsPlainOldData)
class UChildActorComponent* ColdDataStore; // 0x0C18(0x0008) (BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, InstancedReference, IsPlainOldData)
class UCharacterInteractionComponent* InteractionComponent; // 0x0C20(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UClimbingComponent* ClimbingComponent; // 0x0C28(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UDrunkennessComponent* DrunkennessComponent; // 0x0C30(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UStaggerComponent* StaggerComponent; // 0x0C38(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UDrowningComponent* DrowningComponent; // 0x0C40(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class URumbleHandlerComponent* RumbleHandlerComponent; // 0x0C48(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UScreenSpaceVfxComponent* ScreenSpaceVfxComponent; // 0x0C50(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UTreasureMapCollectionComponent* TreasureMapCollectionComponent; // 0x0C58(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UCustomCategoryRadialInventoryComponent* CustomCategoryRadialInventoryComponent; // 0x0C60(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UItemRadialInventoryComponent* ItemRadialInventoryComponent; // 0x0C68(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UPhrasesRadialComponent* PhrasesRadialComponent; // 0x0C70(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UMapRadialInventoryComponent* MapRadialInventoryComponent; // 0x0C78(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UQuickInventoryComponent* QuickInventoryComponent; // 0x0C80(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UPointOfInterestTrackerComponent* PointOfInterestTrackerComponent; // 0x0C88(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class ULimpingComponent* LimpingComponent; // 0x0C90(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UPacingComponent* PacingComponent; // 0x0C98(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UBurpComponent* BurpComponent; // 0x0CA0(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UMeleeAttackLockOnComponent* MeleeLockOnComponent; // 0x0CA8(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, DisableEditOnTemplate, EditConst, InstancedReference, IsPlainOldData)
class UMeleeAttackLockOnComponentSettings* MeleeAttackLockOnSettings; // 0x0CB0(0x0008) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
class UStatusEffectManagerComponent* StatusEffectManagerComponent; // 0x0CB8(0x0008) (Edit, ExportObject, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UStatusEffectPersistenceComponent* StatusEffectPersistenceComponent; // 0x0CC0(0x0008) (Edit, ExportObject, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UPlayerCurseComponent* PlayerCurseComponent; // 0x0CC8(0x0008) (Edit, ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
class UMaterialStatusSusceptibilityComponent* MaterialStatusSusceptibilityComponent; // 0x0CD0(0x0008) (Edit, ExportObject, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UHealthRegenerationPoolComponent* HealthRegenerationPoolComponent; // 0x0CD8(0x0008) (Edit, ExportObject, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class USicknessComponent* SicknessComponent; // 0x0CE0(0x0008) (Edit, ExportObject, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class URewindComponent* PlayerViewRewindComponent; // 0x0CE8(0x0008) (Edit, ExportObject, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UCapsuleComponent* ProjectileTraceHitVolume; // 0x0CF0(0x0008) (Edit, BlueprintVisible, ExportObject, BlueprintReadOnly, ZeroConstructor, EditConst, InstancedReference, IsPlainOldData)
class UPlayerMerchantContractsComponent* PlayerMerchantContractsComponent; // 0x0CF8(0x0008) (ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
class UPlayerShroudBreakerTrackerComponent* PlayerShroudBreakerTrackerComponent; // 0x0D00(0x0008) (ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
class UPetOwnerComponent* PetOwnerComponent; // 0x0D08(0x0008) (ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
class UAISpawnContextContainerComponent* SpawnContextContainerComponent; // 0x0D10(0x0008) (ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
float CameraFOVWhenSprinting; // 0x0D18(0x0004) (Edit, BlueprintVisible, ZeroConstructor, IsPlainOldData)
unsigned char UnknownData01[0x4]; // 0x0D1C(0x0004) MISSED OFFSET
class UCurveVector* ApproximateCamPitchToLocalCamOffsetCurve; // 0x0D20(0x0008) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
struct FWalkConditionsParams WalkConditionParams; // 0x0D28(0x0008) (Edit, BlueprintVisible)
struct FSprintConditionsParams ServerSprintConditionParams; // 0x0D30(0x000C) (Edit, BlueprintVisible)
struct FSprintConditionsParams ClientSprintConditionParams; // 0x0D3C(0x000C) (Edit, BlueprintVisible)
float SprintingServerTimeTolerance; // 0x0D48(0x0004) (Edit, BlueprintVisible, ZeroConstructor, IsPlainOldData)
float PostSprintResetTime; // 0x0D4C(0x0004) (Edit, BlueprintVisible, ZeroConstructor, IsPlainOldData)
class UAthenaSpringArmComponentParams* EmoteSpringArmParameters; // 0x0D50(0x0008) (Edit, BlueprintVisible, ZeroConstructor, IsPlainOldData)
class UAthenaSpringArmComponentParams* DeathSpringArmParameters; // 0x0D58(0x0008) (Edit, BlueprintVisible, ZeroConstructor, IsPlainOldData)
class UEmotesRadialDataAsset* EmotesRadialDataAsset; // 0x0D60(0x0008) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
bool ReplicatedIsSprinting; // 0x0D68(0x0001) (Net, ZeroConstructor, IsPlainOldData)
bool AllowAnimationTickAggregation; // 0x0D69(0x0001) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
unsigned char UnknownData02[0x2]; // 0x0D6A(0x0002) MISSED OFFSET
struct FAchievementKillOpponentFromBehindWithBlunderbuss AchievementKillOpponentFromBehindWithBlunderbuss; // 0x0D6C(0x0004) (Edit, DisableEditOnInstance)
struct FAchievementHeavyAttackRivalOffTheirShip AchievementHeavyAttackRivalOffTheirShip; // 0x0D70(0x0010) (Edit, DisableEditOnInstance)
struct FAchievementBlockThenHitAndGetBlockedThenTakeHit AchievementBlockThenHitAndGetBlockedThenTakeHit; // 0x0D80(0x0010) (Edit, DisableEditOnInstance)
struct FAchievementHaveADrinkWithAnotherCrewAtAnOutpost AchievementHaveADrinkWithAnotherCrewAtAnOutpost; // 0x0D90(0x0020) (Edit, DisableEditOnInstance)
struct FAchievementKillOpponentInContest AchievementKillOpponentInContest; // 0x0DB0(0x0008)
class UClass* ConditionalStatsTriggerForKillingAnotherPlayer; // 0x0DB8(0x0008) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
struct FVengeanceParams VengeanceParams; // 0x0DC0(0x0010) (Edit, DisableEditOnInstance)
class UAICreatureMovementModifierParamsDataAsset* PirateLegendHideoutPetMovementData; // 0x0DD0(0x0008) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
class UAICreatureMovementModifierParamsDataAsset* OnShipPetMovementData; // 0x0DD8(0x0008) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
unsigned char UnknownData03[0x98]; // 0x0DE0(0x0098) MISSED OFFSET
int ReplicatedOverridePirateDescSeed; // 0x0E78(0x0004) (Net, ZeroConstructor, IsPlainOldData)
struct FName ReplicatedOverrideAnimationEntry; // 0x0E7C(0x0008) (Net, ZeroConstructor, Transient, IsPlainOldData)
unsigned char UnknownData04[0x4]; // 0x0E84(0x0004) MISSED OFFSET
class UClass* FirstPersonAnimSetDataId; // 0x0E88(0x0008) (Edit, BlueprintVisible, BlueprintReadOnly, ZeroConstructor, IsPlainOldData)
class UAnimationDataStoreWeakReferenceAsset* FirstPersonAnimationDataStoreWeakReferencesAsset; // 0x0E90(0x0008) (Edit, ZeroConstructor, IsPlainOldData)
TEnumAsByte<ECharacterType> CharacterType; // 0x0E98(0x0001) (Edit, ZeroConstructor, DisableEditOnInstance, IsPlainOldData)
unsigned char UnknownData05[0x1F]; // 0x0E99(0x001F) MISSED OFFSET
class UEmotesRadialComponent* EmotesRadialComponent; // 0x0EB8(0x0008) (ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
unsigned char UnknownData06[0x7C0]; // 0x0EC0(0x07C0) MISSED OFFSET
class FString CurrentWorldLocation; // 0x1680(0x0010) (Net, ZeroConstructor)
unsigned char UnknownData07[0x68]; // 0x1690(0x0068) MISSED OFFSET
class AAthenaPlayerCharacterColdDataStore* ColdDataStoreActor; // 0x16F8(0x0008) (ZeroConstructor, Transient, IsPlainOldData)
struct FAthenaPlayerCharacterModelSwap PendingModelSwap; // 0x1700(0x0028) (Transient)
unsigned char UnknownData08[0x29]; // 0x1728(0x0029) MISSED OFFSET
bool ReplicatedEmoteExitAllowed; // 0x1751(0x0001) (Net, ZeroConstructor, IsPlainOldData)
unsigned char UnknownData09[0x76]; // 0x1752(0x0076) MISSED OFFSET
bool FinishedWaitingForSpawn; // 0x17C8(0x0001) (Net, ZeroConstructor, IsPlainOldData)
unsigned char UnknownData10[0x40]; // 0x17C9(0x0040) MISSED OFFSET
bool ReplicatedIsInvulnerable; // 0x1809(0x0001) (Net, ZeroConstructor, IsPlainOldData)
unsigned char UnknownData11[0x6]; // 0x180A(0x0006) MISSED OFFSET
static UClass* StaticClass()
{
static auto ptr = UObject::FindObject<UClass>(_xor_("Class Athena.AthenaPlayerCharacter"));
return ptr;
}
void WalkActivate();
bool UseItem(class UClass* NotificationInputId);
void TriggerTattooGlow();
void TattooGlowClient();
void TattooGlow();
void SprintActivate();
void ServerWantsSprint(bool WantsSprint);
void OnWieldedItem();
void OnRep_ReplicatedIsSprinting();
void OnRep_ReplicatedIsInvulnerable();
void OnRep_OverridePirateDescSeed();
void OnRep_OverrideAnimationEntry();
void OnMeshSet();
void OnImpactDamageEventReceived(const struct FImpactDamageEvent& InImpactEvent);
void Multicast_OnKrakenTentacleDeath(int RemainingTentacles);
bool IsSprintEnabled();
bool IsInteractionValid(class UObject* InInteractable);
bool HasMeshSet();
bool HasFinishedSpawning();
class UVoyageParticipantComponent* GetTaleVoyageParticipantComponent();
class UVoyageParticipantComponent* GetPrimaryVoyageParticipantComponent();
class UPetLoadoutComponent* GetPetLoadoutComponent();
struct FVector GetPawnViewLocation();
struct FName GetLocalPlayerWorldRegionName();
bool GetIsSprinting();
bool GetIsDigitallyWalking();
class UObject* GetFocusedInteractable();
struct FVector GetFirstPersonMeshOffset();
class UEmotesRadialComponent* GetEmotesRadialComponent();
class UVoyageParticipantComponent* GetEmergentVoyageParticipantComponent();
TEnumAsByte<EShipRegion> GetCurrentShipRegion();
class UClothingLoadoutComponent* GetClothingLoadoutComponent();
void ClientAddPlayerAsFriend(class AAthenaPlayerCharacter* PlayerToAddAsFriend);
bool AllAnimationsLoaded();
};
As you can see, there's a LOT of componenents in that class and a lot of things we can look into, unfortunately a lot of these things are client-sided and so there's no way to access something like the DrunkennessComponent
and intoxicate your opponents.
What is accessible however, is the players position, crew, health, name, camera and more! Since this is just a UE4 AActor*
, we're able to call player->K2_GetActorLocation()
to get an FVector
of their location in the world. We can read their health by looking into the HealthComponent
like so player->HealthComponent->GetCurrentHealth() / player->HealthComponent->MaxHealth
. Using this information, we now know where everyone in the server is, what their health is and what they're looking at. Perfect that's players sorted... for now.
Gold hoarder maps
There's two kinds of maps; X Marks the Spot maps and Riddle maps; both are annoying in their own right, but we'll start with riddle maps.
Riddle maps
Riddle maps are just maps describing a location the player must goto and an action they must perform at that location. I realised there had to be some found of trigger/collision box around the "locations" which they check to determine when you've completed an action inside the location and so it turns out there is! To access these bounding boxes, we had to read the AthenaGameViewportClient->World->Levels
which contains every level in the game. This has TONNES of chunk and can contain tens if not hundreds of thousands of actors for every single mesh/sound/entity in the engine.
We can filter them all out by checking if they have the type ALandmark
, we can see the class definition below.
ALandmark
dumped class
// Class Athena.Landmark
// 0x0288 (0x0698 - 0x0410)
class ALandmark : public AActor
{
public:
struct FText Name; // 0x0410(0x0038) (Edit)
struct FText NameV2; // 0x0448(0x0038) (Edit)
struct FText NameV3; // 0x0480(0x0038) (Edit)
struct FText NameV4; // 0x04B8(0x0038) (Edit)
TEnumAsByte<ELandmarkVagueness> LandmarkVagueness; // 0x04F0(0x0001) (Edit, ZeroConstructor, IsPlainOldData)
TEnumAsByte<ELandmarkUniqueness> LandmarkUniqueness; // 0x04F1(0x0001) (Edit, ZeroConstructor, IsPlainOldData)
bool IsUnderground; // 0x04F2(0x0001) (Edit, ZeroConstructor, IsPlainOldData)
unsigned char UnknownData00[0x5]; // 0x04F3(0x0005) MISSED OFFSET
TArray<TEnumAsByte<EQuestType>> IsUnsuitableFor; // 0x04F8(0x0010) (Edit, ZeroConstructor)
unsigned char UnknownData01[0x11C]; // 0x0508(0x011C) MISSED OFFSET
float PacingStartRadius; // 0x0624(0x0004) (Edit, ZeroConstructor, IsPlainOldData)
TArray<struct FLandmarkReaction> Reactions; // 0x0628(0x0010) (Edit, ZeroConstructor)
unsigned char UnknownData02[0x20]; // 0x0638(0x0020) MISSED OFFSET
class UOverlapTriggerComponent* AudioZoneComponent; // 0x0658(0x0008) (ExportObject, ZeroConstructor, InstancedReference, IsPlainOldData)
unsigned char UnknownData03[0x2C]; // 0x0660(0x002C) MISSED OFFSET
struct FVector StartLocation; // 0x068C(0x000C) (Edit, ZeroConstructor, IsPlainOldData)
static UClass* StaticClass()
{
static auto ptr = UObject::FindObject<UClass>(_xor_("Class Athena.Landmark"));
return ptr;
}
void TriggerLandmarkReactionForNearbyPlayersExplicitList(int LandmarkReactionIndex, TArray<class AActor*> Players);
}
Knowing all the landmarks in the area now, we can utilise reading our players MapRadialInventoryComponent
to read all our players maps. Firstly we want to check that we've completed step 1, which is always, visit the island it's on. We can then start iterating through the steps and comparing them to local landmarks, each line has some template string like "At {location} the treasure close at hand, {num} paces {direction} dig up the land." This allows for some dynamic gameplay and millions of quest combinations, but they also have a Substitutions Array we can iterate and compare with the landmarks and the actions we know. This allows us to utilise the StartLocation
of the landmark to know exactly where to go.
That's all well and good, but how?
Now we know how to get everything from AthenaGameViewportClient
... How the hell do we get AthenaGameViewportClient
!
How do we get our code into the game?
We write a Windows DLL! Then we can utilise functions like VirtualAllocEx and CreateRemoteThreadEx or LoadLibraryEx to remotely load the DLL into the game!
Once inside, we can start looking for what we need! Note: this can also be done without ever executing code inside the target progress it's just faster than calling ReadProcessMemory and WriteProcessMemory.
Here we're using an SDK from igromanru - https://github.com/igromanru/SoT-SDK-Guide - SDK aren't official and they're dumped from the game at some version which is why they contain various weird things like the UnknownDataXX
byte arrays in structs. But they greatly speed up game cheat development.
In some game cheats and games you can use offsets that never change, an example of this is usually the DirectX 11 function pointers. Unfortunately the location of the variables in memory will change everytime the game opens, this is due to Address Space Layout Randomization, but no worries! We can utilise a technique called Pattern Scanning. It takes a pattern that does not change in memory values (not addresses!) and will search the entire address space for it. A pattern signature looks a bit like this 7D 1D 29 B3 ? ? ? ? 8B 83
. Here's a great explanation on GuidedHacking.. In our case (as of 2019) we're looking two things!
- GNames -
48 8B 1D ? ? ? ? 48 85 ? 75 3A
- GObjects -
89 0D ? ? ? ? 48 8B DF 48 89 5C 24
These are internal Unreal Engine global variables which store every UObject in the game as well as their name. We can then use the following function to find the first object with the given name!
template<typename T>
static T* FindObject(const std::string& name)
{
for (int i = 0; i < GetGlobalObjects().Num(); ++i)
{
auto object = GetGlobalObjects().GetByIndex(i);
if (object == nullptr)
{
continue;
}
if (object->GetFullName() == name)
{
return static_cast<T*>(object);
}
}
return nullptr;
}
If we call that with the name AthenaGameViewportClient Transient.AthenaGameEngine_1.AthenaGameViewportClient_1
we finally get our AthenaGameViewportClient
!
Wait, we know all these things about player positioning, but what can I even do with it!
I'm glad you asked! We can hook Unreal Engine's internal rendering functions to draw our own content inside the game, this saves countless time spent dealing with graphics libraries and hooking DirectX etc. To do so, we look at AthenaGameViewportClient
yet again and if we look at the type we can see it's a subclass of UGameViewportClient
which has our goodies, since the UE4 source is open, we can tell that PostRender
is the 88th function on that class, we can then use that 88 * sizeof uintptr_t
to get the offset we need from AthenaGameViewportClient
to our function. Knowing that we can use the following brilliant library for executing a Virtual Method Table hook.
LPVOID HookMethod(_In_ LPVOID lpVirtualTable, _In_ PVOID pHookMethod,
_In_opt_ uintptr_t dwOffset)
{
uintptr_t dwVTable = *((uintptr_t*)lpVirtualTable);
uintptr_t dwEntry = dwVTable + dwOffset;
uintptr_t dwOrig = *((uintptr_t*)dwEntry);
DWORD dwOldProtection;
::VirtualProtect((LPVOID)dwEntry, sizeof(dwEntry),
PAGE_EXECUTE_READWRITE, &dwOldProtection);
*((uintptr_t*)dwEntry) = (uintptr_t)pHookMethod;
::VirtualProtect((LPVOID)dwEntry, sizeof(dwEntry),
dwOldProtection, &dwOldProtection);
return (LPVOID) dwOrig;
}
Putting it all together
Now we've put all of it together, we can make some really cool things!