- inject_gta_city.py: Full GTA-style city with 20 buildings, roads, cars, street lights, traffic lights, trees, 15 human enemies with varied skin/clothing, and 10 COD weapons with visible gun models - inject_bg.py: Background injection using SendMessage/PostMessage Win32 API - inject_bg2.py: PostMessage approach targeting main window for WPF apps - inject_cod_final.py: Working COD game injection (7-step sequential) - cod_inject.py: Combined COD game builder with proper Studio launch - roblox-fps-p1-p6: Split Lua scripts for multi-part injection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
215 lines
7.1 KiB
Python
215 lines
7.1 KiB
Python
"""
|
|
Background injection into Roblox Studio using SendMessage/PostMessage.
|
|
Works even when Studio is minimized or behind other windows.
|
|
Finds the command bar child control and sends text directly to it.
|
|
"""
|
|
import ctypes, ctypes.wintypes, subprocess, time, sys, os
|
|
|
|
user32 = ctypes.windll.user32
|
|
WM_SETTEXT = 0x000C
|
|
WM_KEYDOWN = 0x0100
|
|
WM_KEYUP = 0x0101
|
|
WM_CHAR = 0x0102
|
|
EM_GETLINECOUNT = 0x00BA
|
|
EM_LINELENGTH = 0x00C1
|
|
|
|
def find_studio():
|
|
target = [None]
|
|
def cb(hwnd, _):
|
|
l = user32.GetWindowTextLengthW(hwnd)
|
|
if l > 0:
|
|
buf = ctypes.create_unicode_buffer(l + 1)
|
|
user32.GetWindowTextW(hwnd, buf, l + 1)
|
|
if "Roblox Studio" in buf.value:
|
|
target[0] = hwnd
|
|
return False
|
|
return True
|
|
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)
|
|
user32.EnumWindows(WNDENUMPROC(cb), 0)
|
|
return target[0]
|
|
|
|
def find_command_bar(parent_hwnd):
|
|
"""Find the command bar edit control inside Studio."""
|
|
result = [None]
|
|
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)
|
|
|
|
def enum_children(hwnd, _):
|
|
# Check class name - Roblox command bar is typically an edit control
|
|
buf = ctypes.create_unicode_buffer(256)
|
|
user32.GetClassNameW(hwnd, buf, 256)
|
|
class_name = buf.value
|
|
|
|
# Get control text
|
|
l = user32.GetWindowTextLengthW(hwnd)
|
|
if l > 0:
|
|
tbuf = ctypes.create_unicode_buffer(l + 1)
|
|
user32.GetWindowTextW(hwnd, tbuf, l + 1)
|
|
|
|
# Look for edit controls or text input controls
|
|
# Roblox Studio uses WPF which hosts Win32 controls
|
|
rect = ctypes.wintypes.RECT()
|
|
user32.GetWindowRect(hwnd, ctypes.byref(rect))
|
|
h = rect.bottom - rect.top
|
|
|
|
# Command bar is a thin single-line edit at the bottom
|
|
if class_name in ("Edit", "TextBox", "WindowsForms10.Edit", "WpfTextEdit"):
|
|
if h > 10 and h < 60:
|
|
result[0] = hwnd
|
|
return False
|
|
|
|
# Recurse into children
|
|
user32.EnumChildWindows(hwnd, WNDENUMPROC(enum_children), 0)
|
|
return True
|
|
|
|
user32.EnumChildWindows(parent_hwnd, WNDENUMPROC(enum_children), 0)
|
|
return result[0]
|
|
|
|
def find_all_children(parent_hwnd):
|
|
"""List all child windows for debugging."""
|
|
children = []
|
|
WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.wintypes.HWND, ctypes.wintypes.LPARAM)
|
|
|
|
def enum_children(hwnd, _):
|
|
buf = ctypes.create_unicode_buffer(256)
|
|
user32.GetClassNameW(hwnd, buf, 256)
|
|
class_name = buf.value
|
|
|
|
l = user32.GetWindowTextLengthW(hwnd)
|
|
text = ""
|
|
if l > 0:
|
|
tbuf = ctypes.create_unicode_buffer(l + 1)
|
|
user32.GetWindowTextW(hwnd, tbuf, l + 1)
|
|
text = tbuf.value
|
|
|
|
rect = ctypes.wintypes.RECT()
|
|
user32.GetWindowRect(hwnd, ctypes.byref(rect))
|
|
w = rect.right - rect.left
|
|
h = rect.bottom - rect.top
|
|
|
|
visible = user32.IsWindowVisible(hwnd)
|
|
enabled = user32.IsWindowEnabled(hwnd)
|
|
|
|
children.append({
|
|
'hwnd': hwnd,
|
|
'class': class_name,
|
|
'text': text[:50],
|
|
'size': f"{w}x{h}",
|
|
'pos': f"({rect.left},{rect.top})",
|
|
'visible': visible,
|
|
'enabled': enabled
|
|
})
|
|
return True
|
|
|
|
user32.EnumChildWindows(parent_hwnd, WNDENUMPROC(enum_children), 0)
|
|
return children
|
|
|
|
def inject_background(code, target_hwnd):
|
|
"""Inject Lua code into a specific child control using SendMessage."""
|
|
# Put code on clipboard
|
|
tmp = os.path.join(os.environ["TEMP"], "rbx.lua")
|
|
with open(tmp, "w", encoding="utf-8") as f:
|
|
f.write(code)
|
|
subprocess.run(["powershell", "-Command", f"Get-Content '{tmp}' -Raw | Set-Clipboard"],
|
|
capture_output=True, timeout=10)
|
|
time.sleep(0.2)
|
|
|
|
# Send Ctrl+A (select all) then Ctrl+V (paste) then Enter via PostMessage
|
|
# PostMessage works on background windows
|
|
VK_CONTROL = 0x11
|
|
VK_A = 0x41
|
|
VK_V = 0x56
|
|
VK_RETURN = 0x0D
|
|
|
|
# Select all: Ctrl+A
|
|
user32.PostMessageW(target_hwnd, WM_KEYDOWN, VK_CONTROL, 0)
|
|
user32.PostMessageW(target_hwnd, WM_KEYDOWN, VK_A, 0)
|
|
time.sleep(0.02)
|
|
user32.PostMessageW(target_hwnd, WM_KEYUP, VK_A, 0)
|
|
user32.PostMessageW(target_hwnd, WM_KEYUP, VK_CONTROL, 0)
|
|
time.sleep(0.05)
|
|
|
|
# Paste: Ctrl+V
|
|
user32.PostMessageW(target_hwnd, WM_KEYDOWN, VK_CONTROL, 0)
|
|
user32.PostMessageW(target_hwnd, WM_KEYDOWN, VK_V, 0)
|
|
time.sleep(0.02)
|
|
user32.PostMessageW(target_hwnd, WM_KEYUP, VK_V, 0)
|
|
user32.PostMessageW(target_hwnd, WM_KEYUP, VK_CONTROL, 0)
|
|
time.sleep(0.5)
|
|
|
|
# Execute: Enter
|
|
user32.PostMessageW(target_hwnd, WM_KEYDOWN, VK_RETURN, 0)
|
|
time.sleep(0.02)
|
|
user32.PostMessageW(target_hwnd, WM_KEYUP, VK_RETURN, 0)
|
|
time.sleep(2)
|
|
|
|
# ═══════════════════════════════════════
|
|
# MAIN
|
|
# ═══════════════════════════════════════
|
|
hwnd = find_studio()
|
|
if not hwnd:
|
|
print("ERROR: Roblox Studio not found!")
|
|
sys.exit(1)
|
|
|
|
print(f"Studio window: {hwnd}")
|
|
|
|
# List all child windows to find command bar
|
|
print("\nSearching for command bar control...")
|
|
children = find_all_children(hwnd)
|
|
|
|
# Filter for likely candidates: visible, enabled, small height (thin bar), near bottom
|
|
print(f"Found {len(children)} child windows")
|
|
|
|
# Get main window rect
|
|
main_rect = ctypes.wintypes.RECT()
|
|
user32.GetWindowRect(hwnd, ctypes.byref(main_rect))
|
|
main_bottom = main_rect.bottom
|
|
main_top = main_rect.top
|
|
|
|
# Find candidates near bottom of window, visible, reasonable size
|
|
candidates = []
|
|
for c in children:
|
|
if not c['visible']:
|
|
continue
|
|
try:
|
|
h = int(c['size'].split('x')[1])
|
|
w = int(c['size'].split('x')[0])
|
|
except:
|
|
continue
|
|
if h < 5 or h > 80:
|
|
continue
|
|
if w < 100:
|
|
continue
|
|
# Parse position
|
|
pos = c['pos'].strip('()')
|
|
try:
|
|
y = int(pos.split(',')[1])
|
|
except:
|
|
continue
|
|
# Check if near bottom of main window
|
|
if y > main_bottom - 100:
|
|
candidates.append(c)
|
|
print(f" CANDIDATE: hwnd={c['hwnd']} class={c['class']} text='{c['text']}' size={c['size']} pos={c['pos']}")
|
|
|
|
if not candidates:
|
|
print("\nNo command bar candidates found. Listing ALL visible children near bottom:")
|
|
for c in children:
|
|
if not c['visible']:
|
|
continue
|
|
pos = c['pos'].strip('()')
|
|
try:
|
|
y = int(pos.split(',')[1])
|
|
except:
|
|
continue
|
|
if y > main_bottom - 150:
|
|
print(f" hwnd={c['hwnd']} class={c['class']} text='{c['text']}' size={c['size']} pos={c['pos']}")
|
|
|
|
# Try to test each candidate with a simple print
|
|
test_code = 'print("BG_INJECT_OK")'
|
|
print(f"\nTesting candidates with: {test_code}")
|
|
|
|
for c in candidates:
|
|
target = c['hwnd']
|
|
print(f"\n Trying hwnd={target} class={c['class']}...")
|
|
inject_background(test_code, target)
|
|
print(f" Sent to {target}. Check Studio Output for 'BG_INJECT_OK'")
|