Merge pull request #14 from zaixia108/main
Change dxcam capture mode to Video mode
This commit is contained in:
+36
-33
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user