Files
ClaudeCode-Roblox-Studio-MCP/inject_bg.py
Gemini AI 2065361e57 Add GTA city builder + background injection API for Roblox Studio
- 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>
2026-03-31 21:39:39 +04:00

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'")