-- PROJECT GHOST FULL
-- Safe runtime build: no bypasses, no executor hooks, no bytecode dumping.
-- Green/Black Runtime Panel + Scanner Tool + Script Viewer + Action Menu + Neon Ghost Background
-- Put this LocalScript in:
-- StarterPlayer > StarterPlayerScripts > ProjectGhostClient.lua
--
-- Notes:
-- Pressing 1 only equips the Ghost Scanner tool.
-- The UI opens only when you click/use the scanner on an object, or when clicking the ghost icon.
-- Roblox gameplay LocalScripts cannot read real Script.Source.
-- To show code in the Script Viewer, use CODE_REGISTRY or add Attributes:
-- GhostSource = "your code text"
-- GhostInfo = "your scan info"
-- For ModuleScript reflection, set Attribute:
-- GhostInspectable = true
local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Stats = game:GetService("Stats")
local RS = game:GetService("ReplicatedStorage")
local Lighting = game:GetService("Lighting")
local TweenService = game:GetService("TweenService")
local player = Players.LocalPlayer
local mouse = player:GetMouse()
local playerGui = player:WaitForChild("PlayerGui")
local backpack = player:WaitForChild("Backpack")
local GREEN = Color3.fromRGB(0, 255, 120)
local GREEN_DIM = Color3.fromRGB(0, 150, 70)
local GREEN_DARK = Color3.fromRGB(0, 55, 28)
local BLACK = Color3.fromRGB(0, 0, 0)
local BLACK_2 = Color3.fromRGB(3, 7, 5)
local BLACK_3 = Color3.fromRGB(7, 14, 10)
local WHITE_GREEN = Color3.fromRGB(170, 255, 205)
local PANEL_LINE = Color3.fromRGB(0, 95, 45)
local CYAN = Color3.fromRGB(80, 210, 255)
local WARNING = Color3.fromRGB(255, 210, 90)
local BAD = Color3.fromRGB(255, 80, 80)
local ALLOW_ALL_FOR_TESTING = true
local OWNER_USER_IDS = {
[player.UserId] = true,
-- [123456789] = true,
}
if not ALLOW_ALL_FOR_TESTING and not OWNER_USER_IDS[player.UserId] then
return
end
local PANEL_SCALE_X = 0.82
local PANEL_SCALE_Y = 0.78
local PANEL_POS_X = 0.09
local PANEL_POS_Y = 0.10
local MAX_CLONE_PARTS = 80
local CLONE_OFFSET = Vector3.new(5, 1, 0)
local TOGGLE_KEY = Enum.KeyCode.RightShift
local PROJECT_GHOST_VERSION = "v3.5"
local SpawnCarName = "SpawnCar"
local SpawnCar = RS:FindFirstChild(SpawnCarName)
local ghostConnections = {}
local refreshCurrent = nil
local refreshBusy = false
local refreshQueued = false
local scanToken = 0
actionMenuFollowPart = nil
local undoStack = {}
local redoStack = {}
local MAX_HISTORY = 40
--=====================================================
-- CODE REGISTRY
--=====================================================
local CODE_REGISTRY = {
{
Name = "SpawnCar Remote",
MatchNames = {"SpawnCar"},
Description = "Vehicle spawning RemoteEvent.",
AccessLevel = "GHOST_ROOT",
Source = [[
local RS = game:GetService("ReplicatedStorage")
local SpawnCar = RS:WaitForChild("SpawnCar")
SpawnCar:FireServer(0, "SuperCar")
]],
},
{
Name = "Vehicle Spawn Example",
MatchNames = {"SuperCar", "Jeep", "PickUp", "Van", "MegaJeep", "Helicopter"},
Description = "Registered vehicle spawn code.",
AccessLevel = "VEHICLE_ADMIN",
Source = [[
local RS = game:GetService("ReplicatedStorage")
local SpawnCar = RS:WaitForChild("SpawnCar")
local vehicle = "SuperCar"
SpawnCar:FireServer(0, vehicle)
]],
},
{
Name = "Ghost Scanner Tool",
MatchNames = {"Ghost Scanner"},
Description = "Tool used to inspect and duplicate selected client-visible objects.",
AccessLevel = "SCANNER",
Source = [[
-- Hold the Ghost Scanner.
-- Press 1 only equips the tool.
-- Click an object to scan it and open Project Ghost.
-- Use the popup target menu or the Project Ghost panel.
]],
},
{
Name = "Lighting Blackout",
MatchNames = {"Lighting"},
Description = "Dark hacking-game atmosphere script.",
AccessLevel = "WORLD_EDIT",
Source = [[
local Lighting = game:GetService("Lighting")
Lighting.ClockTime = 0
Lighting.Brightness = 0.5
]],
},
{
Name = "Generic Model Profile",
MatchClasses = {"Model"},
Description = "Generic model profile. Add GhostSource attribute for custom source.",
AccessLevel = "PARTIAL",
Source = [[
-- No exact registered source found.
-- Add Attribute GhostSource to this object to display custom code.
-- Add Attribute GhostInfo to display custom scan notes.
]],
},
}
--=====================================================
-- STATE
--=====================================================
local selectedObject = nil
local selectedFromTool = false
local selectedScriptObject = nil
local ghostLogs = {}
local remoteLogs = {}
local remoteStats = {}
local selectedRemoteObject = nil
local selectedStatObject = nil
local statScanResults = {}
local setSelectedStatValue = nil
local refreshStatsScanner = nil
local scanStatus = "IDLE"
local fps = 0
local lastFrame = os.clock()
local selectedVehicle = "SuperCar"
local vehicles = {"SuperCar", "Jeep", "PickUp", "Van", "MegaJeep", "Helicopter"}
local lastSpawn = 0
local spawnCooldown = 1.0
local trackedObjects = {}
local trackedObjectLog = {}
local scanHistory = {}
local originalLighting = {
ClockTime = Lighting.ClockTime,
Brightness = Lighting.Brightness,
FogEnd = Lighting.FogEnd,
Ambient = Lighting.Ambient,
OutdoorAmbient = Lighting.OutdoorAmbient,
}
--=====================================================
-- UTILS
--=====================================================
local function safeFullName(obj)
local ok, res = pcall(function()
return obj:GetFullName()
end)
return ok and res or tostring(obj)
end
local function pushLog(text)
table.insert(ghostLogs, 1, "[" .. os.date("%H:%M:%S") .. "] " .. tostring(text))
if #ghostLogs > 140 then
table.remove(ghostLogs)
end
end
local function pushRemoteLog(text)
table.insert(remoteLogs, 1, "[" .. os.date("%H:%M:%S") .. "] " .. tostring(text))
if #remoteLogs > 140 then
table.remove(remoteLogs)
end
end
_G.ProjectGhost_Log = pushLog
_G.ProjectGhost_LogRemote = function(remoteName, direction, ...)
local args = {...}
local out = {}
for i, v in ipairs(args) do
out[i] = tostring(v)
end
local argsText = table.concat(out, ", ")
pushRemoteLog(tostring(direction) .. " | " .. tostring(remoteName) .. " | " .. argsText)
logRemoteCall(remoteName, direction, argsText)
end
local function getRegisteredCodeForObject(obj)
if not obj then return nil end
local objName = obj.Name
local fullName = safeFullName(obj)
local className = obj.ClassName
for _, entry in ipairs(CODE_REGISTRY) do
if entry.MatchFullName and entry.MatchFullName == fullName then
return entry
end
if entry.MatchNames then
for _, name in ipairs(entry.MatchNames) do
if name == objName then
return entry
end
end
end
if entry.MatchClasses then
for _, c in ipairs(entry.MatchClasses) do
if c == className then
return entry
end
end
end
end
return nil
end
local function countDescendants(obj)
local ok, desc = pcall(function()
return obj:GetDescendants()
end)
if not ok or not desc then return 0 end
return #desc
end
local function canCloneObject(obj)
if not obj then
return false, "No object selected."
end
if obj:IsDescendantOf(playerGui) then
return false, "Cannot clone PlayerGui UI."
end
if obj == workspace or obj == game then
return false, "Cannot clone root services."
end
local total = countDescendants(obj)
if total > MAX_CLONE_PARTS then
return false, "Object too large. Descendants: " .. tostring(total)
end
if obj:IsA("Terrain") then
return false, "Terrain cloning blocked."
end
return true, "OK"
end
local function getPivotOrCFrame(obj)
if obj:IsA("BasePart") then
return obj.CFrame
end
if obj:IsA("Model") then
local ok, cf = pcall(function()
return obj:GetPivot()
end)
if ok then return cf end
end
return nil
end
local function moveCloneNearOriginal(clone, original)
local cf = getPivotOrCFrame(original)
if not cf then return end
local newCf = cf + CLONE_OFFSET
if clone:IsA("BasePart") then
clone.CFrame = newCf
elseif clone:IsA("Model") then
pcall(function()
clone:PivotTo(newCf)
end)
end
end
local addScanHistory = nil
local diffLatestScans = nil
local function cloneSelectedObject()
local obj = selectedObject
local allowed, reason = canCloneObject(obj)
if not allowed then
pushLog("Clone blocked: " .. tostring(reason))
return false, reason
end
local ok, cloneOrErr = pcall(function()
local clone = obj:Clone()
clone.Name = obj.Name .. "_GhostClone"
clone.Parent = obj.Parent
moveCloneNearOriginal(clone, obj)
return clone
end)
if ok then
pushUndo({Type = "Clone", Clone = cloneOrErr, Parent = cloneOrErr.Parent})
pushLog("Cloned: " .. safeFullName(cloneOrErr))
return true, "Cloned: " .. cloneOrErr.Name
end
pushLog("Clone failed: " .. tostring(cloneOrErr))
return false, tostring(cloneOrErr)
end
local function selectObject(obj, fromTool)
if not obj then return end
selectedObject = obj
selectedScriptObject = nil
selectedFromTool = fromTool == true
scanStatus = "TARGET_LOCKED"
if typeof(addScanHistory) == "function" then
addScanHistory(obj)
end
pushLog("Selected: " .. safeFullName(obj))
end
local function scanForNearbyScripts(obj)
local found = {}
if not obj then return found end
local current = obj
for _ = 1, 6 do
if not current then break end
for _, child in ipairs(current:GetChildren()) do
if child:IsA("LocalScript") or child:IsA("ModuleScript") or child:IsA("Script") then
table.insert(found, child)
end
end
current = current.Parent
end
for _, desc in ipairs(obj:GetDescendants()) do
if desc:IsA("LocalScript") or desc:IsA("ModuleScript") or desc:IsA("Script") then
table.insert(found, desc)
end
end
return found
end
local function getGhostSource(obj)
if not obj then return nil, "No object." end
local attrSource = obj:GetAttribute("GhostSource")
if typeof(attrSource) == "string" and attrSource ~= "" then
return attrSource, "GhostSource Attribute"
end
local registered = getRegisteredCodeForObject(obj)
if registered then
return registered.Source, "CODE_REGISTRY: " .. registered.Name
end
return nil, "No registered source."
end
local function getGhostInfo(obj)
if not obj then return nil end
local info = obj:GetAttribute("GhostInfo")
if typeof(info) == "string" and info ~= "" then
return info
end
local registered = getRegisteredCodeForObject(obj)
if registered then
return registered.Description
end
return nil
end
local function tryReflectModule(moduleScript)
if not moduleScript or not moduleScript:IsA("ModuleScript") then
return nil, "Not a ModuleScript."
end
if moduleScript:GetAttribute("GhostInspectable") ~= true then
return nil, "Set GhostInspectable = true."
end
local finished = false
local okResult = false
local resultValue = nil
task.spawn(function()
local ok, result = pcall(function()
return require(moduleScript)
end)
okResult = ok
resultValue = result
finished = true
end)
local started = os.clock()
while not finished and os.clock() - started < 1.25 do
task.wait()
end
if not finished then
return nil, "Module timeout."
end
if not okResult then
return nil, "Require failed: " .. tostring(resultValue)
end
local lines = {}
table.insert(lines, "Module: " .. safeFullName(moduleScript))
table.insert(lines, "Return type: " .. typeof(resultValue))
if typeof(resultValue) == "table" then
for k, v in pairs(resultValue) do
table.insert(lines, tostring(k) .. " = " .. tostring(v) .. " [" .. typeof(v) .. "]")
end
else
table.insert(lines, "Value: " .. tostring(resultValue))
end
return table.concat(lines, "\n"), "OK"
end
local function findTargetFromMouse()
local target = mouse.Target
if not target then return nil end
local selected = target
if target.Parent and target.Parent:IsA("Model") and target.Parent ~= workspace then
selected = target.Parent
end
return selected
end
local function classifyRemotePower(remote)
local name = remote.Name:lower()
local path = safeFullName(remote):lower()
local text = name .. " " .. path
-- Locked/server-only names are hidden from the usable monitor.
local lockedWords = {
"kick", "ban", "admin", "shutdown", "servercontrol", "server_control",
"givecash", "givemoney", "setcash", "money", "cash", "currency",
"damage", "kill", "forceadmin", "moderator", "modcommand",
"getserverversion", "getservertype", "getserverchannel",
"serverversion", "servertype", "serverchannel"
}
for _, word in ipairs(lockedWords) do
if text:find(word, 1, true) then
return "LOCKED", "Server Only / Not Shown", 0
end
end
local score = 10
local category = "Unknown"
local level = "UNKNOWN"
local usableRules = {
{words = {"spawncar", "spawnvehicle", "vehicle", "car", "heli", "helicopter", "plane", "boat"}, score = 90, category = "Vehicle / Spawn", level = "USABLE"},
{words = {"door", "open", "unlock", "interact", "use", "button"}, score = 80, category = "Interact / Door", level = "USABLE"},
{words = {"mission", "quest", "startmission", "startquest", "objective"}, score = 75, category = "Mission / Quest", level = "TESTABLE"},
{words = {"effect", "visual", "sound", "camera", "shake", "particle", "light"}, score = 65, category = "Visual / Effect", level = "CLIENT"},
{words = {"equip", "tool", "item", "inventory"}, score = 60, category = "Tool / Inventory", level = "TESTABLE"},
{words = {"weather", "time", "lighting", "fog"}, score = 55, category = "World Visual", level = "TESTABLE"},
{words = {"debug", "test", "dev", "ghost"}, score = 50, category = "Debug / Test", level = "TESTABLE"},
}
for _, rule in ipairs(usableRules) do
for _, word in ipairs(rule.words) do
if text:find(word, 1, true) then
if rule.score > score then
score = rule.score
category = rule.category
level = rule.level
end
end
end
end
return level, category, score
end
local function getRemoteStats(remote)
if not remote then return nil end
local key = safeFullName(remote)
if not remoteStats[key] then
local level, category, score = classifyRemotePower(remote)
remoteStats[key] = {
path = key,
name = remote.Name,
className = remote.ClassName,
level = level,
category = category,
score = score,
callCount = 0,
lastArgs = "No calls logged",
lastTime = "Never",
firstSeen = os.clock(),
lastClock = 0,
}
end
return remoteStats[key]
end
local function registerRemoteSeen(remote)
if remote and (remote:IsA("RemoteEvent") or remote:IsA("RemoteFunction")) then
getRemoteStats(remote)
end
end
local function logRemoteCall(remoteName, direction, argsText)
for _, remote in ipairs(game:GetDescendants()) do
if (remote:IsA("RemoteEvent") or remote:IsA("RemoteFunction")) and remote.Name == tostring(remoteName) then
local stat = getRemoteStats(remote)
stat.callCount += 1
stat.lastArgs = argsText or ""
stat.lastTime = os.date("%H:%M:%S")
stat.lastClock = os.clock()
break
end
end
end
local function scanAllRemotes()
for _, obj in ipairs(game:GetDescendants()) do
if obj:IsA("RemoteEvent") or obj:IsA("RemoteFunction") then
registerRemoteSeen(obj)
end
end
end
local function pushObjectTrack(text)
table.insert(trackedObjectLog, 1, "[" .. os.date("%H:%M:%S") .. "] " .. tostring(text))
if #trackedObjectLog > 120 then
table.remove(trackedObjectLog)
end
end
local function startObjectTracker()
if trackedObjects.started then
return
end
trackedObjects.started = true
workspace.DescendantAdded:Connect(function(obj)
if obj:IsA("Model") or obj:IsA("BasePart") or obj:IsA("Tool") then
pushObjectTrack("+ " .. obj.ClassName .. ": " .. safeFullName(obj))
end
end)
workspace.DescendantRemoving:Connect(function(obj)
if obj:IsA("Model") or obj:IsA("BasePart") or obj:IsA("Tool") then
pushObjectTrack("- " .. obj.ClassName .. ": " .. obj.Name)
end
end)
pushObjectTrack("Object tracker started.")
end
local function setGhostVision(state)
removedVisionFlag = state
if state then
Lighting.ClockTime = 0
Lighting.Brightness = 0.45
Lighting.FogEnd = 175
Lighting.Ambient = Color3.fromRGB(0, 35, 15)
Lighting.OutdoorAmbient = Color3.fromRGB(0, 20, 10)
pushLog("Removed enabled.")
else
Lighting.ClockTime = originalLighting.ClockTime
Lighting.Brightness = originalLighting.Brightness
Lighting.FogEnd = originalLighting.FogEnd
Lighting.Ambient = originalLighting.Ambient
Lighting.OutdoorAmbient = originalLighting.OutdoorAmbient
pushLog("Removed disabled.")
end
end
local function editablePropertyRows(obj)
if not obj then return {} end
local rows = {}
if obj:IsA("BasePart") then
table.insert(rows, {"Anchored", "bool"})
table.insert(rows, {"CanCollide", "bool"})
table.insert(rows, {"Transparency", "number"})
table.insert(rows, {"Reflectance", "number"})
end
if obj:IsA("Humanoid") then
table.insert(rows, {"WalkSpeed", "number"})
table.insert(rows, {"JumpPower", "number"})
table.insert(rows, {"Health", "number"})
end
return rows
end
local function toggleBoolProperty(obj, prop)
local oldValue = obj[prop]
local ok, err = pcall(function()
obj[prop] = not obj[prop]
end)
if ok then
pushUndo({Type = "Property", Object = obj, Property = prop, OldValue = oldValue, NewValue = obj[prop]})
pushLog(prop .. " -> " .. tostring(obj[prop]))
else
pushLog("Failed changing " .. prop .. ": " .. tostring(err))
end
end
local function stepNumberProperty(obj, prop, amount)
local oldValue = obj[prop]
local ok, err = pcall(function()
local current = tonumber(obj[prop]) or 0
obj[prop] = current + amount
end)
if ok then
pushUndo({Type = "Property", Object = obj, Property = prop, OldValue = oldValue, NewValue = obj[prop]})
pushLog(prop .. " -> " .. tostring(obj[prop]))
else
pushLog("Failed changing " .. prop .. ": " .. tostring(err))
end
end
local function getSelectedHumanoid()
if not selectedObject then return nil end
if selectedObject:IsA("Humanoid") then
return selectedObject
end
if selectedObject:IsA("Model") then
return selectedObject:FindFirstChildOfClass("Humanoid")
end
if selectedObject.Parent and selectedObject.Parent:IsA("Model") then
return selectedObject.Parent:FindFirstChildOfClass("Humanoid")
end
return nil
end
local function connectGhost(signal, callback)
local connection = signal:Connect(callback)
table.insert(ghostConnections, connection)
return connection
end
local function shutdownGhost()
for _, connection in ipairs(ghostConnections) do
pcall(function()
connection:Disconnect()
end)
end
table.clear(ghostConnections)
if gui then
pcall(function()
gui:Destroy()
end)
end
end
_G.ProjectGhost_Shutdown = shutdownGhost
local function getSpawnCarRemote(timeout)
local remote = RS:FindFirstChild(SpawnCarName)
if remote then
SpawnCar = remote
return remote
end
local ok, result = pcall(function()
return RS:WaitForChild(SpawnCarName, timeout or 1)
end)
if ok and result then
SpawnCar = result
return result
end
return nil
end
local function pushUndo(action)
table.insert(undoStack, action)
if #undoStack > MAX_HISTORY then
table.remove(undoStack, 1)
end
table.clear(redoStack)
end
local function undoLast()
local action = table.remove(undoStack)
if not action then
pushLog("Nothing to undo.")
return
end
if action.Type == "Delete" and action.Clone and action.Parent then
action.Clone.Parent = action.Parent
if action.CFrame and action.Clone:IsA("BasePart") then
action.Clone.CFrame = action.CFrame
elseif action.CFrame and action.Clone:IsA("Model") then
pcall(function()
action.Clone:PivotTo(action.CFrame)
end)
end
selectedObject = action.Clone
pushLog("Undo delete: " .. action.Clone.Name)
table.insert(redoStack, action)
return
end
if action.Type == "Clone" and action.Clone and action.Clone.Parent then
action.Clone.Parent = nil
pushLog("Undo clone.")
table.insert(redoStack, action)
return
end
if action.Type == "Property" and action.Object then
pcall(function()
action.Object[action.Property] = action.OldValue
end)
pushLog("Undo property: " .. action.Property)
table.insert(redoStack, action)
return
end
pushLog("Undo failed.")
end
local function redoLast()
local action = table.remove(redoStack)
if not action then
pushLog("Nothing to redo.")
return
end
if action.Type == "Delete" and action.Clone then
action.Clone.Parent = nil
pushLog("Redo delete.")
table.insert(undoStack, action)
return
end
if action.Type == "Clone" and action.Clone and action.Parent then
action.Clone.Parent = action.Parent
pushLog("Redo clone.")
table.insert(undoStack, action)
return
end
if action.Type == "Property" and action.Object then
pcall(function()
action.Object[action.Property] = action.NewValue
end)
pushLog("Redo property: " .. action.Property)
table.insert(undoStack, action)
return
end
pushLog("Redo failed.")
end
local function clampPanelToScreen()
if not main then return end
camera = workspace.CurrentCamera
if not camera then return end
local viewport = camera.ViewportSize
local size = main.AbsoluteSize
local pos = main.AbsolutePosition
local x = math.clamp(pos.X, 4, math.max(4, viewport.X - size.X - 4))
local y = math.clamp(pos.Y, 4, math.max(4, viewport.Y - size.Y - 4))
main.Position = UDim2.new(0, x, 0, y)
end
local function resetPanelPosition()
main.Size = UDim2.new(PANEL_SCALE_X, 0, PANEL_SCALE_Y, 0)
main.Position = UDim2.new(PANEL_POS_X, 0, PANEL_POS_Y, 0)
pushLog("Panel position reset.")
end
local function smartRefresh()
if refreshBusy then
refreshQueued = true
return
end
refreshBusy = true
refreshCurrent()
refreshBusy = false
if refreshQueued then
refreshQueued = false
task.defer(smartRefresh)
end
end
local function captureObjectSnapshot(obj)
if not obj then return nil end
local data = {
Name = obj.Name,
ClassName = obj.ClassName,
Path = safeFullName(obj),
Time = os.date("%H:%M:%S"),
Descendants = countDescendants(obj),
Props = {},
}
if obj:IsA("BasePart") then
data.Props.Anchored = tostring(obj.Anchored)
data.Props.CanCollide = tostring(obj.CanCollide)
data.Props.Transparency = tostring(obj.Transparency)
data.Props.Position = tostring(obj.Position)
end
local hum = nil
if obj:IsA("Model") then
hum = obj:FindFirstChildOfClass("Humanoid")
elseif obj:IsA("Humanoid") then
hum = obj
end
if hum then
data.Props.Health = tostring(math.floor(hum.Health))
data.Props.WalkSpeed = tostring(hum.WalkSpeed)
data.Props.JumpPower = tostring(hum.JumpPower)
end
return data
end
addScanHistory = function(obj)
local snap = captureObjectSnapshot(obj)
if not snap then return end
table.insert(scanHistory, 1, snap)
if #scanHistory > 80 then
table.remove(scanHistory)
end
end
diffLatestScans = function()
if #scanHistory < 2 then
return "Need two scans."
end
local now = scanHistory[1]
local before = nil
for i = 2, #scanHistory do
if scanHistory[i].Path == now.Path then
before = scanHistory[i]
break
end
end
if not before then
return "No previous scan for target."
end
local lines = {"Diff: " .. now.Name}
for k, v in pairs(now.Props) do
local old = before.Props[k]
if old == nil then
table.insert(lines, "+ " .. k .. " = " .. v)
elseif old ~= v then
table.insert(lines, "~ " .. k .. ": " .. old .. " -> " .. v)
end
end
for k, old in pairs(before.Props) do
if now.Props[k] == nil then
table.insert(lines, "- " .. k .. " = " .. old)
end
end
if #lines == 1 then
table.insert(lines, "No changes.")
end
return table.concat(lines, "\n")
end
--=====================================================
-- RUNTIME API STATUS
-- Safe compatibility check only.
-- No bypassing, no executor hooks, no bytecode dumping.
--=====================================================
local RuntimeAPIStatus = {
HookMetamethod = typeof(hookmetamethod) == "function",
GetNamecallMethod = typeof(getnamecallmethod) == "function",
Decompile = typeof(decompile) == "function",
GetGC = typeof(getgc) == "function",
GetGenv = typeof(getgenv) == "function",
}
local function getRuntimeAPIReport()
local lines = {
"RUNTIME API STATUS",
"hookmetamethod: " .. tostring(RuntimeAPIStatus.HookMetamethod),
"getnamecallmethod: " .. tostring(RuntimeAPIStatus.GetNamecallMethod),
"decompile: " .. tostring(RuntimeAPIStatus.Decompile),
"getgc: " .. tostring(RuntimeAPIStatus.GetGC),
"getgenv: " .. tostring(RuntimeAPIStatus.GetGenv),
"",
"Mode: Safe fallback",
"Bypass: not loaded",
"Bytecode dump: not loaded",
}
return table.concat(lines, "\n")
end
local function isStatLikeName(name)
local n = tostring(name):lower()
local keys = {"cash","money","coin","coins","currency","point","points","time","timer","minutes","seconds","level","xp","exp","score","kills","wins","gems","tokens","credits"}
for _, key in ipairs(keys) do
if n:find(key, 1, true) then
return true
end
end
return false
end
local function isValueObject(obj)
return obj:IsA("IntValue") or obj:IsA("NumberValue") or obj:IsA("StringValue") or obj:IsA("BoolValue")
end
local function addStatResult(obj, source)
if not obj or not isValueObject(obj) then return end
if not isStatLikeName(obj.Name) then return end
table.insert(statScanResults, {
Object = obj,
Name = obj.Name,
Value = tostring(obj.Value),
ClassName = obj.ClassName,
Path = safeFullName(obj),
Source = source or "Unknown",
})
end
local function scanStatsAndCurrency()
table.clear(statScanResults)
local localPlayer = Players.LocalPlayer
if not localPlayer then return statScanResults end
local leaderstats = localPlayer:FindFirstChild("leaderstats")
if leaderstats then
for _, obj in ipairs(leaderstats:GetDescendants()) do
addStatResult(obj, "leaderstats")
end
end
for _, obj in ipairs(localPlayer:GetDescendants()) do
addStatResult(obj, "Player")
end
local playerGui = localPlayer:FindFirstChild("PlayerGui")
if playerGui then
for _, obj in ipairs(playerGui:GetDescendants()) do
if obj:IsA("TextLabel") or obj:IsA("TextButton") or obj:IsA("TextBox") then
local shownText = tostring(obj.Text)
local lowerText = shownText:lower()
if isStatLikeName(obj.Name) or lowerText:find("cash", 1, true) or lowerText:find("money", 1, true) or lowerText:find("points", 1, true) then
table.insert(statScanResults, {
Object = obj,
Name = obj.Name,
Value = shownText,
ClassName = obj.ClassName,
Path = safeFullName(obj),
Source = "PlayerGui",
})
end
else
addStatResult(obj, "PlayerGui")
end
end
end
for _, obj in ipairs(RS:GetDescendants()) do
addStatResult(obj, "ReplicatedStorage")
end
for key, value in pairs(localPlayer:GetAttributes()) do
if isStatLikeName(key) then
table.insert(statScanResults, {
Object = localPlayer,
Name = key,
Value = tostring(value),
ClassName = "Attribute",
Path = safeFullName(localPlayer) .. ".Attribute." .. key,
Source = "Player Attribute",
AttributeName = key,
})
end
end
table.sort(statScanResults, function(a, b)
return a.Name < b.Name
end)
return statScanResults
end
setSelectedStatValue = function(newValue)
if not selectedStatObject then
pushLog("No stat selected.")
return
end
local target = selectedStatObject.Object
if not target then
pushLog("Stat target missing.")
return
end
local ok, err = pcall(function()
if selectedStatObject.AttributeName then
local castValue = tonumber(newValue) or newValue
target:SetAttribute(selectedStatObject.AttributeName, castValue)
pushLog(selectedStatObject.Name .. " -> " .. tostring(castValue))
return
end
if target:IsA("IntValue") then
target.Value = math.floor(tonumber(newValue) or target.Value)
elseif target:IsA("NumberValue") then
target.Value = tonumber(newValue) or target.Value
elseif target:IsA("StringValue") then
target.Value = tostring(newValue)
elseif target:IsA("BoolValue") then
local n = tostring(newValue):lower()
target.Value = (n == "true" or n == "1" or n == "yes")
elseif target:IsA("TextLabel") or target:IsA("TextButton") or target:IsA("TextBox") then
target.Text = tostring(newValue)
end
pushLog(selectedStatObject.Name .. " -> " .. tostring(newValue))
end)
if not ok then
pushLog("Stat edit failed: " .. tostring(err))
end
end
--=====================================================
-- UI ROOT
--=====================================================
if _G.ProjectGhost_Shutdown then
pcall(_G.ProjectGhost_Shutdown)
end
local oldGui = playerGui:FindFirstChild("ProjectGhost")
if oldGui then oldGui:Destroy() end
local gui = Instance.new("ScreenGui")
gui.Name = "ProjectGhost"
gui.IgnoreGuiInset = true
gui.ResetOnSpawn = false
gui.Parent = playerGui
local ghostIcon = Instance.new("TextButton")
ghostIcon.Name = "GhostIcon"
ghostIcon.Size = UDim2.new(0, 56, 0, 56)
ghostIcon.Position = UDim2.new(0, 18, 0.5, -28)
ghostIcon.BackgroundColor3 = BLACK
ghostIcon.Text = "👻"
ghostIcon.TextSize = 31
ghostIcon.TextColor3 = GREEN
ghostIcon.Font = Enum.Font.GothamBold
ghostIcon.AutoButtonColor = false
ghostIcon.Parent = gui
do
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(1, 0)
c.Parent = ghostIcon
local s = Instance.new("UIStroke")
s.Color = GREEN
s.Thickness = 2
s.Parent = ghostIcon
end
-- Draggable ghost button
local draggingGhostIcon = false
local ghostIconDragStart = nil
local ghostIconStartPos = nil
ghostIcon.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
draggingGhostIcon = true
ghostIconDragStart = input.Position
ghostIconStartPos = ghostIcon.Position
input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End then
draggingGhostIcon = false
end
end)
end
end)
UIS.InputChanged:Connect(function(input)
if draggingGhostIcon and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
local delta = input.Position - ghostIconDragStart
ghostIcon.Position = UDim2.new(
ghostIconStartPos.X.Scale,
ghostIconStartPos.X.Offset + delta.X,
ghostIconStartPos.Y.Scale,
ghostIconStartPos.Y.Offset + delta.Y
)
end
end)
local main = Instance.new("Frame")
main.Name = "Main"
main.Size = UDim2.new(PANEL_SCALE_X, 0, PANEL_SCALE_Y, 0)
main.Position = UDim2.new(PANEL_POS_X, 0, PANEL_POS_Y, 0)
main.BackgroundColor3 = BLACK
main.BackgroundTransparency = 0.18
main.BorderSizePixel = 0
main.Visible = false
main.Parent = gui
local mainScale = Instance.new("UIScale")
mainScale.Name = "ResponsiveScale"
mainScale.Scale = UIS.TouchEnabled and 0.82 or 1
mainScale.Parent = main
do
local s = Instance.new("UIStroke")
s.Color = GREEN
s.Thickness = 2
s.Parent = main
end
local top = Instance.new("Frame")
top.Name = "TopBar"
top.Size = UDim2.new(1, 0, 0, 58)
top.BackgroundColor3 = BLACK_2
top.BorderSizePixel = 0
top.Parent = main
local title = Instance.new("TextLabel")
title.Size = UDim2.new(0, 360, 1, 0)
title.Position = UDim2.new(0, 12, 0, 0)
title.BackgroundTransparency = 1
title.Text = "PROJECT GHOST // v3.4 // Scanner"
title.TextColor3 = GREEN
title.Font = Enum.Font.Code
title.TextSize = 18
title.TextXAlignment = Enum.TextXAlignment.Left
title.Parent = top
local statusText = Instance.new("TextLabel")
statusText.Size = UDim2.new(0, 160, 1, 0)
statusText.Position = UDim2.new(0, 390, 0, 0)
statusText.BackgroundTransparency = 1
statusText.Text = "STATUS: OPERATIONAL"
statusText.TextColor3 = GREEN
statusText.Font = Enum.Font.Code
statusText.TextSize = 13
statusText.TextXAlignment = Enum.TextXAlignment.Left
statusText.Parent = top
local targetText = Instance.new("TextLabel")
targetText.Size = UDim2.new(0, 260, 1, 0)
targetText.Position = UDim2.new(0, 570, 0, 0)
targetText.BackgroundTransparency = 1
targetText.Text = "TARGET: none"
targetText.TextColor3 = WHITE_GREEN
targetText.Font = Enum.Font.Code
targetText.TextSize = 13
targetText.TextXAlignment = Enum.TextXAlignment.Left
targetText.Parent = top
local uptimeText = Instance.new("TextLabel")
uptimeText.Size = UDim2.new(0, 170, 1, 0)
uptimeText.Position = UDim2.new(1, -390, 0, 0)
uptimeText.BackgroundTransparency = 1
uptimeText.Text = "UPTIME: 00:00"
uptimeText.TextColor3 = WHITE_GREEN
uptimeText.Font = Enum.Font.Code
uptimeText.TextSize = 13
uptimeText.TextXAlignment = Enum.TextXAlignment.Left
uptimeText.Parent = top
local refreshBtn = Instance.new("TextButton")
refreshBtn.Size = UDim2.new(0, 90, 0, 30)
refreshBtn.Position = UDim2.new(1, -210, 0, 13)
refreshBtn.BackgroundColor3 = BLACK
refreshBtn.Text = "REFRESH"
refreshBtn.TextColor3 = GREEN
refreshBtn.Font = Enum.Font.Code
refreshBtn.TextSize = 14
refreshBtn.AutoButtonColor = false
refreshBtn.Parent = top
local minimizeBtn = Instance.new("TextButton")
minimizeBtn.Size = UDim2.new(0, 36, 0, 30)
minimizeBtn.Position = UDim2.new(1, -110, 0, 13)
minimizeBtn.BackgroundColor3 = BLACK
minimizeBtn.Text = "_"
minimizeBtn.TextColor3 = GREEN
minimizeBtn.Font = Enum.Font.Code
minimizeBtn.TextSize = 20
minimizeBtn.AutoButtonColor = false
minimizeBtn.Parent = top
local closeBtn = Instance.new("TextButton")
closeBtn.Size = UDim2.new(0, 36, 0, 30)
closeBtn.Position = UDim2.new(1, -60, 0, 13)
closeBtn.BackgroundColor3 = BLACK
closeBtn.Text = "X"
closeBtn.TextColor3 = BAD
closeBtn.Font = Enum.Font.Code
closeBtn.TextSize = 16
closeBtn.AutoButtonColor = false
closeBtn.Parent = top
for _, btn in ipairs({refreshBtn, minimizeBtn, closeBtn}) do
local s = Instance.new("UIStroke")
s.Color = GREEN
s.Thickness = 1
s.Parent = btn
end
local sidebar = Instance.new("Frame")
sidebar.Size = UDim2.new(0, 210, 1, -58)
sidebar.Position = UDim2.new(0, 0, 0, 58)
sidebar.BackgroundColor3 = BLACK_2
sidebar.BorderSizePixel = 0
sidebar.Parent = main
local sidebarLayout = Instance.new("UIListLayout")
sidebarLayout.Padding = UDim.new(0, 6)
sidebarLayout.SortOrder = Enum.SortOrder.LayoutOrder
sidebarLayout.Parent = sidebar
local sidebarPad = Instance.new("UIPadding")
sidebarPad.PaddingTop = UDim.new(0, 9)
sidebarPad.PaddingLeft = UDim.new(0, 8)
sidebarPad.PaddingRight = UDim.new(0, 8)
sidebarPad.Parent = sidebar
local modulesHeader = Instance.new("TextLabel")
modulesHeader.Name = "ModulesHeader"
modulesHeader.Size = UDim2.new(1, 0, 0, 28)
modulesHeader.BackgroundTransparency = 1
modulesHeader.Text = "MODULES"
modulesHeader.TextColor3 = WHITE_GREEN
modulesHeader.Font = Enum.Font.Code
modulesHeader.TextSize = 13
modulesHeader.TextXAlignment = Enum.TextXAlignment.Left
modulesHeader.LayoutOrder = -100
modulesHeader.Parent = sidebar
local content = Instance.new("Frame")
content.Name = "Content"
content.Size = UDim2.new(1, -210, 1, -118)
content.Position = UDim2.new(0, 210, 0, 58)
content.BackgroundColor3 = BLACK
content.BackgroundTransparency = 0.58
content.BorderSizePixel = 0
content.ClipsDescendants = true
content.Parent = main
local bottomPanel = Instance.new("Frame")
bottomPanel.Name = "BottomPanel"
bottomPanel.Size = UDim2.new(1, -210, 0, 60)
bottomPanel.Position = UDim2.new(0, 210, 1, -60)
bottomPanel.BackgroundColor3 = BLACK_2
bottomPanel.BackgroundTransparency = 0.25
bottomPanel.BorderSizePixel = 0
bottomPanel.Parent = main
local bottomStroke = Instance.new("UIStroke")
bottomStroke.Color = GREEN_DARK
bottomStroke.Thickness = 1
bottomStroke.Parent = bottomPanel
local terminalLine = Instance.new("TextLabel")
terminalLine.Name = "TerminalLine"
terminalLine.Size = UDim2.new(1, -20, 0, 24)
terminalLine.Position = UDim2.new(0, 10, 0, 30)
terminalLine.BackgroundTransparency = 1
terminalLine.Text = "ghost@terminal:~$ ready"
terminalLine.TextColor3 = WHITE_GREEN
terminalLine.Font = Enum.Font.Code
terminalLine.TextSize = 13
terminalLine.TextXAlignment = Enum.TextXAlignment.Left
terminalLine.Parent = bottomPanel
local quickTitle = Instance.new("TextLabel")
quickTitle.Size = UDim2.new(0, 120, 0, 20)
quickTitle.Position = UDim2.new(0, 10, 0, 6)
quickTitle.BackgroundTransparency = 1
quickTitle.Text = "QUICK ACTIONS"
quickTitle.TextColor3 = GREEN
quickTitle.Font = Enum.Font.Code
quickTitle.TextSize = 12
quickTitle.TextXAlignment = Enum.TextXAlignment.Left
quickTitle.Parent = bottomPanel
local function makeQuickButton(text, x, callback)
local b = Instance.new("TextButton")
b.Size = UDim2.new(0, 145, 0, 24)
b.Position = UDim2.new(0, x, 0, 4)
b.BackgroundColor3 = BLACK
b.BackgroundTransparency = 0.25
b.BorderSizePixel = 0
b.Text = text
b.TextColor3 = GREEN
b.Font = Enum.Font.Code
b.TextSize = 11
b.AutoButtonColor = false
b.Parent = bottomPanel
local bs = Instance.new("UIStroke")
bs.Color = GREEN_DARK
bs.Thickness = 1
bs.Parent = b
b.MouseEnter:Connect(function()
b.BackgroundColor3 = Color3.fromRGB(0, 35, 16)
end)
b.MouseLeave:Connect(function()
b.BackgroundColor3 = BLACK
end)
b.MouseButton1Click:Connect(callback)
return b
end
-- callbacks are resolved later after refresh functions exist
local quickActionsReady = false
--=====================================================
-- ANIMATED GHOST BACKGROUND
-- Clean neon outline ghost, based on the reference image.
--=====================================================
local ghostBg = Instance.new("Frame")
ghostBg.Name = "AnimatedGhostBackground"
ghostBg.Size = UDim2.new(0, 390, 0, 390)
ghostBg.Position = UDim2.new(0.5, -195, 0.5, -195)
ghostBg.BackgroundTransparency = 1
ghostBg.ZIndex = 1
ghostBg.Parent = content
local ghostDots = {}
local outlineDots = {}
local function makeDot(parent, xScale, yScale, size, transparency, z)
local dot = Instance.new("Frame")
dot.Size = UDim2.new(0, size, 0, size)
dot.AnchorPoint = Vector2.new(0.5, 0.5)
dot.Position = UDim2.new(xScale, 0, yScale, 0)
dot.BackgroundColor3 = GREEN
dot.BackgroundTransparency = transparency or 0.35
dot.BorderSizePixel = 0
dot.ZIndex = z or 1
dot.Parent = parent
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(1, 0)
corner.Parent = dot
local glow = Instance.new("UIStroke")
glow.Color = GREEN
glow.Thickness = 4
glow.Transparency = 0.12
glow.Parent = dot
return dot
end
local function addOutlineDot(x, y, size, alpha)
local dot = makeDot(ghostBg, x, y, size or 6, alpha or 0.42, 1)
table.insert(ghostDots, dot)
table.insert(outlineDots, dot)
return dot
end
-- Rounded dome, smoother and less like a full circle.
for i = 0, 46 do
local theta = math.pi - (i / 46) * math.pi
local x = 0.5 + math.cos(theta) * 0.31
local y = 0.34 - math.sin(theta) * 0.265
addOutlineDot(x, y, 6, 0.48)
end
-- Straight sides.
for i = 1, 30 do
local y = 0.34 + i * 0.014
addOutlineDot(0.19, y, 6, 0.48)
addOutlineDot(0.81, y, 6, 0.48)
end
-- Bottom zig-zag ghost sheet.
local zigzag = {
{0.19, 0.76}, {0.27, 0.67}, {0.35, 0.76}, {0.43, 0.67},
{0.51, 0.76}, {0.59, 0.67}, {0.67, 0.76}, {0.75, 0.67}, {0.81, 0.76},
}
for i = 1, #zigzag - 1 do
local a = zigzag[i]
local b = zigzag[i + 1]
for step = 0, 7 do
local n = step / 7
local x = a[1] + (b[1] - a[1]) * n
local y = a[2] + (b[2] - a[2]) * n
addOutlineDot(x, y, 6, 0.48)
end
end
-- Very faint inner ghost body, not a big circle.
local bodyFill = Instance.new("Frame")
bodyFill.Name = "SoftGhostBody"
bodyFill.Size = UDim2.new(0.62, 0, 0.60, 0)
bodyFill.Position = UDim2.new(0.19, 0, 0.16, 0)
bodyFill.BackgroundColor3 = GREEN
bodyFill.BackgroundTransparency = 0.993
bodyFill.BorderSizePixel = 0
bodyFill.ZIndex = 0
bodyFill.Parent = ghostBg
local bodyCorner = Instance.new("UICorner")
bodyCorner.CornerRadius = UDim.new(0.42, 0)
bodyCorner.Parent = bodyFill
local bodyStroke = Instance.new("UIStroke")
bodyStroke.Color = GREEN
bodyStroke.Thickness = 1
bodyStroke.Transparency = 0.97
bodyStroke.Parent = bodyFill
-- Eyes: circular neon rings with small top gaps/highlights.
local eyeDots = {}
local function makeEye(cx, cy)
local ring = {}
for i = 0, 20 do
-- leave a small gap at top-left to mimic the reference
if not (i >= 3 and i <= 6) then
local theta = (i / 20) * math.pi * 2
local x = cx + math.cos(theta) * 0.045
local y = cy + math.sin(theta) * 0.045
local d = makeDot(ghostBg, x, y, 5, 0.26, 2)
table.insert(ring, d)
table.insert(eyeDots, d)
table.insert(ghostDots, d)
end
end
-- small glowing accent curl on each eye
local accent1 = makeDot(ghostBg, cx - 0.018, cy - 0.058, 5, 0.12, 2)
local accent2 = makeDot(ghostBg, cx - 0.002, cy - 0.066, 5, 0.12, 2)
table.insert(eyeDots, accent1)
table.insert(eyeDots, accent2)
table.insert(ghostDots, accent1)
table.insert(ghostDots, accent2)
end
makeEye(0.39, 0.36)
makeEye(0.61, 0.36)
-- Angry neon brows
local angryBrowLeft = Instance.new("Frame")
angryBrowLeft.Size = UDim2.new(0, 72, 0, 5)
angryBrowLeft.Position = UDim2.new(0.39, 0, 0.285, 0)
angryBrowLeft.AnchorPoint = Vector2.new(0.5, 0.5)
angryBrowLeft.BackgroundColor3 = GREEN
angryBrowLeft.BorderSizePixel = 0
angryBrowLeft.Rotation = 24
angryBrowLeft.ZIndex = 3
angryBrowLeft.Parent = ghostBg
local angryLeftCorner = Instance.new("UICorner")
angryLeftCorner.CornerRadius = UDim.new(1, 0)
angryLeftCorner.Parent = angryBrowLeft
local angryLeftGlow = Instance.new("UIStroke")
angryLeftGlow.Color = GREEN
angryLeftGlow.Thickness = 3
angryLeftGlow.Transparency = 0.18
angryLeftGlow.Parent = angryBrowLeft
local angryBrowRight = Instance.new("Frame")
angryBrowRight.Size = UDim2.new(0, 72, 0, 5)
angryBrowRight.Position = UDim2.new(0.61, 0, 0.285, 0)
angryBrowRight.AnchorPoint = Vector2.new(0.5, 0.5)
angryBrowRight.BackgroundColor3 = GREEN
angryBrowRight.BorderSizePixel = 0
angryBrowRight.Rotation = -24
angryBrowRight.ZIndex = 3
angryBrowRight.Parent = ghostBg
local angryRightCorner = Instance.new("UICorner")
angryRightCorner.CornerRadius = UDim.new(1, 0)
angryRightCorner.Parent = angryBrowRight
local angryRightGlow = Instance.new("UIStroke")
angryRightGlow.Color = GREEN
angryRightGlow.Thickness = 3
angryRightGlow.Transparency = 0.18
angryRightGlow.Parent = angryBrowRight
-- Reference-style moving highlight following the outline.
local highlightArc = {}
for i = 1, 18 do
local h = makeDot(ghostBg, 0.5, 0.08, 7, 0.0, 3)
table.insert(highlightArc, h)
end
--=====================================================
--=====================================================
-- PAGE UI
--=====================================================
local pages = {}
local currentPage = "Scanner"
local function makePage(name)
local page = Instance.new("ScrollingFrame")
page.Name = name
page.Size = UDim2.new(1, 0, 1, 0)
page.BackgroundTransparency = 1
page.BorderSizePixel = 0
page.ScrollBarThickness = 6
page.ScrollBarImageColor3 = GREEN
page.CanvasSize = UDim2.new(0, 0, 0, 0)
page.Visible = false
page.ZIndex = 3
page.Parent = content
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 6)
layout.SortOrder = Enum.SortOrder.LayoutOrder
layout.Parent = page
local pad = Instance.new("UIPadding")
pad.PaddingTop = UDim.new(0, 10)
pad.PaddingLeft = UDim.new(0, 10)
pad.PaddingRight = UDim.new(0, 10)
pad.PaddingBottom = UDim.new(0, 10)
pad.Parent = page
layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
page.CanvasSize = UDim2.new(0, 0, 0, layout.AbsoluteContentSize.Y + 25)
end)
pages[name] = page
return page
end
local function clearPage(page)
for _, child in ipairs(page:GetChildren()) do
if not child:IsA("UIListLayout") and not child:IsA("UIPadding") then
child:Destroy()
end
end
end
local function addLine(page, text, color, height)
local label = Instance.new("TextLabel")
label.Size = UDim2.new(1, -8, 0, height or 28)
label.BackgroundColor3 = BLACK_3
label.BackgroundTransparency = 0.68
label.BorderSizePixel = 0
label.Text = tostring(text)
label.TextColor3 = color or GREEN
label.Font = Enum.Font.Code
label.TextSize = 14
label.TextXAlignment = Enum.TextXAlignment.Left
label.TextYAlignment = Enum.TextYAlignment.Center
label.TextWrapped = true
label.ZIndex = 4
label.Parent = page
local pad = Instance.new("UIPadding")
pad.PaddingLeft = UDim.new(0, 7)
pad.PaddingRight = UDim.new(0, 7)
pad.Parent = label
local s = Instance.new("UIStroke")
s.Color = GREEN_DARK
s.Thickness = 1
s.Parent = label
return label
end
local function addBlock(page, text)
local lines = 1
for _ in tostring(text):gmatch("\n") do
lines += 1
end
local box = Instance.new("TextBox")
box.Size = UDim2.new(1, -8, 0, math.clamp(lines * 19 + 24, 80, 360))
box.BackgroundColor3 = BLACK_3
box.BackgroundTransparency = 0.60
box.BorderSizePixel = 0
box.Text = tostring(text)
box.TextColor3 = WHITE_GREEN
box.Font = Enum.Font.Code
box.TextSize = 13
box.TextXAlignment = Enum.TextXAlignment.Left
box.TextYAlignment = Enum.TextYAlignment.Top
box.ClearTextOnFocus = false
box.MultiLine = true
box.TextEditable = false
box.ZIndex = 4
box.Parent = page
local pad = Instance.new("UIPadding")
pad.PaddingLeft = UDim.new(0, 8)
pad.PaddingTop = UDim.new(0, 8)
pad.PaddingRight = UDim.new(0, 8)
pad.PaddingBottom = UDim.new(0, 8)
pad.Parent = box
local s = Instance.new("UIStroke")
s.Color = GREEN_DARK
s.Thickness = 1
s.Parent = box
return box
end
local function addButton(page, text, callback, color)
local btn = Instance.new("TextButton")
btn.Size = UDim2.new(1, -8, 0, 34)
btn.BackgroundColor3 = BLACK
btn.BackgroundTransparency = 0.52
btn.BorderSizePixel = 0
btn.Text = tostring(text)
btn.TextColor3 = color or GREEN
btn.Font = Enum.Font.Code
btn.TextSize = 13
btn.AutoButtonColor = false
btn.ZIndex = 4
btn.Parent = page
local s = Instance.new("UIStroke")
s.Color = color or GREEN
s.Thickness = 1
s.Parent = btn
btn.MouseEnter:Connect(function()
btn.BackgroundColor3 = Color3.fromRGB(0, 35, 16)
end)
btn.MouseLeave:Connect(function()
btn.BackgroundColor3 = BLACK
end)
btn.MouseButton1Click:Connect(callback)
return btn
end
local function switchPage(name)
for _, page in pairs(pages) do
page.Visible = false
end
currentPage = name
pages[name].Visible = true
if title then
title.Text = "PROJECT GHOST // v3.4 // " .. tostring(name)
end
end
local function addTab(name)
local btn = Instance.new("TextButton")
btn.Size = UDim2.new(1, 0, 0, 36)
btn.BackgroundColor3 = BLACK
btn.BorderSizePixel = 0
btn.Text = " > " .. name
btn.TextColor3 = GREEN
btn.Font = Enum.Font.Code
btn.TextSize = 13
btn.TextXAlignment = Enum.TextXAlignment.Left
btn.AutoButtonColor = false
btn.Parent = sidebar
local pad = Instance.new("UIPadding")
pad.PaddingLeft = UDim.new(0, 8)
pad.Parent = btn
local s = Instance.new("UIStroke")
s.Color = GREEN_DARK
s.Thickness = 1
s.Parent = btn
btn.MouseButton1Click:Connect(function()
switchPage(name)
end)
end
local scannerPage = makePage("Scanner")
local scriptViewerPage = makePage("Script Viewer")
local codePage = makePage("Code Registry")
local toolsPage = makePage("Tools")
local propertyPage = makePage("Property Editor")
local playerPage = makePage("Player Analyzer")
local statsScannerPage = makePage("Stats Scanner")
local trackerPage = makePage("Object Tracker")
local runtimePage = makePage("Runtime API")
local remotesPage = makePage("Remotes")
local remoteMonitorPage = makePage("Remote Monitor")
local explorerPage = makePage("Explorer")
local statsPage = makePage("Stats")
local logsPage = makePage("Logs")
for _, name in ipairs({"Scanner", "Script Viewer", "Code Registry", "Tools", "Property Editor", "Player Analyzer", "Stats Scanner", "Object Tracker", "Runtime API", "Remotes", "Remote Monitor", "Explorer", "Stats", "Logs"}) do
addTab(name)
end
--=====================================================
-- PAGE REFRESHERS
--=====================================================
local function refreshScanner()
clearPage(scannerPage)
addLine(scannerPage, "GHOST SCANNER", GREEN)
addLine(scannerPage, "Status: " .. scanStatus, WHITE_GREEN)
if not selectedObject then
addLine(scannerPage, "No object selected.", WARNING)
addLine(scannerPage, "Equip Ghost Scanner, then click a part/model.")
return
end
addLine(scannerPage, "Selected: " .. selectedObject.Name)
addLine(scannerPage, "Class: " .. selectedObject.ClassName)
addLine(scannerPage, "Path: " .. safeFullName(selectedObject), WHITE_GREEN, 48)
addLine(scannerPage, "Descendants: " .. tostring(countDescendants(selectedObject)))
addLine(scannerPage, "Selected By Tool: " .. tostring(selectedFromTool))
if selectedObject:IsA("BasePart") then
addLine(scannerPage, "Position: " .. tostring(selectedObject.Position), WHITE_GREEN)
addLine(scannerPage, "Anchored: " .. tostring(selectedObject.Anchored))
addLine(scannerPage, "CanCollide: " .. tostring(selectedObject.CanCollide))
end
local info = getGhostInfo(selectedObject)
if info then
addLine(scannerPage, "GhostInfo: " .. info, WHITE_GREEN, 50)
end
local registered = getRegisteredCodeForObject(selectedObject)
if registered then
addLine(scannerPage, "Registered Code: " .. registered.Name, GREEN)
addLine(scannerPage, "Access: " .. tostring(registered.AccessLevel or "UNKNOWN"), WHITE_GREEN)
else
addLine(scannerPage, "Registered Code: none", WARNING)
end
local nearbyScripts = scanForNearbyScripts(selectedObject)
addLine(scannerPage, "Nearby Scripts/Modules: " .. tostring(#nearbyScripts), GREEN)
for _, scriptObj in ipairs(nearbyScripts) do
addButton(scannerPage, "[" .. scriptObj.ClassName .. "] " .. safeFullName(scriptObj), function()
selectedScriptObject = scriptObj
switchPage("Script Viewer")
end, WHITE_GREEN)
end
end
local function refreshScriptViewer()
clearPage(scriptViewerPage)
addLine(scriptViewerPage, "SCRIPT VIEWER", GREEN)
if not selectedObject then
addLine(scriptViewerPage, "No object selected.", WARNING)
return
end
addLine(scriptViewerPage, "Target Object: " .. selectedObject.Name)
addLine(scriptViewerPage, "Target Path: " .. safeFullName(selectedObject), WHITE_GREEN, 45)
local nearbyScripts = scanForNearbyScripts(selectedObject)
if #nearbyScripts > 0 then
addLine(scriptViewerPage, "Detected scripts/modules:", GREEN)
for _, scriptObj in ipairs(nearbyScripts) do
addButton(scriptViewerPage, "[" .. scriptObj.ClassName .. "] " .. safeFullName(scriptObj), function()
selectedScriptObject = scriptObj
refreshScriptViewer()
end, WHITE_GREEN)
end
else
addLine(scriptViewerPage, "No nearby scripts/modules found.", WARNING)
end
addLine(scriptViewerPage, " ")
local viewTarget = selectedScriptObject or selectedObject
addLine(scriptViewerPage, "View Target: " .. viewTarget.Name)
addLine(scriptViewerPage, "Class: " .. viewTarget.ClassName)
local source, sourceType = getGhostSource(viewTarget)
if source then
addLine(scriptViewerPage, "Source Type: " .. sourceType, GREEN)
addBlock(scriptViewerPage, source)
else
addLine(scriptViewerPage, "No readable source registered.", WARNING)
addLine(scriptViewerPage, "Add Attribute GhostSource or add entry to CODE_REGISTRY.", WHITE_GREEN, 42)
end
if viewTarget:IsA("ModuleScript") then
addLine(scriptViewerPage, " ")
addButton(scriptViewerPage, "REFLECT MODULE METADATA", function()
local reflected, msg = tryReflectModule(viewTarget)
if reflected then
viewTarget:SetAttribute("GhostLastReflection", reflected)
pushLog("Module reflected: " .. viewTarget.Name)
else
pushLog("Module reflection failed: " .. msg)
end
refreshScriptViewer()
end)
local reflected = viewTarget:GetAttribute("GhostLastReflection")
if typeof(reflected) == "string" and reflected ~= "" then
addLine(scriptViewerPage, "Module Reflection:", GREEN)
addBlock(scriptViewerPage, reflected)
else
addLine(scriptViewerPage, "Set GhostInspectable = true on ModuleScript to allow reflection.", WARNING, 45)
end
end
end
local function refreshCodeRegistry()
clearPage(codePage)
addLine(codePage, "CODE REGISTRY", GREEN)
addLine(codePage, "Viewable hacking-game code entries.", WHITE_GREEN)
for _, entry in ipairs(CODE_REGISTRY) do
addLine(codePage, "Entry: " .. entry.Name, GREEN)
addLine(codePage, "Access: " .. tostring(entry.AccessLevel or "UNKNOWN"))
addLine(codePage, "Info: " .. tostring(entry.Description), WHITE_GREEN, 42)
addBlock(codePage, entry.Source)
end
end
local function refreshTools()
clearPage(toolsPage)
addLine(toolsPage, "OBJECT TOOLS", GREEN)
addButton(toolsPage, "UNDO", function()
undoLast()
refreshTools()
end)
addButton(toolsPage, "REDO", function()
redoLast()
refreshTools()
end)
if selectedObject then
addLine(toolsPage, "Selected: " .. selectedObject.Name)
addLine(toolsPage, "Class: " .. selectedObject.ClassName)
else
addLine(toolsPage, "Selected: none", WARNING)
end
addButton(toolsPage, "DUPLICATE SELECTED OBJECT", function()
local ok, msg = cloneSelectedObject()
if not ok then warn("[Project Ghost]", msg) end
refreshTools()
end)
addButton(toolsPage, "DELETE SELECTED CLIENT COPY", function()
if not selectedObject then
pushLog("Delete blocked: no selected object.")
return
end
local obj = selectedObject
if obj:IsDescendantOf(workspace) and obj ~= workspace then
local name = obj.Name
local backup = nil
local parent = obj.Parent
local cf = getPivotOrCFrame(obj)
pcall(function()
backup = obj:Clone()
end)
pcall(function() obj:Destroy() end)
if backup then
pushUndo({Type = "Delete", Clone = backup, Parent = parent, CFrame = cf})
end
selectedObject = nil
selectedScriptObject = nil
pushLog("Destroyed client-visible object: " .. name)
else
pushLog("Delete blocked: unsafe target.")
end
refreshTools()
end, BAD)
addButton(toolsPage, "OPEN PROPERTY EDITOR", function()
switchPage("Property Editor")
refreshPropertyEditor()
end)
addButton(toolsPage, "OPEN PLAYER ANALYZER", function()
switchPage("Player Analyzer")
refreshPlayerAnalyzer()
end)
addButton(toolsPage, "OPEN STATS SCANNER", function()
switchPage("Stats Scanner")
refreshStatsScanner()
end)
addButton(toolsPage, "BLACKOUT LIGHTING", function()
Lighting.ClockTime = 0
Lighting.Brightness = 0.5
pushLog("Lighting blackout applied.")
end)
addButton(toolsPage, "RESTORE LIGHTING", function()
Lighting.ClockTime = 14
Lighting.Brightness = 2
pushLog("Lighting restored.")
end)
addLine(toolsPage, " ")
addLine(toolsPage, "VEHICLE SPAWNER", GREEN)
addLine(toolsPage, "Selected Vehicle: " .. selectedVehicle)
addLine(toolsPage, "SpawnCar Remote: " .. (SpawnCar and "FOUND" or "NOT FOUND"), SpawnCar and GREEN or BAD)
for _, vehicle in ipairs(vehicles) do
addButton(toolsPage, "Select " .. vehicle, function()
selectedVehicle = vehicle
refreshTools()
end)
end
addButton(toolsPage, "SPAWN SELECTED VEHICLE", function()
SpawnCar = getSpawnCarRemote(1)
if not SpawnCar then
pushLog("Spawn remote missing.")
return
end
if os.clock() - lastSpawn < spawnCooldown then
pushLog("Spawn blocked: cooldown active.")
return
end
lastSpawn = os.clock()
local ok, err = pcall(function()
SpawnCar:FireServer(0, selectedVehicle)
end)
if ok then
pushLog("Spawn request sent: " .. selectedVehicle)
pushRemoteLog("FireServer | SpawnCar | 0, " .. selectedVehicle)
else
pushLog("Spawn error: " .. tostring(err))
end
end)
end
local function refreshPropertyEditor()
clearPage(propertyPage)
addLine(propertyPage, "PROPERTY EDITOR", GREEN)
if not selectedObject then
addLine(propertyPage, "No selected object.", WARNING)
addLine(propertyPage, "Scan something first with Ghost Scanner.")
return
end
addLine(propertyPage, "Selected: " .. selectedObject.Name)
addLine(propertyPage, "Class: " .. selectedObject.ClassName)
addLine(propertyPage, "Path: " .. safeFullName(selectedObject), WHITE_GREEN, 46)
local rows = editablePropertyRows(selectedObject)
if #rows == 0 then
addLine(propertyPage, "No editable properties.", WARNING)
addLine(propertyPage, "Useful targets: Parts, MeshParts, Humanoids.", WHITE_GREEN)
return
end
for _, row in ipairs(rows) do
local prop = row[1]
local typ = row[2]
local value = "?"
pcall(function()
value = tostring(selectedObject[prop])
end)
addLine(propertyPage, prop .. " = " .. value, WHITE_GREEN)
if typ == "bool" then
addButton(propertyPage, "Toggle " .. prop, function()
toggleBoolProperty(selectedObject, prop)
refreshPropertyEditor()
end)
elseif typ == "number" then
addButton(propertyPage, prop .. " +1", function()
stepNumberProperty(selectedObject, prop, 1)
refreshPropertyEditor()
end)
addButton(propertyPage, prop .. " -1", function()
stepNumberProperty(selectedObject, prop, -1)
refreshPropertyEditor()
end)
end
end
end
local function refreshPlayerAnalyzer()
clearPage(playerPage)
addLine(playerPage, "PLAYER ANALYZER", GREEN)
local targetModel = nil
if selectedObject then
if selectedObject:IsA("Model") then
targetModel = selectedObject
elseif selectedObject.Parent and selectedObject.Parent:IsA("Model") then
targetModel = selectedObject.Parent
end
end
if not targetModel then
addLine(playerPage, "No player/character selected.", WARNING)
addLine(playerPage, "Select target.")
else
addLine(playerPage, "Selected Model: " .. targetModel.Name)
local humanoid = targetModel:FindFirstChildOfClass("Humanoid")
local root = targetModel:FindFirstChild("HumanoidRootPart")
if humanoid then
addLine(playerPage, "Humanoid Health: " .. tostring(math.floor(humanoid.Health)) .. "/" .. tostring(math.floor(humanoid.MaxHealth)))
addLine(playerPage, "WalkSpeed: " .. tostring(humanoid.WalkSpeed))
addLine(playerPage, "JumpPower: " .. tostring(humanoid.JumpPower))
addLine(playerPage, "State: " .. tostring(humanoid:GetState()))
end
if root then
addLine(playerPage, "Position: " .. tostring(root.Position), WHITE_GREEN, 42)
end
local targetPlayer = Players:GetPlayerFromCharacter(targetModel)
if targetPlayer then
addLine(playerPage, "Player: " .. targetPlayer.Name)
addLine(playerPage, "DisplayName: " .. targetPlayer.DisplayName)
addLine(playerPage, "UserId: " .. tostring(targetPlayer.UserId))
addLine(playerPage, "AccountAge: " .. tostring(targetPlayer.AccountAge))
else
addLine(playerPage, "Type: NPC / non-player character", WHITE_GREEN)
end
end
addLine(playerPage, " ")
addLine(playerPage, "ONLINE PLAYERS", GREEN)
for _, plr in ipairs(Players:GetPlayers()) do
addButton(playerPage, plr.Name .. " // " .. plr.DisplayName, function()
if plr.Character then
selectObject(plr.Character, false)
refreshPlayerAnalyzer()
end
end, WHITE_GREEN)
end
end
function applySelectedStatAndRefresh(value)
if typeof(setSelectedStatValue) ~= "function" then
pushLog("Stat setter missing.")
return
end
setSelectedStatValue(value)
scanStatsAndCurrency()
refreshStatsScanner()
end
function applySelectedStatDelta(delta)
if not selectedStatObject then
pushLog("No stat selected.")
return
end
local current = tonumber(selectedStatObject.Value) or 0
applySelectedStatAndRefresh(current + delta)
end
refreshStatsScanner = function()
clearPage(statsScannerPage)
addLine(statsScannerPage, "STATS SCANNER", GREEN)
addLine(statsScannerPage, "Currency, time, points.", WHITE_GREEN)
addButton(statsScannerPage, "SCAN STATS", function()
scanStatsAndCurrency()
refreshStatsScanner()
end)
if #statScanResults == 0 then
scanStatsAndCurrency()
end
addLine(statsScannerPage, "Found: " .. tostring(#statScanResults), GREEN)
for _, stat in ipairs(statScanResults) do
addButton(statsScannerPage, stat.Name .. " = " .. stat.Value .. " // " .. stat.Source, function()
selectedStatObject = stat
refreshStatsScanner()
end, WHITE_GREEN)
end
addLine(statsScannerPage, " ")
if selectedStatObject then
addLine(statsScannerPage, "SELECTED STAT", GREEN)
addLine(statsScannerPage, "Name: " .. selectedStatObject.Name)
addLine(statsScannerPage, "Value: " .. tostring(selectedStatObject.Value))
addLine(statsScannerPage, "Type: " .. selectedStatObject.ClassName)
addLine(statsScannerPage, "Source: " .. selectedStatObject.Source)
-- path removed for register optimization
addButton(statsScannerPage, "SET 0", function()
applySelectedStatAndRefresh(0)
end)
addButton(statsScannerPage, "SET 100", function()
applySelectedStatAndRefresh(100)
end)
addButton(statsScannerPage, "SET 1000", function()
applySelectedStatAndRefresh(1000)
end)
addButton(statsScannerPage, "+100", function()
applySelectedStatDelta(100)
end)
addButton(statsScannerPage, "-100", function()
applySelectedStatDelta(-100)
end)
addLine(statsScannerPage, "Local only unless server allows it.", WARNING, 42)
else
addLine(statsScannerPage, "Select stat.", WARNING)
end
end
local function refreshObjectTracker()
clearPage(trackerPage)
addLine(trackerPage, "OBJECT TRACKER", GREEN)
addButton(trackerPage, "START TRACKER", function()
startObjectTracker()
refreshObjectTracker()
end)
addLine(trackerPage, "Status: " .. (trackedObjects.started and "RUNNING" or "OFF"), trackedObjects.started and GREEN or WARNING)
addLine(trackerPage, "Tracking objects.", WHITE_GREEN, 42)
addLine(trackerPage, " ")
addLine(trackerPage, "SCAN HISTORY", GREEN)
addButton(trackerPage, "SHOW LATEST DIFF", function()
pushObjectTrack(diffLatestScans())
refreshObjectTracker()
end)
for i = 1, math.min(10, #scanHistory) do
local item = scanHistory[i]
addButton(trackerPage, item.Time .. " // " .. item.Name, function()
pushObjectTrack("Selected history: " .. item.Path)
refreshObjectTracker()
end, WHITE_GREEN)
end
addLine(trackerPage, " ")
addLine(trackerPage, "RECENT OBJECT EVENTS", GREEN)
if #trackedObjectLog == 0 then
addLine(trackerPage, "No tracked events yet.", WARNING)
else
for i = 1, math.min(35, #trackedObjectLog) do
addLine(trackerPage, trackedObjectLog[i], WHITE_GREEN, 38)
end
end
end
local function refreshRuntimeAPI()
clearPage(runtimePage)
addLine(runtimePage, "RUNTIME API", GREEN)
addLine(runtimePage, "Safe compatibility check.", WHITE_GREEN)
addBlock(runtimePage, getRuntimeAPIReport())
addLine(runtimePage, "Executor-only APIs are not part of Roblox Studio.", WARNING, 42)
addLine(runtimePage, "Project Ghost uses normal LocalScript-safe systems only.", WHITE_GREEN, 42)
end
local function refreshRemotes()
clearPage(remotesPage)
addLine(remotesPage, "REMOTE SCANNER", GREEN)
local count = 0
for _, obj in ipairs(game:GetDescendants()) do
if obj:IsA("RemoteEvent") or obj:IsA("RemoteFunction") then
count += 1
addLine(remotesPage, "[" .. obj.ClassName .. "] " .. safeFullName(obj), WHITE_GREEN, 40)
end
end
addLine(remotesPage, "Total remotes: " .. tostring(count), GREEN)
addLine(remotesPage, " ")
addLine(remotesPage, "REMOTE LOGS", GREEN)
if #remoteLogs == 0 then
addLine(remotesPage, "No remote logs yet.", WARNING)
addLine(remotesPage, "Use _G.ProjectGhost_LogRemote(name, direction, ...) in your own scripts.", WHITE_GREEN, 42)
else
for _, line in ipairs(remoteLogs) do
addLine(remotesPage, line, WHITE_GREEN)
end
end
end
local function refreshRemoteMonitor()
clearPage(remoteMonitorPage)
scanAllRemotes()
addLine(remoteMonitorPage, "LIVE REMOTE MONITOR // USABLE ONLY", GREEN)
addLine(remoteMonitorPage, "Usable remotes only.", WHITE_GREEN, 50)
local list = {}
for _, stat in pairs(remoteStats) do
if stat.level ~= "LOCKED" then
table.insert(list, stat)
end
end
table.sort(list, function(a, b)
if a.score == b.score then
return a.path < b.path
end
return a.score > b.score
end)
if #list == 0 then
addLine(remoteMonitorPage, "No remotes found.", WARNING)
return
end
for _, stat in ipairs(list) do
local color = GREEN
if stat.level == "USABLE" then
color = GREEN
elseif stat.level == "TESTABLE" then
color = WHITE_GREEN
elseif stat.level == "CLIENT" then
color = Color3.fromRGB(80, 210, 255)
else
color = WARNING
end
addButton(remoteMonitorPage, "[" .. stat.level .. "] " .. stat.name .. " // " .. stat.category .. " // Score " .. tostring(stat.score), function()
for _, obj in ipairs(game:GetDescendants()) do
if (obj:IsA("RemoteEvent") or obj:IsA("RemoteFunction")) and safeFullName(obj) == stat.path then
selectedRemoteObject = obj
break
end
end
refreshRemoteMonitor()
end, color)
end
addLine(remoteMonitorPage, " ")
if selectedRemoteObject then
local stat = getRemoteStats(selectedRemoteObject)
addLine(remoteMonitorPage, "SELECTED REMOTE DETAILS", GREEN)
addLine(remoteMonitorPage, "Name: " .. stat.name)
addLine(remoteMonitorPage, "Class: " .. stat.className)
addLine(remoteMonitorPage, "Use Status: " .. stat.level, stat.level == "USABLE" and GREEN or stat.level == "CLIENT" and Color3.fromRGB(80, 210, 255) or WHITE_GREEN)
addLine(remoteMonitorPage, "Category: " .. stat.category)
addLine(remoteMonitorPage, "Score: " .. tostring(stat.score))
addLine(remoteMonitorPage, "Path: " .. stat.path, WHITE_GREEN, 48)
addLine(remoteMonitorPage, "Logged Calls: " .. tostring(stat.callCount))
addLine(remoteMonitorPage, "Last Args: " .. tostring(stat.lastArgs), WHITE_GREEN, 44)
addLine(remoteMonitorPage, "Last Time: " .. tostring(stat.lastTime))
local sourceText = "-- Remote Analysis\\n"
.. "Remote: " .. stat.name .. "\\n"
.. "Type: " .. stat.className .. "\\n"
.. "Use Status: " .. stat.level .. "\\n"
.. "Category: " .. stat.category .. "\\n\\n"
.. "-- This is a monitor view. It does not auto-fire unknown remotes.\\n"
.. "-- Add safe test actions manually inside your game's Tools page."
addBlock(remoteMonitorPage, sourceText)
addButton(remoteMonitorPage, "PIN REMOTE TO LOGS", function()
pushLog("Pinned remote: " .. stat.path .. " [" .. stat.level .. " / " .. stat.category .. "]")
switchPage("Logs")
refreshLogs()
end)
addButton(remoteMonitorPage, "SELECT REMOTE AS TARGET", function()
selectObject(selectedRemoteObject, false)
switchPage("Scanner")
refreshScanner()
end)
else
addLine(remoteMonitorPage, "Select remote.", WARNING)
end
addLine(remoteMonitorPage, " ")
addLine(remoteMonitorPage, "RECENT REMOTE ACTIVITY", GREEN)
if #remoteLogs == 0 then
addLine(remoteMonitorPage, "No remote calls logged yet.", WARNING)
else
for i = 1, math.min(12, #remoteLogs) do
addLine(remoteMonitorPage, remoteLogs[i], WHITE_GREEN)
end
end
end
local function refreshExplorer()
clearPage(explorerPage)
addLine(explorerPage, "VISIBLE EXPLORER", GREEN)
local roots = {
workspace,
RS,
Lighting,
game:GetService("StarterGui"),
game:GetService("StarterPlayer"),
}
for _, root in ipairs(roots) do
addLine(explorerPage, "▼ " .. root.Name, GREEN)
for _, child in ipairs(root:GetChildren()) do
addButton(explorerPage, "[" .. child.ClassName .. "] " .. child.Name, function()
selectObject(child, false)
switchPage("Scanner")
refreshScanner()
end, WHITE_GREEN)
end
end
end
local function refreshStats()
clearPage(statsPage)
addLine(statsPage, "RUNTIME STATS", GREEN)
addLine(statsPage, "FPS: " .. tostring(fps))
addLine(statsPage, "Memory: " .. string.format("%.2f MB", Stats:GetTotalMemoryUsageMb()))
addLine(statsPage, "Players: " .. tostring(#Players:GetPlayers()))
addLine(statsPage, "Objects: " .. tostring(#game:GetDescendants()))
addLine(statsPage, "Selected: " .. (selectedObject and selectedObject.Name or "none"))
addLine(statsPage, "Selected Script: " .. (selectedScriptObject and selectedScriptObject.Name or "none"))
addLine(statsPage, "Logs: " .. tostring(#ghostLogs))
addLine(statsPage, "Remote Logs: " .. tostring(#remoteLogs))
end
local function refreshLogs()
clearPage(logsPage)
addLine(logsPage, "GHOST LOGS", GREEN)
for _, line in ipairs(ghostLogs) do
addLine(logsPage, line, WHITE_GREEN)
end
if #ghostLogs == 0 then
addLine(logsPage, "No logs.")
end
end
refreshCurrent = function()
if currentPage == "Scanner" then refreshScanner() end
if currentPage == "Script Viewer" then refreshScriptViewer() end
if currentPage == "Code Registry" then refreshCodeRegistry() end
if currentPage == "Tools" then refreshTools() end
if currentPage == "Property Editor" then refreshPropertyEditor() end
if currentPage == "Player Analyzer" then refreshPlayerAnalyzer() end
if currentPage == "Stats Scanner" then refreshStatsScanner() end
if currentPage == "Object Tracker" then refreshObjectTracker() end
if currentPage == "Runtime API" then refreshRuntimeAPI() end
if currentPage == "Remotes" then refreshRemotes() end
if currentPage == "Remote Monitor" then refreshRemoteMonitor() end
if currentPage == "Explorer" then refreshExplorer() end
if currentPage == "Stats" then refreshStats() end
if currentPage == "Logs" then refreshLogs() end
end
if not quickActionsReady then
quickActionsReady = true
makeQuickButton("SPAWN VEHICLE", 120, function()
switchPage("Tools")
refreshTools()
end)
makeQuickButton("REMOTE MONITOR", 275, function()
switchPage("Remote Monitor")
refreshRemoteMonitor()
end)
makeQuickButton("PROPERTY EDITOR", 430, function()
switchPage("Property Editor")
refreshPropertyEditor()
end)
makeQuickButton("PLAYER INFO", 585, function()
switchPage("Player Analyzer")
refreshPlayerAnalyzer()
end)
makeQuickButton("OBJECT TRACKER", 740, function()
switchPage("Object Tracker")
refreshObjectTracker()
end)
end
--=====================================================
-- DRAGGABLE PANEL
--=====================================================
local draggingGhostPanel = false
local dragStartPosition = nil
local panelStartPosition = nil
top.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
draggingGhostPanel = true
dragStartPosition = input.Position
panelStartPosition = main.Position
input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End then
draggingGhostPanel = false
end
end)
end
end)
UIS.InputChanged:Connect(function(input)
if draggingGhostPanel and input.UserInputType == Enum.UserInputType.MouseMovement then
local delta = input.Position - dragStartPosition
main.Position = UDim2.new(
panelStartPosition.X.Scale,
panelStartPosition.X.Offset + delta.X,
panelStartPosition.Y.Scale,
panelStartPosition.Y.Offset + delta.Y
)
clampPanelToScreen()
end
end)
--=====================================================
-- ACTION MENU
--=====================================================
actionMenu = nil
actionTitle = nil
local function buildActionMenu()
actionMenu = Instance.new("Frame")
actionMenu.Name = "GhostTargetActionMenu"
actionMenu.Size = UDim2.new(0, 155, 0, 245)
actionMenu.BackgroundColor3 = BLACK
actionMenu.BorderSizePixel = 0
actionMenu.Visible = false
actionMenu.ZIndex = 50
actionMenu.Parent = gui
local menuStroke = Instance.new("UIStroke")
menuStroke.Color = GREEN
menuStroke.Thickness = 2
menuStroke.Parent = actionMenu
local menuCorner = Instance.new("UICorner")
menuCorner.CornerRadius = UDim.new(0, 6)
menuCorner.Parent = actionMenu
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 3)
layout.SortOrder = Enum.SortOrder.LayoutOrder
layout.Parent = actionMenu
local padding = Instance.new("UIPadding")
padding.PaddingTop = UDim.new(0, 5)
padding.PaddingLeft = UDim.new(0, 5)
padding.PaddingRight = UDim.new(0, 5)
padding.PaddingBottom = UDim.new(0, 5)
padding.Parent = actionMenu
actionTitle = Instance.new("TextLabel")
actionTitle.Size = UDim2.new(1, 0, 0, 21)
actionTitle.BackgroundColor3 = BLACK
actionTitle.BorderSizePixel = 0
actionTitle.Text = "TARGET ACTIONS"
actionTitle.TextColor3 = WHITE_GREEN
actionTitle.Font = Enum.Font.Code
actionTitle.TextSize = 10
actionTitle.TextXAlignment = Enum.TextXAlignment.Left
actionTitle.ZIndex = 51
actionTitle.Parent = actionMenu
local titlePadding = Instance.new("UIPadding")
titlePadding.PaddingLeft = UDim.new(0, 5)
titlePadding.Parent = actionTitle
local function addMenuButton(text, callback, color)
local b = Instance.new("TextButton")
b.Size = UDim2.new(1, 0, 0, 23)
b.BackgroundColor3 = BLACK_2
b.BorderSizePixel = 0
b.Text = text
b.TextColor3 = color or GREEN
b.Font = Enum.Font.Code
b.TextSize = 10
b.TextXAlignment = Enum.TextXAlignment.Left
b.AutoButtonColor = false
b.ZIndex = 51
b.Parent = actionMenu
local bp = Instance.new("UIPadding")
bp.PaddingLeft = UDim.new(0, 8)
bp.Parent = b
local bs = Instance.new("UIStroke")
bs.Color = color or GREEN
bs.Thickness = 1
bs.Transparency = 0.35
bs.Parent = b
b.MouseEnter:Connect(function()
b.BackgroundColor3 = Color3.fromRGB(0, 35, 16)
end)
b.MouseLeave:Connect(function()
b.BackgroundColor3 = BLACK_2
end)
b.MouseButton1Click:Connect(function()
actionMenu.Visible = false
callback()
end)
return b
end
addMenuButton("VIEW DETAILS", function()
if selectedObject then
main.Visible = true
switchPage("Scanner")
refreshScanner()
end
end)
addMenuButton("SCRIPT VIEWER", function()
if selectedObject then
main.Visible = true
switchPage("Script Viewer")
refreshScriptViewer()
end
end)
addMenuButton("PROPERTY EDITOR", function()
if selectedObject then
main.Visible = true
switchPage("Property Editor")
refreshPropertyEditor()
end
end)
addMenuButton("PLAYER INFO", function()
if selectedObject then
main.Visible = true
switchPage("Player Analyzer")
refreshPlayerAnalyzer()
end
end)
addMenuButton("STATS SCANNER", function()
main.Visible = true
switchPage("Stats Scanner")
refreshStatsScanner()
end)
addMenuButton("REMOTE MONITOR", function()
main.Visible = true
switchPage("Remote Monitor")
refreshRemoteMonitor()
end)
addMenuButton("DUPE / CLONE", function()
if selectedObject then
local ok, msg = cloneSelectedObject()
pushLog("Dupe: " .. tostring(msg))
main.Visible = true
switchPage("Tools")
refreshTools()
end
end)
addMenuButton("PIN PATH", function()
if selectedObject then
pushLog("Pinned: " .. safeFullName(selectedObject))
main.Visible = true
switchPage("Logs")
refreshLogs()
end
end)
addMenuButton("DELETE COPY", function()
if selectedObject and selectedObject:IsDescendantOf(workspace) and selectedObject ~= workspace then
local old = selectedObject
local name = old.Name
local backup = nil
local parent = old.Parent
local cf = getPivotOrCFrame(old)
pcall(function()
backup = old:Clone()
end)
pcall(function()
old:Destroy()
end)
if backup then
pushUndo({Type = "Delete", Clone = backup, Parent = parent, CFrame = cf})
end
selectedObject = nil
pushLog("Deleted: " .. name)
main.Visible = true
switchPage("Logs")
refreshLogs()
else
pushLog("Delete blocked.")
end
end, BAD)
addMenuButton("CLOSE", function()
actionMenu.Visible = false
end, WHITE_GREEN)
end
buildActionMenu()
function getActionMenuAdornee()
if not selectedObject then
return nil
end
if selectedObject:IsA("BasePart") then
return selectedObject
end
if selectedObject:IsA("Model") then
return selectedObject.PrimaryPart or selectedObject:FindFirstChildWhichIsA("BasePart", true)
end
if selectedObject.Parent and selectedObject.Parent:IsA("Model") then
return selectedObject.Parent.PrimaryPart or selectedObject.Parent:FindFirstChildWhichIsA("BasePart", true)
end
return nil
end
function updateActionMenuPosition()
if not actionMenu or not actionMenu.Visible then
return
end
if not selectedObject then
actionMenu.Visible = false
return
end
camera = workspace.CurrentCamera
if not camera then
return
end
part = actionMenuFollowPart or getActionMenuAdornee()
if not part or not part:IsDescendantOf(game) then
actionMenu.Visible = false
return
end
worldPos = part.Position + Vector3.new(0, math.max(part.Size.Y + 2, 5), 0)
screenPos, onScreen = camera:WorldToViewportPoint(worldPos)
if not onScreen then
actionMenu.Visible = false
return
end
local viewport = camera.ViewportSize
local x = math.clamp(screenPos.X - 77, 8, viewport.X - 163)
local y = math.clamp(screenPos.Y - 24, 8, viewport.Y - 253)
actionMenu.Position = UDim2.new(0, x, 0, y)
end
function showActionMenuForSelected()
if not selectedObject then
actionMenu.Visible = false
return
end
actionMenuFollowPart = getActionMenuAdornee()
actionMenu.Parent = gui
actionMenu.Size = UDim2.new(0, 155, 0, 245)
actionTitle.Text = "TARGET: " .. string.sub(selectedObject.Name, 1, 18)
actionMenu.Visible = true
updateActionMenuPosition()
end
UIS.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 and actionMenu and actionMenu.Visible then
local pos = UIS:GetMouseLocation()
local inside =
pos.X >= actionMenu.AbsolutePosition.X and
pos.X <= actionMenu.AbsolutePosition.X + actionMenu.AbsoluteSize.X and
pos.Y >= actionMenu.AbsolutePosition.Y and
pos.Y <= actionMenu.AbsolutePosition.Y + actionMenu.AbsoluteSize.Y
if not inside then
actionMenu.Visible = false
end
end
end)
--=====================================================
-- SCANNER TOOL
--=====================================================
local tool = nil
local equipped = false
local function createGhostScannerTool()
local oldTool = backpack:FindFirstChild("Ghost Scanner")
if oldTool then oldTool:Destroy() end
if player.Character then
local charTool = player.Character:FindFirstChild("Ghost Scanner")
if charTool then charTool:Destroy() end
end
tool = Instance.new("Tool")
tool.Name = "Ghost Scanner"
tool.RequiresHandle = true
tool.CanBeDropped = false
tool.ToolTip = "Click target"
tool.Parent = backpack
local handle = Instance.new("Part")
handle.Name = "Handle"
handle.Size = Vector3.new(0.4, 1.2, 0.4)
handle.Material = Enum.Material.Neon
handle.Color = GREEN
handle.CanCollide = false
handle.Parent = tool
local light = Instance.new("PointLight")
light.Color = GREEN
light.Brightness = 2
light.Range = 8
light.Parent = handle
tool.Equipped:Connect(function()
equipped = true
pushLog("Scanner equipped.")
end)
tool.Unequipped:Connect(function()
equipped = false
actionMenu.Visible = false
pushLog("Scanner unequipped.")
end)
tool.Activated:Connect(function()
if not equipped then return end
scanToken += 1
local thisToken = scanToken
scanStatus = "SCANNING"
local target = findTargetFromMouse()
if not target then
pushLog("No target.")
return
end
local fixedTarget = target
selectObject(fixedTarget, true)
task.delay(0.05, function()
if thisToken ~= scanToken then return end
if selectedObject ~= fixedTarget then return end
scanStatus = "DONE"
showActionMenuForSelected()
end)
end)
pushLog("Scanner ready.")
end
createGhostScannerTool()
player.CharacterAdded:Connect(function()
task.wait(0.6)
if gui and gui.Parent then
createGhostScannerTool()
end
end)
--=====================================================
-- BUTTONS / LOOPS
--=====================================================
refreshBtn.MouseButton1Click:Connect(smartRefresh)
ghostIcon.MouseButton1Click:Connect(function()
main.Visible = not main.Visible
if main.Visible then
refreshCurrent()
end
end)
minimizeBtn.MouseButton1Click:Connect(function()
main.Visible = false
end)
closeBtn.MouseButton1Click:Connect(function()
actionMenu.Visible = false
main.Visible = false
if tool then
pcall(function()
tool:Destroy()
end)
end
if gui then
pcall(function()
gui:Destroy()
end)
end
end)
RunService.RenderStepped:Connect(function()
local now = os.clock()
fps = math.floor(1 / math.max(now - lastFrame, 0.001))
lastFrame = now
if targetText then
targetText.Text = "TARGET: " .. (selectedObject and string.sub(selectedObject.Name, 1, 28) or "none")
end
if uptimeText then
local alive = math.floor(os.clock())
local m = math.floor(alive / 60)
local s = alive % 60
uptimeText.Text = string.format("UPTIME: %02d:%02d", m, s)
end
if terminalLine then
terminalLine.Text = "ghost@terminal:~$ " .. tostring(currentPage) .. " // " .. tostring(#remoteLogs) .. " remote logs"
end
updateActionMenuPosition()
if main.Visible and #outlineDots > 0 then
local speed = os.clock() * 0.45
local count = #outlineDots
local head = (math.floor(speed * count) % count) + 1
for i, dot in ipairs(outlineDots) do
local distance = math.abs(i - head)
local wrapDistance = count - distance
local d = math.min(distance, wrapDistance)
if d < 4 then
dot.BackgroundTransparency = 0.02
dot.Size = UDim2.new(0, 9, 0, 9)
elseif d < 10 then
dot.BackgroundTransparency = 0.20
dot.Size = UDim2.new(0, 7, 0, 7)
else
dot.BackgroundTransparency = 0.68
dot.Size = UDim2.new(0, 5, 0, 5)
end
end
-- Smooth bright segment riding along the outline, like a neon stroke.
for i, h in ipairs(highlightArc) do
local idx = ((head + i - 2) % count) + 1
local ref = outlineDots[idx]
h.Position = ref.Position
h.BackgroundTransparency = math.clamp((i - 1) / #highlightArc, 0, 0.75)
h.Size = UDim2.new(0, 10 - math.floor(i / 4), 0, 10 - math.floor(i / 4))
end
local pulse = (math.sin(os.clock() * 3) + 1) / 2
for _, eye in ipairs(eyeDots) do
eye.BackgroundTransparency = 0.38 - pulse * 0.08
end
if bodyFill then
bodyFill.BackgroundTransparency = 0.975 - pulse * 0.015
bodyStroke.Transparency = 0.88 - pulse * 0.08
end
end
end)
task.spawn(function()
while gui.Parent do
if main.Visible then
smartRefresh()
end
task.wait(0.5)
end
end)
UIS.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.KeyCode == TOGGLE_KEY then
main.Visible = not main.Visible
if main.Visible then
clampPanelToScreen()
smartRefresh()
end
elseif input.KeyCode == Enum.KeyCode.Home then
resetPanelPosition()
end
end)
-- Startup
scanAllRemotes()
pushLog("Ghost online.")
pushLog("Remote monitor active.")
pushLog("Modules loaded.")
pushLog("Stats scanner ready.")
pushLog("Ghost Scanner tool added to Backpack.")
pushLog("Code Registry entries: " .. tostring(#CODE_REGISTRY))
switchPage("Scanner")
refreshScanner()
To embed this project on your website, copy the following code and paste it into your website's HTML: