Skip to content

Commit

Permalink
Updated for hlmv++
Browse files Browse the repository at this point in the history
  • Loading branch information
jbzdarkid committed Nov 20, 2023
1 parent d816660 commit 59c697a
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 91 deletions.
70 changes: 44 additions & 26 deletions HLMVModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from subprocess import Popen, PIPE
from struct import pack, unpack

mem_offsets = {}
def mem(*args):
"""
Communicate with the C memory management utility via subprocess.Popen
Expand All @@ -23,7 +22,7 @@ def mem(*args):
if stderr:
raise Exception(stderr)
elif args[0] == 'sigscan':
return [bytes.fromhex(line) for line in stdout.strip().split('\n')]
return [bytes.fromhex(line) for line in stdout.split(' ')]
elif args[0] == 'read':
return bytes.fromhex(stdout.strip())

Expand All @@ -42,42 +41,61 @@ def __init__(self, initial):
# mem.exe will search for these bytes and return the assembly (also in hex)
# and we can then decode that assembly back into the offset of the variable.
sigscans = mem('sigscan',
'81CA00010000803D', # normals
'50F30F2CC0F30F1005', # color
'8B4328A80274', # rotation and position
'558BEC81ECE8010000', # background
'81CA00010000803D', # HLMV normals
'8B4328A80274', # HLMV rotation and position
'81CA0001000025FFFEFFFF', # HLMV++ normals
'EB2FA802742B', # HLMV++ rotation and position
)

self.mem_offsets = {}

# Normal mapping
self.mem_offsets['nm'] = str(unpack('<i', sigscans[0][8:12])[0] - 0x410000)
# Background color
self.mem_offsets['color'] = str(unpack('<i', sigscans[1][9:13])[0] - 0x410000)
if sigscans[2]: # HLMV
# Background color
self.mem_offsets['color'] = str(unpack('<i', sigscans[0][9:13])[0] - 0x410000)

object_ref = str(unpack('<i', sigscans[2][29:33])[0] - 0x410000)
object_base = unpack('<i', mem('read', '4', object_ref))[0] - 0x410000
# Enable background
self.mem_offsets['bg'] = str(unpack('<i', sigscans[1][11:15])[0] - 0x410000)

# Absolute rotation
self.mem_offsets['rot'] = str(object_base + 0x08)
self.mem_offsets['trans'] = str(object_base + 0x14)
# Normal mapping
self.mem_offsets['nm'] = str(unpack('<i', sigscans[2][8:12])[0] - 0x410000)

# Enable background
self.mem_offsets['bg'] = str(unpack('<i', sigscans[3][11:15])[0] - 0x410000)
# Absolute rotation and translation
object_ref = str(unpack('<i', sigscans[3][29:33])[0] - 0x410000)
object_base = unpack('<i', mem('read', '4', object_ref))[0] - 0x410000
self.mem_offsets['rot'] = str(object_base + 0x08)
self.mem_offsets['trans'] = str(object_base + 0x14)

# for k, v in self.mem_offsets.items():
# print(k, hex(int(v)))
elif sigscans[4]: # HLMV++
# Background color
self.mem_offsets['color'] = str(unpack('<i', sigscans[0][9:13])[0] - 0x400000)

mem('write', self.mem_offsets['nm'], pack('b', 1))
mem('write', self.mem_offsets['color'], pack('ffff', 1.0, 1.0, 1.0, 1.0))
# Enable background
self.mem_offsets['bg'] = str(unpack('<i', sigscans[1][11:15])[0] - 0x400000)

# Normal mapping
self.mem_offsets['nm'] = str(unpack('<i', sigscans[4][13:17])[0] - 0x400000)

# Absolute rotation and translation
object_ref = str(unpack('<i', sigscans[5][29:33])[0] - 0x400000)
object_base = unpack('<i', mem('read', '4', object_ref))[0] - 0x400000
self.mem_offsets['rot'] = str(object_base + 0x08)
self.mem_offsets['trans'] = str(object_base + 0x14)

else:
raise ValueError('Sigscan mismatch for both HLMV and HLMV++')

mem('write', pack('b', 1), self.mem_offsets['nm'])
mem('write', pack('ffff', 1.0, 1.0, 1.0, 1.0), self.mem_offsets['color'])
if initial['rotation']:
self.rotation = initial['rotation']
mem('write', self.mem_offsets['rot'], pack('fff', self.rotation))
mem('write', pack('fff', self.rotation), self.mem_offsets['rot'])
else: # Load from current state
self.rotation = unpack('fff', mem('read', '12', self.mem_offsets['rot']))
if initial['translation']:
self.translation = initial['translation']
mem('write', self.mem_offsets['trans'], pack('fff', self.translation))
mem('write', pack('fff', self.translation), self.mem_offsets['trans'])
else:
self.translation = unpack('fff', mem('read', '12', self.mem_offsets['trans']))
if initial['rotation_offset']:
Expand All @@ -93,7 +111,7 @@ def set_background(self, value):
"""
Set the HLMV background to a given value.
"""
mem('write', self.mem_offsets['bg'], pack('b', value*1))
mem('write', pack('b', value*1), self.mem_offsets['bg'])

def rotate(self, x, y):
"""
Expand All @@ -104,18 +122,18 @@ def rotate(self, x, y):
Note that HLMV uses degrees while python uses radians.
"""

mem('write', self.mem_offsets['rot'], pack('fff',
mem('write', pack('fff',
self.rotation[0] + x,
self.rotation[1] + y,
self.rotation[2]
))
), self.mem_offsets['rot'])

x = radians(x)
y = radians(y)

xy_shift = sin(x)*sin(y)*self.vert_offset
mem('write', self.mem_offsets['trans'], pack('fff',
mem('write', pack('fff',
self.translation[0] + cos(y)*self.rot_offset + xy_shift,
self.translation[1] + sin(y)*self.rot_offset + xy_shift,
self.translation[2] - sin(x)*self.rot_offset
))
), self.mem_offsets['trans'])
37 changes: 22 additions & 15 deletions automate.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ def enum_callback(hwnd, _):
then compute the cropping boundary based on its resulting size
"""
if GetWindowText(hwnd)[:7] == 'models\\':
SetForegroundWindow(hwnd)
ShowWindow(hwnd, SW_MAXIMIZE)
global rect
rect = GetWindowRect(hwnd)
if not rect:
SetForegroundWindow(hwnd)
ShowWindow(hwnd, SW_MAXIMIZE)
rect = GetWindowRect(hwnd)
rect = None
EnumWindows(enum_callback, [])
EnumWindows(enum_callback, None)
if not rect:
print("Couldn't find HLMV, is it open with a model loaded?")
exit()
Expand Down Expand Up @@ -93,17 +94,23 @@ def enum_callback(hwnd, _):
full_image.convert('RGB').save(output_file, 'JPEG', quality=100, progressive=True, optimize=True)
title = input('Upload file name: ') + ' 3D.jpg'

wiki = Wiki('https://wiki.teamfortress.com/w/api.php')
username = input('Wiki username: ')
for i in range(3):
if wiki.login(username):
break

page = Page(wiki, 'File:' + title)
with open(output_file, 'rb') as file:
r = page.upload(file)
if r:
print('Failed to upload %s: %s' % (file, r))
try:
wiki = Wiki('https://wiki.teamfortress.com/w/api.php')
username = input('Wiki username: ')
for i in range(3):
if wiki.login(username):
break

page = Page(wiki, 'File:' + title)
with open(output_file, 'rb') as file:
r = page.upload(file)
if r:
raise ValueError('Failed to upload %s: %s' % (file, r))
except:
import traceback
traceback.print_exc()

print('Upload failed, image & description saved to temp.jpeg & temp.txt')

# Generate the description after uploading so that the timestamp is correct.
hash = md5(title.replace(' ', '_').encode('utf-8')).hexdigest()
Expand Down
Binary file modified mem.exe
Binary file not shown.
85 changes: 35 additions & 50 deletions mem/mem.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@
#include "Memory.h"
#include <Windows.h>

// https://stackoverflow.com/q/1387064
// Move this to Memory to replace cassert?
void throwError() {
wchar_t message[256];
FormatMessageW(4096, NULL, GetLastError(), 1024, message, 256, NULL);
fprintf(stderr, "Error: %ls\n", message);
exit(EXIT_FAILURE);
}

std::vector<uint8_t> ParseHexString(const char* str) {
size_t length = strlen(str) / 2; // 1 byte per 2 hex characters
std::vector<uint8_t> bytes(length);
Expand All @@ -26,77 +17,71 @@ std::vector<uint8_t> ParseHexString(const char* str) {
return bytes;
}

std::vector<int64_t> ParseOffsets(int argc, char *argv[]) {
if (argc < 3) {
fprintf(stderr, "Missing read/write offsets (argument 3)");
exit(EXIT_FAILURE);
}

std::vector<int64_t> offsets;
for (int i = 3; i < argc; i++) {
int64_t offset = 0;
int retval = sscanf(argv[i], "%lld", &offset);
if (retval != 1) {
fprintf(stderr, "Could not parse offset %d: '%s'", (i - 3), argv[i]);
exit(EXIT_FAILURE);
}
offsets.push_back(offset);
}

return offsets;
}

void PrintHexString(const std::vector<uint8_t>& bytes) {
for (const uint8_t byte : bytes) {
printf("%02X", byte);
}
printf(" ");
}

int main(int argc, char *argv[]) {
if (argc <= 2) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, "mem sigscan [hex bytes]\n");
fprintf(stderr, "mem read [num bytes] [hex address]");
fprintf(stderr, "mem write [hex address] [hex bytes]");
fprintf(stderr, "mem sigscan <scan bytes> [scan bytes] [scan bytes] ...\n");
fprintf(stderr, "mem read <num bytes> <hex offset> [hex offset] [hex offset] ...\n");
fprintf(stderr, "mem write <hex data> <hex offset> [hex offset] [hex offset] ...\n");
exit(EXIT_FAILURE);
}

Memory memory(L"hlmv"); // Prefix search, matches either hlmv.exe *or* hlmvplusplus.exe

if (strcmp(argv[1], "sigscan") == 0) {
Memory memory(L"hlmv.exe");
for (int i = 2; i < argc; i++) {
memory.AddSigScan(ParseHexString(argv[i]), [&](int64_t offset, int index, const std::vector<uint8_t>& data) {
auto start = data.begin() + index;
auto end = start + min(100, data.size() - index); // 100 bytes or end of data
PrintHexString(std::vector<uint8_t>(start, end));
printf("\n");
memory.AddSigScan(ParseHexString(argv[i]), [](std::vector<uint8_t>& data) {
// If we found a match, limit to 100 bytes. Otherwise we emit no data.
if (data.size() > 0) data.resize(100);
PrintHexString(data);
});
}

memory.ExecuteSigScans();

} else if (strcmp(argv[1], "read") == 0) {
Memory memory(L"hlmv.exe");

int32_t numBytes = 0;
int retval = sscanf(argv[2], "%d", &numBytes);
size_t numBytes = 0;
int retval = sscanf(argv[2], "%zd", &numBytes);
if (retval != 1) {
fprintf(stderr, "Could not parse number of bytes to read: '%s'", argv[2]);
exit(EXIT_FAILURE);
}

std::vector<int64_t> offsets;
for (int i = 3; i < argc; i++) {
int64_t offset = 0;
int retval = sscanf(argv[i], "%lld", &offset);
if (retval != 1) {
fprintf(stderr, "Could not parse offset %d: '%s'", (i - 3), argv[i]);
exit(EXIT_FAILURE);
}

offsets.push_back(offset);
}
std::vector<int64_t> offsets = ParseOffsets(argc, argv);

PrintHexString(memory.ReadData<uint8_t>(offsets, numBytes));

} else if (strcmp(argv[1], "write") == 0) {
Memory memory(L"hlmv.exe");

std::vector<int64_t> offsets;
int64_t offset = 0;
int retval = sscanf(argv[2], "%lld", &offset);
if (retval != 1) {
fprintf(stderr, "Could not parse offset: '%s'", argv[2]);
exit(EXIT_FAILURE);
}

offsets.push_back(offset);

if (argc < 3) {
fprintf(stderr, "Missing write bytes (argument 3)");
exit(EXIT_FAILURE);
}
std::vector<uint8_t> bytes = ParseHexString(argv[2]);
std::vector<int64_t> offsets = ParseOffsets(argc, argv);

std::vector<uint8_t> bytes = ParseHexString(argv[3]);
memory.WriteData<uint8_t>(offsets, bytes);

} else {
Expand Down

0 comments on commit 59c697a

Please sign in to comment.