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