Hayden Sim.
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
SDK/include/SoT_Athena_classes.hpp
  // 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
SDK/include/SoT_Athena_classes.hpp
  // 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!