-- Keymapper Finale v5 - Superman Save Edition -- By Taz + ChatGPT -- TEMPORARY: Resets on re-run but supports saving/loading to workspace.KeymapperSaves -- Paste into mobile executor (Delta/Arceus X) and run. -- Services local Players = game:GetService("Players") local UIS = game:GetService("UserInputService") local RunService = game:GetService("RunService") local HttpService = game:GetService("HttpService") local LocalPlayer = Players.LocalPlayer local VIM = game:GetService("VirtualInputManager") -- may error in some environments; handled. local Camera = workspace.CurrentCamera -- ---------------------- Self-clean previous instance ---------------------- local EXISTING = LocalPlayer:FindFirstChild("PlayerGui") and LocalPlayer.PlayerGui:FindFirstChild("KeymapperGUI") if EXISTING then -- attempt to clean up pcall(function() EXISTING:Destroy() end) end -- Also clear any previous saves folder to avoid duplicates only if we want fresh (we keep saves) -- create saves folder if not present local savesFolder = workspace:FindFirstChild("KeymapperSaves") if not savesFolder then savesFolder = Instance.new("Folder") savesFolder.Name = "KeymapperSaves" savesFolder.Parent = workspace end -- Connection management table for safe disconnect on reload local connections = {} local function Connect(signal, fn) local c = signal:Connect(fn) table.insert(connections, c) return c end -- If any previous global connections table existed, try to disconnect them (best-effort) if _G.__KeymapperConnections then for _,c in ipairs(_G.__KeymapperConnections) do pcall(function() c:Disconnect() end) end end _G.__KeymapperConnections = connections -- ---------------------- GUI Setup ---------------------- local ScreenGui = Instance.new("ScreenGui") ScreenGui.Name = "KeymapperGUI" ScreenGui.ResetOnSpawn = false ScreenGui.Parent = LocalPlayer:WaitForChild("PlayerGui") local function makeFrame(parent, size, pos, bg) local f = Instance.new("Frame") f.Size = size f.Position = pos f.BackgroundColor3 = bg or Color3.fromRGB(50,50,50) f.BackgroundTransparency = 0.35 f.BorderSizePixel = 0 f.Active = true f.Draggable = true f.Parent = parent return f end local function makeButton(parent, text, size, pos) local b = Instance.new("TextButton") b.Size = size b.Position = pos b.BackgroundColor3 = Color3.fromRGB(30,30,30) b.BackgroundTransparency = 0.45 b.TextColor3 = Color3.fromRGB(255,255,255) b.Text = text b.Font = Enum.Font.SourceSansBold b.TextSize = 14 b.Active = true b.Draggable = true b.Parent = parent return b end local function makeLabel(parent, text, size, pos) local l = Instance.new("TextLabel") l.Size = size l.Position = pos l.BackgroundTransparency = 1 l.TextColor3 = Color3.fromRGB(230,230,230) l.Text = text l.Font = Enum.Font.SourceSans l.TextSize = 13 l.Parent = parent return l end -- main menu frame local menu = makeFrame(ScreenGui, UDim2.new(0,220,0,380), UDim2.new(0,0,0,90)) menu.Name = "MainMenu" local title = makeLabel(menu, "Keymapper Finale v5", UDim2.new(0,1,0,0), UDim2.new(0,10,0,6)) title.Font = Enum.Font.GothamBold title.TextSize = 16 -- mode state local mode = "Keymap" -- "Keymap" or "WithoutKeymap" local shiftlock = false local keyMappings = {} -- mapKey: {btn = Instance, type="KEY"/"LMB"/"RMB", key = enum} local activeKeys = {} -- keys currently held (for continuous sends) -- UI elements local modeBtn = makeButton(menu, "Mode: Keymap", UDim2.new(0,200,0,36), UDim2.new(0,10,0,34)) local spawnMappedBtn = makeButton(menu, "Spawn Mapped Btn", UDim2.new(0,200,0,36), UDim2.new(0,10,0,80)) local spawnLMBBtn = makeButton(menu, "Spawn LMB Btn", UDim2.new(0,200,0,36), UDim2.new(0,10,0,126)) local spawnRMBBtn = makeButton(menu, "Spawn RMB Btn", UDim2.new(0,200,0,36), UDim2.new(0,10,0,172)) local shiftBtn = makeButton(menu, "Shiftlock: OFF", UDim2.new(0,200,0,36), UDim2.new(0,10,0,218)) local saveBtn = makeButton(menu, "Save Setup", UDim2.new(0,200,0,36), UDim2.new(0,10,0,264)) local loadBtn = makeButton(menu, "Load Setup", UDim2.new(0,200,0,36), UDim2.new(0,10,0,310)) local hideBtn = makeButton(menu, "Hide Menu", UDim2.new(0,200,0,36), UDim2.new(0,10,0,356)) -- floating show menu button local showBtn = makeButton(ScreenGui, "Show Menu", UDim2.new(0,120,0,36), UDim2.new(0,10,0,10)) showBtn.Visible = false -- small prompt frame for inputting save name & confirmations local promptFrame = makeFrame(ScreenGui, UDim2.new(0,300,0,140), UDim2.new(0,120,0,200)) promptFrame.Visible = false local promptLabel = makeLabel(promptFrame, "Prompt", UDim2.new(0,1,0,0), UDim2.new(0,10,0,8)) promptLabel.TextSize = 15 local textBox = Instance.new("TextBox", promptFrame) textBox.Size = UDim2.new(0,280,0,32) textBox.Position = UDim2.new(0,10,0,40) textBox.BackgroundTransparency = 0.5 textBox.Text = "" textBox.ClearTextOnFocus = false textBox.TextColor3 = Color3.fromRGB(255,255,255) textBox.PlaceholderText = "Type slot name..." textBox.Font = Enum.Font.SourceSans textBox.TextSize = 14 local promptConfirm = makeButton(promptFrame, "OK", UDim2.new(0,120,0,32), UDim2.new(0,10,0,82)) local promptCancel = makeButton(promptFrame, "Cancel", UDim2.new(0,120,0,32), UDim2.new(0,150,0,82)) -- small warning display local function showWarning(msg, t) t = t or 4 local warn = makeFrame(ScreenGui, UDim2.new(0,360,0,60), UDim2.new(0.5,-180,0,50)) local label = makeLabel(warn, "⚠️ "..msg, UDim2.new(0,1,0,1), UDim2.new(0,10,0,8)) warn.BackgroundTransparency = 0.25 delay(t, function() pcall(function() warn:Destroy() end) end) end -- ---------------------- Helper functions ---------------------- local function serializeUDim2(u) return {X = u.X.Scale, XS = u.X.Offset, Y = u.Y.Scale, YS = u.Y.Offset} end local function deserializeUDim2(t) return UDim2.new(t.X or 0, t.XS or 0, t.Y or 0, t.YS or 0) end local function serializeButtonData() local out = {} for k,v in pairs(keyMappings) do local keyIdentifier if typeof(k) == "EnumItem" then -- Mouse buttons stored as string "MouseButton1"/"MouseButton2" keyIdentifier = tostring(k) else keyIdentifier = tostring(k.Value) -- KeyCode value number end out[#out+1] = { key = keyIdentifier, type = v.type, pos = serializeUDim2(v.btn.Position), size = serializeUDim2(v.btn.Size), text = v.btn.Text } end return out end local function clearCurrentMappedButtons() for _,v in pairs(keyMappings) do if v.btn and v.btn.Parent then pcall(function() v.btn:Destroy() end) end end keyMappings = {} end -- create mapped button instance (returns the btn) local function createMappedButtonInstance(labelText, pos, size) local btn = Instance.new("TextButton") btn.Size = size or UDim2.new(0,50,0,50) btn.Position = pos or UDim2.new(0.5,-25,0.5,-25) btn.BackgroundColor3 = Color3.fromRGB(100,100,255) btn.BackgroundTransparency = 0.5 btn.TextColor3 = Color3.fromRGB(255,255,255) btn.Text = labelText or "?" btn.Font = Enum.Font.Gotham btn.TextSize = 14 btn.Active = true btn.Draggable = true btn.Parent = ScreenGui return btn end -- save to workspace folder as StringValue local function saveSlot(slotName) if not slotName or slotName == "" then return end local data = { mode = mode, shiftlock = shiftlock, mappings = serializeButtonData() } local json = HttpService:JSONEncode(data) -- create or overwrite StringValue under workspace.KeymapperSaves local sv = savesFolder:FindFirstChild(slotName) if sv then sv.Value = json else local s = Instance.new("StringValue") s.Name = slotName s.Value = json s.Parent = savesFolder end showWarning("Saved slot '"..slotName.."'", 3) end local function listSlots() local t = {} for _,v in ipairs(savesFolder:GetChildren()) do if v:IsA("StringValue") then table.insert(t, v.Name) end end return t end local function loadSlot(slotName) local sv = savesFolder:FindFirstChild(slotName) if not sv then showWarning("Slot not found: "..tostring(slotName), 3) return end local ok, data = pcall(function() return HttpService:JSONDecode(sv.Value) end) if not ok or not data then showWarning("Failed to decode slot data.",3) return end -- confirm overwrite (we show a quick warning and wait for confirm via prompt) -- we'll ask user via promptFrame; reuse prompt promptLabel.Text = "Loading will overwrite current setup.\nType YES to confirm." textBox.Text = "" promptFrame.Visible = true promptConfirm.Visible = true promptCancel.Visible = true local connConfirm connConfirm = Connect(promptConfirm.MouseButton1Click, function() if textBox.Text:upper() == "YES" then -- proceed to load clearCurrentMappedButtons() mode = data.mode or "Keymap" modeBtn.Text = "Mode: "..(mode == "Keymap" and "Keymap" or "Without Keymap") shiftlock = data.shiftlock or false shiftBtn.Text = "Shiftlock: "..(shiftlock and "ON" or "OFF") -- create mapped buttons for _,m in ipairs(data.mappings or {}) do local pos = deserializeUDim2(m.pos) local size = deserializeUDim2(m.size) local b = createMappedButtonInstance(m.text or "?", pos, size) local keyId = m.key if string.find(keyId, "Mouse") then -- mouse mapping stored as "Enum.UserInputType.MouseButton1" if keyId:find("MouseButton1") then keyMappings[Enum.UserInputType.MouseButton1] = {btn = b, type = "LMB"} elseif keyId:find("MouseButton2") then keyMappings[Enum.UserInputType.MouseButton2] = {btn = b, type = "RMB"} end else -- key code stored as number string local num = tonumber(keyId) if num then local kc = Enum.KeyCode:GetEnumItems()[1] -- dummy -- we need to find KeyCode by value; brute force for _,e in ipairs(Enum.KeyCode:GetEnumItems()) do if e.Value == num then kc = e break end end keyMappings[kc] = {btn = b, type = "KEY", key = kc} end end end promptFrame.Visible = false showWarning("Loaded slot '"..slotName.."'", 3) else showWarning("Load canceled.",2) promptFrame.Visible = false end pcall(function() connConfirm:Disconnect() end) end) Connect(promptCancel.MouseButton1Click, function() promptFrame.Visible = false pcall(function() connConfirm:Disconnect() end) end) end -- ---------------------- Fail Detection for VIM availability ---------------------- local function checkVIMAvailable() local ok, err = pcall(function() -- try a harmless call VIM:SendKeyEvent(true, Enum.KeyCode.Unknown, false, game) -- and mouse VIM:SendMouseButtonEvent(0,0,0,true,game,1) end) return ok end if not checkVIMAvailable() then showWarning("⚠️ VirtualInputManager not available. You need a real keymapper if this doesn't work.", 6) end -- ---------------------- Create mapped buttons handlers ---------------------- -- When user clicks spawnMappedBtn, we create a button that listens for next keyboard press to bind spawnMappedBtn.MouseButton1Click:Connect(function() local b = createMappedButtonInstance("?", UDim2.new(0.5,-25,0.5,-25), nil) local listening = true local connA, connB connA = Connect(b.MouseButton1Click, function() b.Text = "Press Key..." listening = true end) connB = Connect(UIS.InputBegan, function(input, gp) if listening and input.UserInputType == Enum.UserInputType.Keyboard then local kc = input.KeyCode b.Text = kc.Name keyMappings[kc] = {btn = b, type = "KEY", key = kc} listening = false end end) end) -- spawn LMB spawnLMBBtn.MouseButton1Click:Connect(function() local b = createMappedButtonInstance("LMB", UDim2.new(0.6,0,0.5,-25), nil) keyMappings[Enum.UserInputType.MouseButton1] = {btn = b, type = "LMB"} end) -- spawn RMB spawnRMBBtn.MouseButton1Click:Connect(function() local b = createMappedButtonInstance("RMB", UDim2.new(0.7,0,0.5,-25), nil) keyMappings[Enum.UserInputType.MouseButton2] = {btn = b, type = "RMB"} end) -- shiftlock toggle shiftBtn.MouseButton1Click:Connect(function() shiftlock = not shiftlock shiftBtn.Text = "Shiftlock: " .. (shiftlock and "ON" or "OFF") LocalPlayer.DevEnableMouseLock = shiftlock if shiftlock then UIS.MouseBehavior = Enum.MouseBehavior.LockCenter UIS.MouseIconEnabled = false else UIS.MouseBehavior = Enum.MouseBehavior.Default UIS.MouseIconEnabled = true end end) -- Hide/Show menu hideBtn.MouseButton1Click:Connect(function() menu.Visible = false showBtn.Visible = true end) showBtn.MouseButton1Click:Connect(function() menu.Visible = true showBtn.Visible = false end) -- Save button: prompt for slot name saveBtn.MouseButton1Click:Connect(function() promptLabel.Text = "Enter slot name to save:" textBox.Text = "" promptFrame.Visible = true local conn conn = Connect(promptConfirm.MouseButton1Click, function() local name = textBox.Text or "" if name == "" then showWarning("Slot name cannot be empty", 2) else saveSlot(name) promptFrame.Visible = false end pcall(function() conn:Disconnect() end) end) Connect(promptCancel.MouseButton1Click, function() promptFrame.Visible = false pcall(function() conn:Disconnect() end) end) end) -- Load button: show list of slots in prompt and ask to type slot name loadBtn.MouseButton1Click:Connect(function() local slots = listSlots() if #slots == 0 then showWarning("No save slots found.", 3) return end promptLabel.Text = "Available slots:\n- "..table.concat(slots, "\n- ").."\n\nType slot name to load:" textBox.Text = "" promptFrame.Visible = true local conn conn = Connect(promptConfirm.MouseButton1Click, function() local name = textBox.Text or "" if name == "" then showWarning("Type a slot name to load",2) else promptFrame.Visible = false loadSlot(name) end pcall(function() conn:Disconnect() end) end) Connect(promptCancel.MouseButton1Click, function() promptFrame.Visible = false pcall(function() conn:Disconnect() end) end) end) -- ---------------------- Input Handling (multi-key + hold support) ---------------------- -- We'll track activeKeys and spawn coroutines that send events while held. local sendIntervals = 0.08 -- seconds between repeated forced events while holding local function sendKeyOnce(keycode) local ok, err = pcall(function() VIM:SendKeyEvent(true, keycode, false, game) task.wait(0.03) VIM:SendKeyEvent(false, keycode, false, game) end) if not ok then -- VIM failed; warn once showWarning("Our key sending is blocked. You might need a real keymapper.", 4) end end local function sendMouseClickAtButton(btn, mouseButtonType) local success, err = pcall(function() local pos = btn.AbsolutePosition + (btn.AbsoluteSize/2) VIM:SendMouseButtonEvent(pos.X, pos.Y, mouseButtonType, true, game, 1) task.wait(0.02) VIM:SendMouseButtonEvent(pos.X, pos.Y, mouseButtonType, false, game, 1) end) if not success then showWarning("Mouse send blocked. You might need a real keymapper.", 4) end end -- spawn a loop to repeatedly send while key held (for WithoutKeymap Mode) local holdingLoops = {} -- map KeyCode -> coroutine handle local function startHoldingKey(kc) if holdingLoops[kc] then return end local co = coroutine.create(function() while activeKeys[kc] do sendKeyOnce(kc) task.wait(sendIntervals) end holdingLoops[kc] = nil end) holdingLoops[kc] = co coroutine.resume(co) end local function stopHoldingKey(kc) activeKeys[kc] = nil -- coroutine will exit automatically end -- InputBegan: mark active, handle according to mode Connect(UIS.InputBegan, function(input, gp) local uit = input.UserInputType if uit == Enum.UserInputType.Keyboard then local kc = input.KeyCode -- mark active activeKeys[kc] = true -- if WithoutKeymap: force-send (and start repeat while held) if mode == "WithoutKeymap" then -- start continuous sending while held startHoldingKey(kc) end -- if Keymap mode and this key is mapped to a UI button, simulate mouse tap if mode == "Keymap" then local map = keyMappings[kc] if map and map.btn and map.type == "KEY" then spawn(function() sendMouseClickAtButton(map.btn, 0) end) end end elseif uit == Enum.UserInputType.MouseButton1 or uit == Enum.UserInputType.MouseButton2 then -- mouse buttons: treat as user press; if mapped in Keymap mode, trigger mapped on-screen button if mode == "Keymap" then local map = keyMappings[uit] if map and map.btn then local mb = (uit == Enum.UserInputType.MouseButton2) and 1 or 0 spawn(function() sendMouseClickAtButton(map.btn, mb) end) end else -- WithoutKeymap: we can try to force a mouse event for every mouse click (non-blocking) local ok, err = pcall(function() -- send a raw click (at current center of screen) local x = Camera.ViewportSize.X/2 local y = Camera.ViewportSize.Y/2 local mb = (uit == Enum.UserInputType.MouseButton2) and 1 or 0 VIM:SendMouseButtonEvent(x,y,mb,true,game,1) task.wait(0.02) VIM:SendMouseButtonEvent(x,y,mb,false,game,1) end) if not ok then -- ignore silently or show warn end end end end) -- InputEnded: stop holding Connect(UIS.InputEnded, function(input, gp) if input.UserInputType == Enum.UserInputType.Keyboard then local kc = input.KeyCode stopHoldingKey(kc) end end) -- ---------------------- Mode Switch & showing/hiding mapped buttons on mode change ---------------------- modeBtn.MouseButton1Click:Connect(function() if mode == "Keymap" then mode = "WithoutKeymap" modeBtn.Text = "Mode: WithoutKeymap" -- hide mapped buttons visually (but keep data) for k,v in pairs(keyMappings) do if v.btn then v.btn.Visible = false end end else mode = "Keymap" modeBtn.Text = "Mode: Keymap" for k,v in pairs(keyMappings) do if v.btn then v.btn.Visible = true end end end end) -- ---------------------- Persistence: when re-run this script will have removed prior GUI and listeners ---------------------- -- Add small help label local help = makeLabel(menu, "Save slots stored in workspace.KeymapperSaves\nUse Save/Load with custom names.\nType YES to confirm loads.", UDim2.new(0,1,0,80), UDim2.new(0,10,0,400)) help.TextSize = 11 -- ---------------------- Click-to-edit mapped buttons (allow rebind & delete) ---------------------- -- When user clicks a mapped on-screen button, open a tiny context menu local function attachMappedButtonContext(btn, keyRep) -- show two small buttons near the btn: Rebind, Delete btn.MouseButton2Click:Connect(function() -- Right click menu (if supported) -- For simplicity, just prompt for rebind or deletion via prompt promptLabel.Text = "Type NEW key name to rebind or DELETE to remove." textBox.Text = "" promptFrame.Visible = true local connC connC = Connect(promptConfirm.MouseButton1Click, function() local v = textBox.Text if v == "" then showWarning("No input.",2) elseif v:upper() == "DELETE" then -- remove mapping entry for k,m in pairs(keyMappings) do if m.btn == btn then keyMappings[k] = nil break end end pcall(function() btn:Destroy() end) promptFrame.Visible = false else -- try to find KeyCode by name local found = nil for _,kc in ipairs(Enum.KeyCode:GetEnumItems()) do if kc.Name:upper() == v:upper() then found = kc break end end if found then -- find previous mapping and update keyMappings for k,m in pairs(keyMappings) do if m.btn == btn then keyMappings[k] = nil keyMappings[found] = {btn = btn, type = "KEY", key = found} btn.Text = found.Name break end end promptFrame.Visible = false else showWarning("Key not found: "..v, 2) end end pcall(function() connC:Disconnect() end) end) Connect(promptCancel.MouseButton1Click, function() promptFrame.Visible = false pcall(function() connC:Disconnect() end) end) end) end -- Hook creation so context menus attach -- Whenever a mapped UI button is created we should attach context. We'll attach for existing ones too. for k,v in pairs(keyMappings) do if v.btn then attachMappedButtonContext(v.btn, k) end end -- Also attach when createMappedButtonInstance used earlier in load; hook creation: override that function? Instead we will periodically ensure attachments Connect(RunService.Heartbeat, function() for k,v in pairs(keyMappings) do if v and v.btn and not v._contextAttached then pcall(function() attachMappedButtonContext(v.btn, k) end) v._contextAttached = true end end -- ensure shiftlock remains active when set if shiftlock then UIS.MouseBehavior = Enum.MouseBehavior.LockCenter UIS.MouseIconEnabled = false end end) -- ---------------------- Final message ---------------------- showWarning("Keymapper Finale v5 loaded. Use Save to create slots. Run again to replace.", 5) -- End of script