Merge pull request #14 from zaixia108/main

Change dxcam capture mode to Video mode
This commit is contained in:
lm
2025-12-23 20:53:36 +01:00
committed by GitHub
+36 -33
View File
@@ -21,44 +21,45 @@ import win32gui
import win32api import win32api
import win32con import win32con
def get_process_info(process_name): def get_process_info(process_name):
""" """
Get process information for a given process name on Windows. Get process information for a given process name on Windows.
Args: Args:
process_name (str): Name of the process (e.g., "isaac-ng.exe") process_name (str): Name of the process (e.g., "isaac-ng.exe")
Returns: Returns:
list: List of dictionaries containing PID, window_name, and architecture list: List of dictionaries containing PID, window_name, and architecture
for each matching process. Returns empty list if no process found. for each matching process. Returns empty list if no process found.
""" """
results = [] results = []
# Find all processes with the given name # Find all processes with the given name
for proc in psutil.process_iter(['pid', 'name']): for proc in psutil.process_iter(['pid', 'name']):
try: try:
if proc.info['name'].lower() == process_name.lower(): if proc.info['name'].lower() == process_name.lower():
pid = proc.info['pid'] pid = proc.info['pid']
# Get architecture # Get architecture
try: try:
# Check if process is 32-bit or 64-bit # Check if process is 32-bit or 64-bit
process_handle = win32api.OpenProcess( process_handle = win32api.OpenProcess(
win32con.PROCESS_QUERY_INFORMATION, win32con.PROCESS_QUERY_INFORMATION,
False, False,
pid pid
) )
is_wow64 = win32process.IsWow64Process(process_handle) is_wow64 = win32process.IsWow64Process(process_handle)
win32api.CloseHandle(process_handle) win32api.CloseHandle(process_handle)
# On 64-bit Windows: WOW64 means "Windows 32-bit on Windows 64-bit", i.e. a 32-bit process # On 64-bit Windows: WOW64 means "Windows 32-bit on Windows 64-bit", i.e. a 32-bit process
architecture = "x86" if is_wow64 else "x64" architecture = "x86" if is_wow64 else "x64"
except: except:
architecture = "unknown" architecture = "unknown"
# Find windows associated with this PID # Find windows associated with this PID
windows = [] windows = []
def enum_window_callback(hwnd, pid_to_find): def enum_window_callback(hwnd, pid_to_find):
_, found_pid = win32process.GetWindowThreadProcessId(hwnd) _, found_pid = win32process.GetWindowThreadProcessId(hwnd)
if found_pid == pid_to_find: if found_pid == pid_to_find:
@@ -70,13 +71,13 @@ def get_process_info(process_name):
'visible': win32gui.IsWindowVisible(hwnd) 'visible': win32gui.IsWindowVisible(hwnd)
}) })
return True return True
# Find all windows for this PID # Find all windows for this PID
try: try:
win32gui.EnumWindows(enum_window_callback, pid) win32gui.EnumWindows(enum_window_callback, pid)
except: except:
pass pass
# Choose the best window # Choose the best window
window_name = None window_name = None
if windows: if windows:
@@ -85,31 +86,31 @@ def get_process_info(process_name):
print("Using heuristics to select the correct window...") print("Using heuristics to select the correct window...")
# Filter out common proxy/helper windows # Filter out common proxy/helper windows
proxy_keywords = ['d3dproxywindow', 'proxy', 'helper', 'overlay'] proxy_keywords = ['d3dproxywindow', 'proxy', 'helper', 'overlay']
# First try to find a visible window without proxy keywords # First try to find a visible window without proxy keywords
for win in windows: for win in windows:
if not any(keyword in win['title'].lower() for keyword in proxy_keywords): if not any(keyword in win['title'].lower() for keyword in proxy_keywords):
window_name = win['title'] window_name = win['title']
break break
# If no good window found, just use the first one # If no good window found, just use the first one
if window_name is None and windows: if window_name is None and windows:
window_name = windows[0]['title'] window_name = windows[0]['title']
results.append({ results.append({
'pid': pid, 'pid': pid,
'window_name': window_name, 'window_name': window_name,
'architecture': architecture 'architecture': architecture
}) })
except (psutil.NoSuchProcess, psutil.AccessDenied): except (psutil.NoSuchProcess, psutil.AccessDenied):
continue continue
if len(results) == 0: if len(results) == 0:
raise ValueError(f"No process found with name: {process_name}") raise ValueError(f"No process found with name: {process_name}")
elif len(results) > 1: elif len(results) > 1:
print(f"Warning: Multiple processes found with name '{process_name}'. Returning first match.") print(f"Warning: Multiple processes found with name '{process_name}'. Returning first match.")
return results[0] return results[0]
@@ -338,6 +339,7 @@ class GamepadEmulator:
self.gamepad.reset() self.gamepad.reset()
self.gamepad.update() self.gamepad.update()
class PyautoguiScreenshotBackend: class PyautoguiScreenshotBackend:
def __init__(self, bbox): def __init__(self, bbox):
@@ -346,15 +348,17 @@ class PyautoguiScreenshotBackend:
def screenshot(self): def screenshot(self):
return pyautogui.screenshot(region=self.bbox) return pyautogui.screenshot(region=self.bbox)
class DxcamScreenshotBackend: class DxcamScreenshotBackend:
def __init__(self, bbox): def __init__(self, bbox, fps):
import dxcam import dxcam
self.camera = dxcam.create() self.camera = dxcam.create()
self.bbox = bbox self.bbox = bbox
self.last_screenshot = None self.last_screenshot = None
self.camera.start(region=self.bbox, target_fps=fps, video_mode=True)
def screenshot(self): def screenshot(self):
screenshot = self.camera.grab(region=self.bbox) screenshot = self.camera.get_latest_frame()
if screenshot is None: if screenshot is None:
print("DXCAM failed to capture frame, trying to use the latest screenshot") print("DXCAM failed to capture frame, trying to use the latest screenshot")
if self.last_screenshot is not None: if self.last_screenshot is not None:
@@ -381,15 +385,15 @@ class GamepadEnv(Env):
""" """
def __init__( def __init__(
self, self,
game, game,
image_height=1440, image_height=1440,
image_width=2560, image_width=2560,
controller_type="xbox", controller_type="xbox",
game_speed=1.0, game_speed=1.0,
env_fps=10, env_fps=10,
async_mode=True, async_mode=True,
screenshot_backend="dxcam", screenshot_backend="dxcam",
): ):
super().__init__() super().__init__()
@@ -414,12 +418,12 @@ class GamepadEnv(Env):
self.game_arch = proc_info["architecture"] self.game_arch = proc_info["architecture"]
self.game_window_name = proc_info["window_name"] self.game_window_name = proc_info["window_name"]
print(f"Game process found: {self.game} (PID: {self.game_pid}, Arch: {self.game_arch}, Window: {self.game_window_name})") print(
f"Game process found: {self.game} (PID: {self.game_pid}, Arch: {self.game_arch}, Window: {self.game_window_name})")
if self.game_pid is None: if self.game_pid is None:
raise Exception(f"Could not find PID for game: {game}") raise Exception(f"Could not find PID for game: {game}")
self.observation_space = Box( self.observation_space = Box(
low=0, high=255, shape=(self.image_height, self.image_width, 3), dtype="uint8" low=0, high=255, shape=(self.image_height, self.image_width, 3), dtype="uint8"
) )
@@ -464,20 +468,19 @@ class GamepadEnv(Env):
self.game_window.activate() self.game_window.activate()
l, t, r, b = self.game_window.left, self.game_window.top, self.game_window.right, self.game_window.bottom l, t, r, b = self.game_window.left, self.game_window.top, self.game_window.right, self.game_window.bottom
self.bbox = (l, t, r-l, b-t) self.bbox = (l, t, r - l, b - t)
# Initialize speedhack client if using DLL injection # Initialize speedhack client if using DLL injection
self.speedhack_client = xsh.Client(process_id=self.game_pid, arch=self.game_arch) self.speedhack_client = xsh.Client(process_id=self.game_pid, arch=self.game_arch)
# Get the screenshot backend # Get the screenshot backend
if screenshot_backend == "dxcam": if screenshot_backend == "dxcam":
self.screenshot_backend = DxcamScreenshotBackend(self.bbox) self.screenshot_backend = DxcamScreenshotBackend(self.bbox, self.env_fps)
elif screenshot_backend == "pyautogui": elif screenshot_backend == "pyautogui":
self.screenshot_backend = PyautoguiScreenshotBackend(self.bbox) self.screenshot_backend = PyautoguiScreenshotBackend(self.bbox)
else: else:
raise ValueError("Unsupported screenshot backend. Use 'dxcam' or 'pyautogui'.") raise ValueError("Unsupported screenshot backend. Use 'dxcam' or 'pyautogui'.")
def calculate_step_duration(self): def calculate_step_duration(self):
""" """
Calculate the step duration based on game speed and environment FPS. Calculate the step duration based on game speed and environment FPS.