pulse-rb 1.2.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -0
- package/adapters/linoria.lua +233 -0
- package/adapters/windui.llms.txt +366 -0
- package/adapters/windui.lua +505 -0
- package/bin/rb.js +2 -0
- package/dist/index.js +3285 -0
- package/package.json +59 -0
- package/pulse/dev/debuggui.lua +1206 -0
- package/pulse/dev/devconfig.lua +81 -0
- package/pulse/dev/ui/9_DevPanel.lua +384 -0
- package/pulse/helpers/aim.lua +193 -0
- package/pulse/helpers/cache.lua +68 -0
- package/pulse/helpers/cleaner.lua +110 -0
- package/pulse/helpers/conn.lua +33 -0
- package/pulse/helpers/cooldown.lua +47 -0
- package/pulse/helpers/draw.lua +122 -0
- package/pulse/helpers/hitbox.lua +63 -0
- package/pulse/helpers/input.lua +24 -0
- package/pulse/helpers/log.lua +228 -0
- package/pulse/helpers/loop.lua +58 -0
- package/pulse/helpers/memory.lua +48 -0
- package/pulse/helpers/narrate.lua +160 -0
- package/pulse/helpers/notify.lua +51 -0
- package/pulse/helpers/perf.lua +48 -0
- package/pulse/helpers/remote.lua +128 -0
- package/pulse/helpers/restore.lua +59 -0
- package/pulse/helpers/store.lua +39 -0
- package/pulse/helpers/team.lua +83 -0
- package/pulse/helpers/testmode.lua +80 -0
- package/pulse/helpers/trace.lua +111 -0
- package/pulse/helpers/track.lua +85 -0
- package/pulse/helpers/vec.lua +52 -0
- package/pulse/helpers/world.lua +51 -0
- package/pulse/runtime.lua +343 -0
- package/pulse/ui/linoria_settings.lua +55 -0
- package/pulse/ui/windui_settings.lua +87 -0
- package/templates/AGENTS.md +177 -0
- package/templates/CLAUDE.md +424 -0
- package/templates/deploy_config.example +17 -0
- package/templates/example_esp.rblua +69 -0
- package/templates/example_fov.rblua +20 -0
- package/templates/example_speed.rblua +25 -0
- package/templates/gitignore +4 -0
- package/templates/globals.lua +66 -0
- package/templates/layout.rblua +28 -0
- package/templates/module.lua +7 -0
- package/templates/module.rblua +32 -0
- package/templates/page_home.rblua +9 -0
- package/templates/remote.lua +6 -0
- package/templates/remotes.lua +14 -0
- package/vscode/language-configuration.json +35 -0
- package/vscode/package.json +35 -0
- package/vscode/src/extension.js +397 -0
- package/vscode/syntaxes/rblua.tmLanguage.json +126 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════════════
|
|
2
|
+
-- DEVELOPER CONFIGURATION — the only file you edit per debug session
|
|
3
|
+
--
|
|
4
|
+
-- Scenarios and which tools to use:
|
|
5
|
+
--
|
|
6
|
+
-- "Feature randomly stopped working"
|
|
7
|
+
-- → Pulse.Log.watchSignal(Components.Aimbot.enabled, "aimbot", "enabled")
|
|
8
|
+
-- Logs the exact moment and value every time the toggle changes.
|
|
9
|
+
--
|
|
10
|
+
-- "I don't know if the feature is even running"
|
|
11
|
+
-- → Pulse.Monitor.tick("aimbot") inside the loop body
|
|
12
|
+
-- Counter in monitor bar stops climbing = loop is dead.
|
|
13
|
+
--
|
|
14
|
+
-- "What data does the function receive / return?"
|
|
15
|
+
-- → Pulse.Trace.instrument(func, "globals")
|
|
16
|
+
-- Every func.* call logs actual argument values and return values.
|
|
17
|
+
--
|
|
18
|
+
-- "Something threw but I don't know where"
|
|
19
|
+
-- → Framework errors (Signal, Event, Component) now route to Pulse.Log.error
|
|
20
|
+
-- automatically — check the debug UI, filter level=ERR.
|
|
21
|
+
--
|
|
22
|
+
-- "Narrow to one feature so I'm not overwhelmed"
|
|
23
|
+
-- → Pulse.TestMode.configure({ target = "FeatureName" })
|
|
24
|
+
-- All other toggles start disabled.
|
|
25
|
+
-- ═══════════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
-- ── 1. Logging ────────────────────────────────────────────────────
|
|
28
|
+
Pulse.Log.configure({
|
|
29
|
+
level = "debug", -- trace | debug | info | warn | error
|
|
30
|
+
console = true,
|
|
31
|
+
file = "pulse_dev.log",
|
|
32
|
+
|
|
33
|
+
-- Restrict to specific tags (whitelist):
|
|
34
|
+
-- tags = { "aimbot", "globals" },
|
|
35
|
+
|
|
36
|
+
-- Or exclude noisy tags:
|
|
37
|
+
-- except = { "component", "loop" },
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
-- ── 2. Test mode ──────────────────────────────────────────────────
|
|
41
|
+
-- Change target = "FeatureName" to start with only that feature on.
|
|
42
|
+
Pulse.TestMode.configure({
|
|
43
|
+
all = true, -- ← change to: target = "ShifterPartsCutter"
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
-- ── 3. Signal watching — "why did this feature turn off?" ─────────
|
|
47
|
+
-- Logs every value change on the signal, tagged so you can search it.
|
|
48
|
+
-- Uncomment and change to whatever component you're investigating.
|
|
49
|
+
--
|
|
50
|
+
-- Pulse.Log.watchSignal(Components.ShifterPartsCutter.enabled, "cutter", "enabled")
|
|
51
|
+
-- Pulse.Log.watchSignal(Components.Aimbot.enabled, "aimbot", "enabled")
|
|
52
|
+
|
|
53
|
+
-- ── 4. Function tracing — "what data does it receive/return?" ─────
|
|
54
|
+
-- Shows actual argument values and return values for every func.* call.
|
|
55
|
+
-- Can be noisy — use tags filter to narrow (e.g. tags = { "globals" }).
|
|
56
|
+
-- Uncomment when you need to see data flowing through helpers.
|
|
57
|
+
--
|
|
58
|
+
-- Pulse.Trace.instrument(func, "globals")
|
|
59
|
+
--
|
|
60
|
+
-- Or trace just one function:
|
|
61
|
+
-- func.IsWarrior = Pulse.Trace.wrap(func.IsWarrior, "globals", "IsWarrior")
|
|
62
|
+
|
|
63
|
+
-- ── 5. Execution probe — "is this loop even running?" ─────────────
|
|
64
|
+
-- Add Pulse.Monitor.tick("featureName") inside any loop body.
|
|
65
|
+
-- The monitor bar shows the counter — stops increasing = loop is dead.
|
|
66
|
+
-- (Done in your component code, not here — this is just a reminder)
|
|
67
|
+
|
|
68
|
+
-- ── 6. Narration ──────────────────────────────────────────────────
|
|
69
|
+
-- Auto-announces warn+error entries as on-screen toasts + sound.
|
|
70
|
+
Pulse.Narrate.configure({
|
|
71
|
+
sound = true,
|
|
72
|
+
volume = 0.4,
|
|
73
|
+
autoLevel = "warn", -- "error" for less noise
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
-- ── 7. Initial monitors ───────────────────────────────────────────
|
|
77
|
+
Pulse.Monitor.set("titans", 0)
|
|
78
|
+
Pulse.Monitor.set("shifters", 0)
|
|
79
|
+
Pulse.Monitor.set("players", 0)
|
|
80
|
+
|
|
81
|
+
Pulse.Log.info("devlog", "dev build active")
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
-- [pulse/dev/ui/9_DevPanel.lua]
|
|
2
|
+
-- WindUI Developer tab — injected in the UI phase of --dev builds only.
|
|
3
|
+
-- Runs AFTER layout.rblua has created the WindUI window, so _windWindow is live.
|
|
4
|
+
-- Adds Tools (Hydroxide, Dex) and Scanner (context-optimised game intelligence).
|
|
5
|
+
-- Linoria builds: _windWindow is nil → early return, no-op.
|
|
6
|
+
|
|
7
|
+
if not _windWindow then return end
|
|
8
|
+
|
|
9
|
+
-- ── Clipboard ─────────────────────────────────────────────────────────────────
|
|
10
|
+
local function _devCopy(text)
|
|
11
|
+
local ok = false
|
|
12
|
+
if not ok then pcall(function() setclipboard(text); ok = true end) end
|
|
13
|
+
if not ok then pcall(function() toclipboard(text); ok = true end) end
|
|
14
|
+
if not ok then pcall(function() writeclipboard(text); ok = true end) end
|
|
15
|
+
return ok
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
-- ── Path compression ──────────────────────────────────────────────────────────
|
|
19
|
+
-- Longest prefix first so LP. doesn't match before PG.
|
|
20
|
+
local _ALIASES = {
|
|
21
|
+
{ "Players.LocalPlayer.PlayerGui.", "PG." },
|
|
22
|
+
{ "Players.LocalPlayer.", "LP." },
|
|
23
|
+
{ "ReplicatedStorage.", "RS." },
|
|
24
|
+
{ "ReplicatedFirst.", "RF." },
|
|
25
|
+
{ "workspace.", "WS." },
|
|
26
|
+
{ "StarterGui.", "SG." },
|
|
27
|
+
{ "ServerScriptService.", "SSS." },
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
local function _compress(obj)
|
|
31
|
+
local parts = {}
|
|
32
|
+
local cur = obj
|
|
33
|
+
while cur and cur ~= game do
|
|
34
|
+
table.insert(parts, 1, cur.Name)
|
|
35
|
+
cur = cur.Parent
|
|
36
|
+
end
|
|
37
|
+
local s = table.concat(parts, ".")
|
|
38
|
+
for _, pair in ipairs(_ALIASES) do
|
|
39
|
+
if s:sub(1, #pair[1]) == pair[1] then
|
|
40
|
+
return pair[2] .. s:sub(#pair[1] + 1)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
return s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
-- ── Scanner ───────────────────────────────────────────────────────────────────
|
|
47
|
+
-- Returns: fullText, remoteCount, scriptCount, instanceCount, valueCount
|
|
48
|
+
local function _runScan()
|
|
49
|
+
local RS = game:GetService("ReplicatedStorage")
|
|
50
|
+
local PG = game:GetService("Players").LocalPlayer.PlayerGui
|
|
51
|
+
local RF = game:GetService("ReplicatedFirst")
|
|
52
|
+
local WS = workspace
|
|
53
|
+
local out = {}
|
|
54
|
+
|
|
55
|
+
-- Header
|
|
56
|
+
local gameName = "?"
|
|
57
|
+
pcall(function()
|
|
58
|
+
gameName = game:GetService("MarketplaceService"):GetProductInfo(game.PlaceId).Name
|
|
59
|
+
end)
|
|
60
|
+
table.insert(out, ("[%s] pid:%d %s"):format(gameName, game.PlaceId, os.date("%Y-%m-%d %H:%M")))
|
|
61
|
+
|
|
62
|
+
-- ── Remotes ───────────────────────────────────────────────────────────────
|
|
63
|
+
local remotes = {}
|
|
64
|
+
local function _scanRemotes(inst, depth)
|
|
65
|
+
if depth > 8 then return end
|
|
66
|
+
local ok, ch = pcall(function() return inst:GetChildren() end)
|
|
67
|
+
if not ok then return end
|
|
68
|
+
for _, c in ipairs(ch) do
|
|
69
|
+
local cls = c.ClassName
|
|
70
|
+
if cls == "RemoteEvent" or cls == "RemoteFunction" then
|
|
71
|
+
table.insert(remotes, { p = _compress(c), t = cls == "RemoteEvent" and "RE" or "RF" })
|
|
72
|
+
end
|
|
73
|
+
if c:IsA("Folder") or c:IsA("Configuration") or c:IsA("Model") then
|
|
74
|
+
_scanRemotes(c, depth + 1)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
pcall(_scanRemotes, RS, 0)
|
|
79
|
+
table.sort(remotes, function(a, b) return a.p < b.p end)
|
|
80
|
+
|
|
81
|
+
table.insert(out, ("\nREMOTES (%d)"):format(#remotes))
|
|
82
|
+
for i, r in ipairs(remotes) do
|
|
83
|
+
if i > 40 then
|
|
84
|
+
table.insert(out, (" ...+%d more"):format(#remotes - 40))
|
|
85
|
+
break
|
|
86
|
+
end
|
|
87
|
+
table.insert(out, (" %-52s %s"):format(r.p, r.t))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
-- ── LocalScripts ──────────────────────────────────────────────────────────
|
|
91
|
+
local scripts = {}
|
|
92
|
+
local function _scanScripts(inst)
|
|
93
|
+
local ok, ch = pcall(function() return inst:GetChildren() end)
|
|
94
|
+
if not ok then return end
|
|
95
|
+
for _, c in ipairs(ch) do
|
|
96
|
+
if c:IsA("LocalScript") and c.Name ~= "RobloxPromptGui" then
|
|
97
|
+
table.insert(scripts, _compress(c))
|
|
98
|
+
end
|
|
99
|
+
local ok2, gc = pcall(function() return c:GetChildren() end)
|
|
100
|
+
if ok2 then
|
|
101
|
+
for _, g in ipairs(gc) do
|
|
102
|
+
if g:IsA("LocalScript") then
|
|
103
|
+
table.insert(scripts, _compress(g))
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
pcall(_scanScripts, PG)
|
|
110
|
+
pcall(_scanScripts, RF)
|
|
111
|
+
table.sort(scripts)
|
|
112
|
+
|
|
113
|
+
table.insert(out, ("\nSCRIPTS (%d)"):format(#scripts))
|
|
114
|
+
for i, s in ipairs(scripts) do
|
|
115
|
+
if i > 15 then
|
|
116
|
+
table.insert(out, (" ...+%d more"):format(#scripts - 15))
|
|
117
|
+
break
|
|
118
|
+
end
|
|
119
|
+
table.insert(out, " " .. s)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
-- ── Key instances ─────────────────────────────────────────────────────────
|
|
123
|
+
local instances = {}
|
|
124
|
+
local ok1, wsKids = pcall(function() return WS:GetChildren() end)
|
|
125
|
+
local ok2, rsKids = pcall(function() return RS:GetChildren() end)
|
|
126
|
+
if ok1 then
|
|
127
|
+
for _, c in ipairs(wsKids) do
|
|
128
|
+
if c:IsA("Folder") or c:IsA("Model") then
|
|
129
|
+
local n = 0
|
|
130
|
+
pcall(function() n = #c:GetChildren() end)
|
|
131
|
+
if n > 0 then
|
|
132
|
+
table.insert(instances, { p = _compress(c), cls = c.ClassName, n = n })
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
if ok2 then
|
|
138
|
+
for _, c in ipairs(rsKids) do
|
|
139
|
+
if c:IsA("Folder") then
|
|
140
|
+
local n = 0
|
|
141
|
+
pcall(function() n = #c:GetChildren() end)
|
|
142
|
+
if n > 0 then
|
|
143
|
+
table.insert(instances, { p = _compress(c), cls = "Folder", n = n })
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
table.sort(instances, function(a, b) return a.p < b.p end)
|
|
149
|
+
|
|
150
|
+
table.insert(out, ("\nKEY INSTANCES (%d)"):format(#instances))
|
|
151
|
+
for i, inst in ipairs(instances) do
|
|
152
|
+
if i > 20 then
|
|
153
|
+
table.insert(out, (" ...+%d more"):format(#instances - 20))
|
|
154
|
+
break
|
|
155
|
+
end
|
|
156
|
+
table.insert(out, (" %-48s %s(%d)"):format(
|
|
157
|
+
inst.p,
|
|
158
|
+
inst.cls == "Folder" and "Folder " or "Model ",
|
|
159
|
+
inst.n
|
|
160
|
+
))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
-- ── Notable values ────────────────────────────────────────────────────────
|
|
164
|
+
local values = {}
|
|
165
|
+
if ok2 then
|
|
166
|
+
for _, c in ipairs(rsKids) do
|
|
167
|
+
local cls = c.ClassName
|
|
168
|
+
if cls == "StringValue" or cls == "IntValue" or cls == "BoolValue" or cls == "NumberValue" then
|
|
169
|
+
local v = "?"
|
|
170
|
+
pcall(function() v = tostring(c.Value) end)
|
|
171
|
+
table.insert(values, { p = _compress(c), v = v })
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
table.insert(out, ("\nVALUES (%d)"):format(#values))
|
|
177
|
+
for i, val in ipairs(values) do
|
|
178
|
+
if i > 12 then
|
|
179
|
+
table.insert(out, (" ...+%d more"):format(#values - 12))
|
|
180
|
+
break
|
|
181
|
+
end
|
|
182
|
+
table.insert(out, (" %-48s %s"):format(val.p, val.v))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
return table.concat(out, "\n"), #remotes, #scripts, #instances, #values
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
-- ── State ─────────────────────────────────────────────────────────────────────
|
|
189
|
+
local _lastScanText = ""
|
|
190
|
+
local _scanRunning = false
|
|
191
|
+
local _scanStatusPara = nil -- assigned after UI is built
|
|
192
|
+
|
|
193
|
+
-- Forward-declared so button callbacks can reference it before the body is defined.
|
|
194
|
+
local _doScan
|
|
195
|
+
|
|
196
|
+
-- ── Build WindUI Dev tab ───────────────────────────────────────────────────────
|
|
197
|
+
local _devTab
|
|
198
|
+
pcall(function()
|
|
199
|
+
_devTab = _windWindow:Tab({ Title = "Dev", Icon = "bug" })
|
|
200
|
+
end)
|
|
201
|
+
|
|
202
|
+
if not _devTab then
|
|
203
|
+
Pulse.Log.warn("dev-panel", "Dev tab skipped — _windWindow:Tab() failed")
|
|
204
|
+
return
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
-- Single-column VStack (no 2-column split needed for dev tools)
|
|
208
|
+
local _col
|
|
209
|
+
pcall(function() _col = _devTab:VStack({}) end)
|
|
210
|
+
_col = _col or _devTab
|
|
211
|
+
|
|
212
|
+
-- ── Tools section ─────────────────────────────────────────────────────────────
|
|
213
|
+
local _toolSect
|
|
214
|
+
pcall(function()
|
|
215
|
+
_toolSect = _col:Section({
|
|
216
|
+
Title = "Tools",
|
|
217
|
+
Icon = "wrench",
|
|
218
|
+
Box = true,
|
|
219
|
+
BoxBorder = true,
|
|
220
|
+
Opened = true,
|
|
221
|
+
})
|
|
222
|
+
end)
|
|
223
|
+
|
|
224
|
+
local _TOOLS = {
|
|
225
|
+
{
|
|
226
|
+
name = "Hydroxide",
|
|
227
|
+
desc = "Remote spy with web interface",
|
|
228
|
+
-- Hydroxide is modular — needs two imports from the 'revision' branch.
|
|
229
|
+
load = function()
|
|
230
|
+
local owner, branch = "Upbolt", "revision"
|
|
231
|
+
local function webImport(file)
|
|
232
|
+
loadstring(
|
|
233
|
+
game:HttpGetAsync(("https://raw.githubusercontent.com/%s/Hydroxide/%s/%s.lua"):format(owner, branch, file)),
|
|
234
|
+
file .. ".lua"
|
|
235
|
+
)()
|
|
236
|
+
end
|
|
237
|
+
webImport("init")
|
|
238
|
+
webImport("ui/main")
|
|
239
|
+
end,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name = "Infinite Yield",
|
|
243
|
+
desc = "Admin / exploit command panel",
|
|
244
|
+
url = "https://raw.githubusercontent.com/DarkNetworks/Infinite-Yield/main/latest.lua",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name = "Dex Explorer",
|
|
248
|
+
desc = "Browse and inspect the live game tree",
|
|
249
|
+
url = "https://raw.githubusercontent.com/infyiff/backup/main/dex.lua",
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if _toolSect then
|
|
254
|
+
for _, tool in ipairs(_TOOLS) do
|
|
255
|
+
local _launched = false
|
|
256
|
+
local _btn
|
|
257
|
+
pcall(function()
|
|
258
|
+
_btn = _toolSect:Button({
|
|
259
|
+
Title = tool.name,
|
|
260
|
+
Desc = tool.desc,
|
|
261
|
+
Callback = function()
|
|
262
|
+
if _launched then
|
|
263
|
+
_PulseNotify("Already running: " .. tool.name, 2)
|
|
264
|
+
return
|
|
265
|
+
end
|
|
266
|
+
pcall(function() _btn:SetTitle(tool.name .. " · loading…") end)
|
|
267
|
+
task.spawn(function()
|
|
268
|
+
local ok, err = pcall(function()
|
|
269
|
+
if tool.load then
|
|
270
|
+
tool.load()
|
|
271
|
+
else
|
|
272
|
+
local src = game:HttpGet(tool.url)
|
|
273
|
+
local fn, ce = loadstring(src)
|
|
274
|
+
if not fn then error(ce) end
|
|
275
|
+
fn()
|
|
276
|
+
end
|
|
277
|
+
end)
|
|
278
|
+
if ok then
|
|
279
|
+
_launched = true
|
|
280
|
+
pcall(function() _btn:SetTitle("✓ " .. tool.name) end)
|
|
281
|
+
_PulseNotify(tool.name .. " launched", 3)
|
|
282
|
+
Pulse.Log.info("dev-tools", tool.name .. " launched")
|
|
283
|
+
else
|
|
284
|
+
pcall(function() _btn:SetTitle(tool.name .. " · failed") end)
|
|
285
|
+
_PulseNotify(tool.name .. " failed — see LOG", 3)
|
|
286
|
+
Pulse.Log.error("dev-tools", tool.name .. ": " .. tostring(err))
|
|
287
|
+
task.delay(3, function()
|
|
288
|
+
if not _launched then
|
|
289
|
+
pcall(function() _btn:SetTitle(tool.name) end)
|
|
290
|
+
end
|
|
291
|
+
end)
|
|
292
|
+
end
|
|
293
|
+
end)
|
|
294
|
+
end,
|
|
295
|
+
})
|
|
296
|
+
end)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
-- ── Scanner section ───────────────────────────────────────────────────────────
|
|
301
|
+
local _scanSect
|
|
302
|
+
pcall(function()
|
|
303
|
+
_scanSect = _col:Section({
|
|
304
|
+
Title = "Scanner",
|
|
305
|
+
Icon = "scan",
|
|
306
|
+
Box = true,
|
|
307
|
+
BoxBorder = true,
|
|
308
|
+
Opened = true,
|
|
309
|
+
})
|
|
310
|
+
end)
|
|
311
|
+
|
|
312
|
+
if _scanSect then
|
|
313
|
+
pcall(function()
|
|
314
|
+
_scanStatusPara = _scanSect:Paragraph({
|
|
315
|
+
Title = "Game Scanner",
|
|
316
|
+
Desc = "Auto-scan runs on inject. Results copied to clipboard.",
|
|
317
|
+
})
|
|
318
|
+
end)
|
|
319
|
+
pcall(function()
|
|
320
|
+
_scanSect:Button({
|
|
321
|
+
Title = "Scan Now",
|
|
322
|
+
Desc = "Re-scan and copy results to clipboard",
|
|
323
|
+
Callback = function() _doScan() end,
|
|
324
|
+
})
|
|
325
|
+
end)
|
|
326
|
+
pcall(function()
|
|
327
|
+
_scanSect:Button({
|
|
328
|
+
Title = "Copy Last Results",
|
|
329
|
+
Desc = "Re-copy the most recent scan to clipboard",
|
|
330
|
+
Callback = function()
|
|
331
|
+
if _lastScanText == "" then
|
|
332
|
+
_PulseNotify("No scan yet — run Scan Now first", 3)
|
|
333
|
+
return
|
|
334
|
+
end
|
|
335
|
+
local ok = _devCopy(_lastScanText)
|
|
336
|
+
_PulseNotify(ok and "Scan results copied" or "No clipboard function found", 3)
|
|
337
|
+
end,
|
|
338
|
+
})
|
|
339
|
+
end)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
-- ── Scan logic ────────────────────────────────────────────────────────────────
|
|
343
|
+
_doScan = function()
|
|
344
|
+
if _scanRunning then return end
|
|
345
|
+
_scanRunning = true
|
|
346
|
+
pcall(function()
|
|
347
|
+
_scanStatusPara:SetTitle("Game Scanner")
|
|
348
|
+
_scanStatusPara:SetDesc("Scanning…")
|
|
349
|
+
end)
|
|
350
|
+
task.spawn(function()
|
|
351
|
+
local ok, txt, nRem, nScr, nInst, nVal = pcall(_runScan)
|
|
352
|
+
_scanRunning = false
|
|
353
|
+
if ok then
|
|
354
|
+
_lastScanText = txt
|
|
355
|
+
local summary = ("%d remotes · %d scripts · %d instances · %d values"):format(
|
|
356
|
+
nRem, nScr, nInst, nVal)
|
|
357
|
+
local copied = _devCopy(txt)
|
|
358
|
+
pcall(function()
|
|
359
|
+
_scanStatusPara:SetTitle("Scan " .. os.date("%H:%M:%S"))
|
|
360
|
+
_scanStatusPara:SetDesc(summary .. (copied and " · copied to clipboard" or ""))
|
|
361
|
+
end)
|
|
362
|
+
Pulse.Log.info("dev-scanner", summary)
|
|
363
|
+
if copied then
|
|
364
|
+
_PulseNotify("Game scan complete · results copied", 4)
|
|
365
|
+
end
|
|
366
|
+
else
|
|
367
|
+
pcall(function()
|
|
368
|
+
_scanStatusPara:SetTitle("Scan failed")
|
|
369
|
+
_scanStatusPara:SetDesc("Check LOG tab › dev-scanner tag for details.")
|
|
370
|
+
end)
|
|
371
|
+
Pulse.Log.error("dev-scanner", tostring(txt))
|
|
372
|
+
end
|
|
373
|
+
end)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
-- ── Auto-scan on inject ────────────────────────────────────────────────────────
|
|
377
|
+
-- Fires 2 s after game is loaded so services are fully populated.
|
|
378
|
+
task.spawn(function()
|
|
379
|
+
if not game:IsLoaded() then game.Loaded:Wait() end
|
|
380
|
+
task.wait(2)
|
|
381
|
+
_doScan()
|
|
382
|
+
end)
|
|
383
|
+
|
|
384
|
+
Pulse.Log.debug("dev-panel", "WindUI Dev tab ready")
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
-- ── Pulse.Aim ────────────────────────────────────────────────────────────────
|
|
2
|
+
-- Camera targeting helpers for aimbot-style features.
|
|
3
|
+
-- Usage:
|
|
4
|
+
-- if Pulse.Aim.inFOV(nape.Position, radius()) then ... end
|
|
5
|
+
-- local dist = Pulse.Aim.screenDist(nape.Position)
|
|
6
|
+
-- Pulse.Aim.lookAt(nape.Position) -- snap
|
|
7
|
+
-- Pulse.Aim.smoothTo(nape.Position, 0.95) -- smooth lerp
|
|
8
|
+
-- local nearest = Pulse.Aim.findNearest(titans, { fovRadius = 300 })
|
|
9
|
+
--
|
|
10
|
+
-- Stateful locker (for aimbot components):
|
|
11
|
+
-- local lock = Pulse.Aim.locker({
|
|
12
|
+
-- getPos = function(e) return e.Nape.Position end,
|
|
13
|
+
-- validate = function(e) return e.Parent ~= nil end,
|
|
14
|
+
-- })
|
|
15
|
+
-- lock:lock(entity) -- set target
|
|
16
|
+
-- lock:release() -- clear
|
|
17
|
+
-- lock:getTarget() -- current entity or nil
|
|
18
|
+
-- lock:isValid() -- validates current target via opts.validate
|
|
19
|
+
-- lock:aim() -- smooth camera move; returns false if target became invalid
|
|
20
|
+
-- lock:aim("snap") -- snap mode (uses Pulse.Aim.lookAt)
|
|
21
|
+
|
|
22
|
+
Pulse.Aim = (function()
|
|
23
|
+
local A = {}
|
|
24
|
+
|
|
25
|
+
-- True if worldPos projects within pixelRadius pixels of screen centre.
|
|
26
|
+
function A.inFOV(worldPos, pixelRadius)
|
|
27
|
+
local cam = workspace.CurrentCamera
|
|
28
|
+
if not cam then return false end
|
|
29
|
+
local sp, onScreen = cam:WorldToViewportPoint(worldPos)
|
|
30
|
+
if not onScreen then return false end
|
|
31
|
+
local sz = cam.ViewportSize
|
|
32
|
+
local dx = sp.X - sz.X * 0.5
|
|
33
|
+
local dy = sp.Y - sz.Y * 0.5
|
|
34
|
+
return (dx * dx + dy * dy) <= (pixelRadius * pixelRadius)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
-- Pixel distance from screen centre to worldPos (math.huge if off-screen).
|
|
38
|
+
function A.screenDist(worldPos)
|
|
39
|
+
local cam = workspace.CurrentCamera
|
|
40
|
+
if not cam then return math.huge end
|
|
41
|
+
local sp, onScreen = cam:WorldToViewportPoint(worldPos)
|
|
42
|
+
if not onScreen then return math.huge end
|
|
43
|
+
local sz = cam.ViewportSize
|
|
44
|
+
local dx = sp.X - sz.X * 0.5
|
|
45
|
+
local dy = sp.Y - sz.Y * 0.5
|
|
46
|
+
return math.sqrt(dx * dx + dy * dy)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
-- Lock the camera to look at a world position.
|
|
50
|
+
-- Syncs the VirtualInputManager cursor so the game's camera script agrees,
|
|
51
|
+
-- then triple-sets CFrame to override any interpolation.
|
|
52
|
+
function A.lookAt(pos)
|
|
53
|
+
local cam = workspace.CurrentCamera
|
|
54
|
+
if not cam then return end
|
|
55
|
+
local sp, onScreen = cam:WorldToViewportPoint(pos)
|
|
56
|
+
if onScreen then
|
|
57
|
+
pcall(function() _VIM:SendMouseMoveEvent(sp.X, sp.Y, game) end)
|
|
58
|
+
end
|
|
59
|
+
local cf = CFrame.lookAt(cam.CFrame.Position, pos)
|
|
60
|
+
cam.CFrame = cf
|
|
61
|
+
pcall(function() cam.CoordinateFrame = cf end)
|
|
62
|
+
cam.CFrame = cf
|
|
63
|
+
if onScreen then
|
|
64
|
+
pcall(function() _VIM:SendMouseMoveEvent(sp.X, sp.Y, game) end)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
-- Return the nearest entity from candidates.
|
|
69
|
+
-- opts:
|
|
70
|
+
-- origin — Vector3 (defaults to local HRP position)
|
|
71
|
+
-- fovRadius — pixel radius; skips entities outside FOV (optional)
|
|
72
|
+
-- maxDist — world-space distance cap (optional)
|
|
73
|
+
-- filter — function(entity) → bool; return false to skip
|
|
74
|
+
-- getRoot — function(entity) → BasePart (defaults to HumanoidRootPart)
|
|
75
|
+
function A.findNearest(candidates, opts)
|
|
76
|
+
opts = opts or {}
|
|
77
|
+
local hrp = _PulseGetHRP()
|
|
78
|
+
local origin = opts.origin or (hrp and hrp.Position)
|
|
79
|
+
if not origin then return nil end
|
|
80
|
+
|
|
81
|
+
local getRoot = opts.getRoot or function(e)
|
|
82
|
+
return e:FindFirstChild("HumanoidRootPart")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
local nearest = nil
|
|
86
|
+
local bestDist = opts.maxDist or math.huge
|
|
87
|
+
local _sk = { filter = 0, root = 0, fov = 0, dist = 0 }
|
|
88
|
+
|
|
89
|
+
for _, entity in ipairs(candidates) do
|
|
90
|
+
if opts.filter and not opts.filter(entity) then
|
|
91
|
+
_sk.filter = _sk.filter + 1
|
|
92
|
+
else
|
|
93
|
+
local root = getRoot(entity)
|
|
94
|
+
if not root then
|
|
95
|
+
_sk.root = _sk.root + 1
|
|
96
|
+
elseif opts.fovRadius and not A.inFOV(root.Position, opts.fovRadius) then
|
|
97
|
+
_sk.fov = _sk.fov + 1
|
|
98
|
+
else
|
|
99
|
+
local d = (root.Position - origin).Magnitude
|
|
100
|
+
if d < bestDist then
|
|
101
|
+
bestDist = d
|
|
102
|
+
nearest = entity
|
|
103
|
+
else
|
|
104
|
+
_sk.dist = _sk.dist + 1
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if nearest == nil and #candidates > 0 then
|
|
111
|
+
Pulse.Log.trace("Aim", "findNearest: no result", {
|
|
112
|
+
n = #candidates,
|
|
113
|
+
filter = _sk.filter,
|
|
114
|
+
noRoot = _sk.root,
|
|
115
|
+
fov = _sk.fov,
|
|
116
|
+
dist = _sk.dist,
|
|
117
|
+
})
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
return nearest
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
-- Smooth-lerp the camera toward a world position (no VIM sync, no triple-set).
|
|
124
|
+
-- alpha = lerp factor per frame; 0.95 feels like a snappy but smooth lock.
|
|
125
|
+
function A.smoothTo(pos, alpha)
|
|
126
|
+
local cam = workspace.CurrentCamera
|
|
127
|
+
if not cam then return end
|
|
128
|
+
alpha = alpha or 0.95
|
|
129
|
+
local origin = cam.CFrame.Position
|
|
130
|
+
local currentLook = cam.CFrame.LookVector
|
|
131
|
+
local desiredLook = (pos - origin).Unit
|
|
132
|
+
local smoothed = currentLook:Lerp(desiredLook, alpha)
|
|
133
|
+
cam.CFrame = CFrame.new(origin, origin + smoothed)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
-- Create a stateful locker for a single locked target.
|
|
137
|
+
-- opts:
|
|
138
|
+
-- getPos(entity) → Vector3 — where to aim at on the entity (required for aim())
|
|
139
|
+
-- validate(entity) → bool — is this entity still a valid target?
|
|
140
|
+
function A.locker(opts)
|
|
141
|
+
opts = opts or {}
|
|
142
|
+
local _target = nil
|
|
143
|
+
local L = {}
|
|
144
|
+
|
|
145
|
+
function L:lock(entity)
|
|
146
|
+
_target = entity
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
function L:release()
|
|
150
|
+
_target = nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
function L:getTarget()
|
|
154
|
+
return _target
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
-- Validate the current lock. Returns false (and clears) if invalid.
|
|
158
|
+
function L:isValid()
|
|
159
|
+
if not _target then return false end
|
|
160
|
+
if opts.validate then
|
|
161
|
+
if not opts.validate(_target) then _target = nil; return false end
|
|
162
|
+
return true
|
|
163
|
+
end
|
|
164
|
+
if not _target.Parent then _target = nil; return false end
|
|
165
|
+
return true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
-- Move camera toward locked target.
|
|
169
|
+
-- mode = "smooth" (default) | "snap"
|
|
170
|
+
-- Returns false if the target was invalid (lock cleared).
|
|
171
|
+
function L:aim(mode, alpha)
|
|
172
|
+
if not self:isValid() then return false end
|
|
173
|
+
local pos
|
|
174
|
+
if opts.getPos then
|
|
175
|
+
pos = opts.getPos(_target)
|
|
176
|
+
else
|
|
177
|
+
local root = _target:FindFirstChild("HumanoidRootPart")
|
|
178
|
+
pos = root and root.Position
|
|
179
|
+
end
|
|
180
|
+
if not pos then _target = nil; return false end
|
|
181
|
+
if mode == "snap" then
|
|
182
|
+
A.lookAt(pos)
|
|
183
|
+
else
|
|
184
|
+
A.smoothTo(pos, alpha or 0.95)
|
|
185
|
+
end
|
|
186
|
+
return true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
return L
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
return A
|
|
193
|
+
end)()
|