Skip to content

Commit

Permalink
python pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
loicmagne committed Jul 26, 2024
1 parent ce7d11d commit cec8276
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 0 deletions.
29 changes: 29 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "xspeedhack"
dynamic = ["version"]
description = "Universal speedhack library for Python"
readme = "README.md"
authors = [{name = "Loïc Magne", email = "[email protected]"}]
keywords = ["speedhack", "games", "speed", "cheat"]
requires-python = ">=3.8"
license = { file = "LICENSE" }
classifiers = [
"Programming Language :: Python :: 3",
]
dependencies = []

[project.optional-dependencies]
dev = [
"pytest",
"ruff",
]

[tool.setuptools]
packages = ["xspeedhack"]

[tool.setuptools.package-data]
"*" = ["*.exe", "*.dll"]
1 change: 1 addition & 0 deletions xspeedhack/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .xspeedhack import SpeedHackClient as Client
81 changes: 81 additions & 0 deletions xspeedhack/xspeedhack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import time
import time
import struct
import platform
import psutil
import subprocess
from pathlib import Path
from typing import Literal
from importlib import resources

if platform.system() == "Windows":
import win32file

def resolve_resource(resource_name: str) -> Path:
return Path(resources.files("bin") / resource_name)

RESOURCES = {
"x86": {
"dll": resolve_resource("SpeedHackDLL32.dll"),
"inj": resolve_resource("xspeedhack32.exe"),
},
"x64": {
"dll": resolve_resource("SpeedHackDLL64.dll"),
"inj": resolve_resource("xspeedhack64.exe"),
},
}

def get_pid(process_name: str) -> int:
for proc in psutil.process_iter():
if proc.name() == process_name:
return proc.pid
raise RuntimeError(f"Process {process_name} not open")

class SpeedHackClient:
PIPE_NAME = r"\\.\pipe\SoulsGymSpeedHackPipe"
def __init__(
self,
process_name: str = None,
process_id: int = None,
arch: Literal["x86", "x64"] = "x64"
):
"""
Args:
process_name: Name of the process to connect to. If provided, process_id is ignored.
process_id: ID of the process to connect to. If provided, process_name is ignored.
arch: Architecture of the process to connect to. "x86" for 32-bit processes and "x64" for 64-bit processes.
"""
assert process_name is not None or process_id is not None, "Either process_name or process_id must be provided"

if process_name is not None:
self.pid = get_pid(process_name)
else:
self.pid = process_id

self.pipe = None
self.arch = arch

self.inject_dll(self.pid)
time.sleep(1) # Give the pipe time to come up after the injection (very conservative)
self.pipe = self._connect_pipe()

def set_speed(self, value: float):
assert value >= 0
win32file.WriteFile(self.pipe, struct.pack("f", value))

def _connect_pipe(self) -> int:
return win32file.CreateFile(self.PIPE_NAME, win32file.GENERIC_WRITE, 0, None,
win32file.OPEN_EXISTING, 0, None)

def __del__(self):
"""Close the pipe handle on deletion."""
if self.pipe is not None:
try:
win32file.CloseHandle(self.pipe)
except TypeError: # Throws NoneType object not callable error for some reason
...

def inject_dll(self, pid: int):
path_dll = RESOURCES[self.arch]["dll"]
path_injector = RESOURCES[self.arch]["inj"]
subprocess.run([path_injector, str(pid), str(path_dll)], check=True)

0 comments on commit cec8276

Please sign in to comment.