
Key RequiredUniversal

-- 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 scriptNo comments yet for this script.



