-
Notifications
You must be signed in to change notification settings - Fork 73
/
sot_hack.py
206 lines (179 loc) · 8.24 KB
/
sot_hack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""
@Author https://github.com/DougTheDruid
@Source https://github.com/DougTheDruid/SoT-ESP-Framework
For community support, please contact me on Discord: DougTheDruid#2784
"""
import struct
import logging
from memory_helper import ReadMemory
from mapping import ship_keys
from helpers import OFFSETS, CONFIG, logger
from Modules.ship import Ship
from Modules.crews import Crews
class SoTMemoryReader:
"""
Wrapper class to handle reading data from the game, parsing what is
important, and returning it to be shown by pyglet
"""
def __init__(self):
"""
Upon initialization of this object, we want to find the base address
for the SoTGame.exe, then begin to load in the static addresses for the
uWorld, gName, gObject, and uLevel objects.
We also poll the local_player object to get a first round of coords.
When running read_actors, we update the local players coordinates
using the camera-manager object
Also initialize a number of class variables which help us cache some
basic information
"""
self.rm = ReadMemory("SoTGame.exe")
base_address = self.rm.base_address
logging.info(f"Process ID: {self.rm.pid}")
u_world_offset = self.rm.read_ulong(
base_address + self.rm.u_world_base + 3
)
u_world = base_address + self.rm.u_world_base + u_world_offset + 7
self.world_address = self.rm.read_ptr(u_world)
g_name_offset = self.rm.read_ulong(
base_address + self.rm.g_name_base + 3
)
g_name = base_address + self.rm.g_name_base + g_name_offset + 7
logging.info(f"SoT gName Address: {hex(g_name)}")
self.g_name = self.rm.read_ptr(g_name)
g_objects_offset = self.rm.read_ulong(
base_address + self.rm.g_object_base + 3
)
g_objects = base_address + self.rm.g_object_base + g_objects_offset + 7
logging.info(f"SoT gObject Address: {hex(g_objects)}")
self.g_objects = self.rm.read_ptr(g_objects)
self.u_level = self.rm.read_ptr(self.world_address +
OFFSETS.get('World.PersistentLevel'))
self.u_local_player = self._load_local_player()
self.player_controller = self.rm.read_ptr(
self.u_local_player + OFFSETS.get('LocalPlayer.PlayerController')
)
self.my_coords = self._coord_builder(self.u_local_player)
self.my_coords['fov'] = 90
self.actor_name_map = {}
self.server_players = []
self.display_objects = []
self.crew_data = None
def _load_local_player(self) -> int:
"""
Returns the local player object out of uWorld.UGameInstance.
Used to get the players coordinates before reading any actors
:rtype: int
:return: Memory address of the local player object
"""
game_instance = self.rm.read_ptr(
self.world_address + OFFSETS.get('World.OwningGameInstance')
)
local_player = self.rm.read_ptr(
game_instance + OFFSETS.get('GameInstance.LocalPlayers')
)
return self.rm.read_ptr(local_player)
def update_my_coords(self):
"""
Function to update the players coordinates and camera information
storing that new info back into the my_coords field. Necessary as
we dont always run a full scan and we need a way to update ourselves
"""
manager = self.rm.read_ptr(
self.player_controller + OFFSETS.get('PlayerController.CameraManager')
)
self.my_coords = self._coord_builder(
manager,
OFFSETS.get('PlayerCameraManager.CameraCache')
+ OFFSETS.get('CameraCacheEntry.MinimalViewInfo'),
fov=True)
def _coord_builder(self, actor_address: int, offset=0x78, camera=True,
fov=False) -> dict:
"""
Given a specific actor, loads the coordinates for that actor given
a number of parameters to define the output
:param int actor_address: Actors base memory address
:param int offset: Offset from actor address to beginning of coords
:param bool camera: If you want the camera info as well
:param bool fov: If you want the FoV info as well
:rtype: dict
:return: A dictionary containing the coordinate information
for a specific actor
"""
if fov:
actor_bytes = self.rm.read_bytes(actor_address + offset, 44)
unpacked = struct.unpack("<ffffff16pf", actor_bytes)
else:
actor_bytes = self.rm.read_bytes(actor_address + offset, 24)
unpacked = struct.unpack("<ffffff", actor_bytes)
coordinate_dict = {"x": unpacked[0]/100, "y": unpacked[1]/100,
"z": unpacked[2]/100}
if camera:
coordinate_dict["cam_x"] = unpacked[3]
coordinate_dict["cam_y"] = unpacked[4]
coordinate_dict["cam_z"] = unpacked[5]
if fov:
coordinate_dict['fov'] = unpacked[7]
return coordinate_dict
def read_actors(self):
"""
Represents a full scan of every actor within our render distance.
Will create an object for each type of object we are interested in,
and store it in a class variable (display_objects).
Then our main game loop updates those objects
"""
# On a full run, start by cleaning up all the existing text renders
for display_ob in self.display_objects:
try:
display_ob.text_render.delete()
except:
continue
try:
display_ob.icon.delete()
except:
continue
self.display_objects = []
self.update_my_coords()
actor_raw = self.rm.read_bytes(self.u_level + 0xa0, 0xC)
actor_data = struct.unpack("<Qi", actor_raw)
# Credit @mogistink https://www.unknowncheats.me/forum/members/3434160.html
# One very large read for all the actors addresses to save us 1000+ reads every read_all
level_actors_raw = self.rm.read_bytes(actor_data[0], actor_data[1] * 8)
self.server_players = []
for x in range(0, actor_data[1]):
# We start by getting the ActorID for a given actor, and comparing
# that ID to a list of "known" id's we cache in self.actor_name_map
raw_name = ""
actor_address = int.from_bytes(level_actors_raw[(x*8):(x*8+8)], byteorder='little', signed=False)
actor_id = self.rm.read_int(
actor_address + OFFSETS.get('Actor.actorId')
)
# We save a mapping of actor id to actor name for the sake of
# saving memory calls
if actor_id not in self.actor_name_map and actor_id != 0:
try:
raw_name = self.rm.read_gname(actor_id)
self.actor_name_map[actor_id] = raw_name
except Exception as e:
logger.error(f"Unable to find actor name: {e}")
elif actor_id in self.actor_name_map:
raw_name = self.actor_name_map.get(actor_id)
# Ignore anything we cannot find a name for
if not raw_name:
continue
# If we have Ship ESP enabled in helpers.py, and the name of the
# actor is in our mapping.py ship_keys object, interpret the actor
# as a ship
if CONFIG.get('SHIPS_ENABLED') and raw_name in ship_keys:
ship = Ship(self.rm, actor_id, actor_address, self.my_coords,
raw_name)
# if "Near" not in ship.name and ship.distance < 1720:
# continue
# else:
self.display_objects.append(ship)
# If we have the crews data enabled in helpers.py and the name
# of the actor is CrewService, we create a class based on that Crew
# data to generate information about people on the server
# NOTE: This will NOT give us information on nearby players for the
# sake of ESP
elif CONFIG.get('CREWS_ENABLED') and raw_name == "CrewService":
self.crew_data = Crews(self.rm, actor_id, actor_address)