pulse-rb 1.4.2 → 1.4.4
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/adapters/windui.lua +267 -58
- package/dist/index.js +71 -59
- package/package.json +1 -1
- package/pulse/runtime.lua +5 -3
- package/templates/component_aimbot.ts +18 -0
- package/templates/layout.ts +35 -2
- package/templates/page_combat.ts +9 -0
package/adapters/windui.lua
CHANGED
|
@@ -25,6 +25,14 @@ local _BundleComponents = Components or {}
|
|
|
25
25
|
local _BundlePages = _Pages or {}
|
|
26
26
|
local _BundlePulse = Pulse or {}
|
|
27
27
|
|
|
28
|
+
-- Auto-enable dev logging when layout.dev = true.
|
|
29
|
+
-- Runs synchronously (before any task.spawn) so all UI setup logs are captured.
|
|
30
|
+
if _PULSE_LAYOUT.dev and _BundlePulse and _BundlePulse.Log then
|
|
31
|
+
_BundlePulse.Log.enable()
|
|
32
|
+
_BundlePulse.Log.configure({ level = "debug", console = true })
|
|
33
|
+
_BundlePulse.Log.info("dev", "dev build active")
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
local _WINDUI_URL = "https://github.com/Footagesus/WindUI/releases/download/1.6.64-fix/main.lua"
|
|
29
37
|
local _PULSE_LOGO = "https://pulse-rb.vercel.app/img/logo.svg"
|
|
30
38
|
|
|
@@ -51,6 +59,7 @@ local _toggleKeyName = "RightControl"
|
|
|
51
59
|
local _windowVisible = true
|
|
52
60
|
|
|
53
61
|
local _isMobile = _UIS.TouchEnabled and not _UIS.KeyboardEnabled
|
|
62
|
+
local _PULSE_PREMIUM = false -- set true during key validation if key grants premium
|
|
54
63
|
|
|
55
64
|
-- ── Adapter ───────────────────────────────────────────────────────────────────
|
|
56
65
|
|
|
@@ -132,7 +141,7 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
132
141
|
local openBtnMobileOnly = opts.open_btn_mobile_only ~= false
|
|
133
142
|
local openBtnIcon = (opts.open_btn_icon and opts.open_btn_icon ~= "") and opts.open_btn_icon or nil
|
|
134
143
|
|
|
135
|
-
|
|
144
|
+
local _winCfg = {
|
|
136
145
|
Title = title,
|
|
137
146
|
Icon = icon,
|
|
138
147
|
Author = author,
|
|
@@ -143,7 +152,6 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
143
152
|
Transparent = acrylic or (transparency > 0),
|
|
144
153
|
BackgroundImageTransparency = acrylic and 0 or transparency,
|
|
145
154
|
Acrylic = acrylic,
|
|
146
|
-
|
|
147
155
|
OpenButton = {
|
|
148
156
|
Enabled = true,
|
|
149
157
|
Draggable = true,
|
|
@@ -153,7 +161,62 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
153
161
|
CornerRadius = UDim.new(1, 0),
|
|
154
162
|
StrokeThickness = 3,
|
|
155
163
|
},
|
|
156
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
-- Key system — configured via layout.keySystem
|
|
167
|
+
local _ks = _PULSE_LAYOUT.keySystem
|
|
168
|
+
if _ks then
|
|
169
|
+
local function _checkPremium(key)
|
|
170
|
+
local _pr = _PULSE_LAYOUT.premium
|
|
171
|
+
if not _pr then return end
|
|
172
|
+
if _pr.keys then
|
|
173
|
+
for _, k in ipairs(_pr.keys) do
|
|
174
|
+
if k == key then _PULSE_PREMIUM = true; return end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
if _pr.validatorUrl and _pr.validatorUrl ~= "" then
|
|
178
|
+
local ok, resp = pcall(function() return game:HttpGet(_pr.validatorUrl .. key) end)
|
|
179
|
+
if ok and resp and resp:lower():match("^true") then _PULSE_PREMIUM = true end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
local _ksCfg = {
|
|
183
|
+
Title = _ks.title or "Key Required",
|
|
184
|
+
Note = (_ks.note and _ks.note ~= "") and _ks.note or nil,
|
|
185
|
+
SaveKey = _ks.saveKey == true,
|
|
186
|
+
URL = (_ks.getKeyUrl and _ks.getKeyUrl ~= "") and _ks.getKeyUrl or nil,
|
|
187
|
+
KeyValidator = function(key)
|
|
188
|
+
local passed = false
|
|
189
|
+
-- Check against static keys list
|
|
190
|
+
if _ks.keys then
|
|
191
|
+
for _, k in ipairs(_ks.keys) do
|
|
192
|
+
if k == key then passed = true; break end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
-- HTTP validation (server-side) when no static list or key not found
|
|
196
|
+
if not passed and _ks.validatorUrl and _ks.validatorUrl ~= "" then
|
|
197
|
+
local ok, resp = pcall(function() return game:HttpGet(_ks.validatorUrl .. key) end)
|
|
198
|
+
if ok and resp and resp:lower():match("^true") then passed = true end
|
|
199
|
+
end
|
|
200
|
+
-- No validation configured → any non-empty key passes (dev mode)
|
|
201
|
+
if not passed and (not _ks.keys or #_ks.keys == 0)
|
|
202
|
+
and (not _ks.validatorUrl or _ks.validatorUrl == "") then
|
|
203
|
+
passed = key ~= nil and key ~= ""
|
|
204
|
+
end
|
|
205
|
+
_checkPremium(key)
|
|
206
|
+
return passed or _PULSE_PREMIUM -- premium key also grants basic access
|
|
207
|
+
end,
|
|
208
|
+
}
|
|
209
|
+
if _ks.thumbnail then
|
|
210
|
+
_ksCfg.Thumbnail = {
|
|
211
|
+
Image = _ks.thumbnail.image or "",
|
|
212
|
+
Title = _ks.thumbnail.title or "",
|
|
213
|
+
Width = _ks.thumbnail.width or 100,
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
_winCfg.KeySystem = _ksCfg
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
_windWindow = WindUI:CreateWindow(_winCfg)
|
|
157
220
|
|
|
158
221
|
pcall(function()
|
|
159
222
|
_windConfig = _windWindow.ConfigManager:Config(_folder)
|
|
@@ -544,6 +607,28 @@ task.spawn(function()
|
|
|
544
607
|
|
|
545
608
|
local L = _PULSE_LAYOUT
|
|
546
609
|
|
|
610
|
+
-- Premium dynamic unlock system
|
|
611
|
+
-- Each entry is a function that destroys the locked UI and mounts the real component.
|
|
612
|
+
local _premiumPendingUnlocks = {}
|
|
613
|
+
local _premiumStatusPara = nil
|
|
614
|
+
|
|
615
|
+
local function _activatePremium()
|
|
616
|
+
_PULSE_PREMIUM = true
|
|
617
|
+
-- Update settings status paragraph if it exists
|
|
618
|
+
pcall(function()
|
|
619
|
+
if _premiumStatusPara then
|
|
620
|
+
_premiumStatusPara:SetTitle("Premium Active")
|
|
621
|
+
_premiumStatusPara:SetDesc("All premium features are now unlocked.")
|
|
622
|
+
end
|
|
623
|
+
end)
|
|
624
|
+
-- Swap locked UI → real components for every registered premium groupbox
|
|
625
|
+
for _, fn in ipairs(_premiumPendingUnlocks) do
|
|
626
|
+
task.spawn(function() pcall(fn) end)
|
|
627
|
+
end
|
|
628
|
+
_premiumPendingUnlocks = {}
|
|
629
|
+
pcall(function() _PulseNotify("Premium unlocked!", 3) end)
|
|
630
|
+
end
|
|
631
|
+
|
|
547
632
|
-- Resolve toggle key
|
|
548
633
|
local _tkName = (L.toggleKey and L.toggleKey ~= "") and L.toggleKey or "RightControl"
|
|
549
634
|
local _tk = pcall(function() return Enum.KeyCode[_tkName] end) and Enum.KeyCode[_tkName]
|
|
@@ -594,13 +679,12 @@ task.spawn(function()
|
|
|
594
679
|
local _execName = "Unknown"
|
|
595
680
|
local _execVer = ""
|
|
596
681
|
pcall(function()
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if ok and n then _execName = tostring(n) end
|
|
682
|
+
-- pcall each call directly — avoids rawget missing __index-proxied executor globals
|
|
683
|
+
local ok, n, v = pcall(function() return identifyexecutor() end)
|
|
684
|
+
if ok and n then _execName = tostring(n); _execVer = tostring(v or "") end
|
|
685
|
+
if _execName == "Unknown" then
|
|
686
|
+
local ok2, n2 = pcall(function() return getexecutorname() end)
|
|
687
|
+
if ok2 and n2 then _execName = tostring(n2) end
|
|
604
688
|
end
|
|
605
689
|
if _execName == "Unknown" then
|
|
606
690
|
if rawget(_G, "syn") or rawget(_G, "Synapse") then _execName = "Synapse X"
|
|
@@ -771,7 +855,107 @@ task.spawn(function()
|
|
|
771
855
|
end
|
|
772
856
|
if gb.mount then
|
|
773
857
|
local comp = _BundleComponents[gb.mount]
|
|
774
|
-
|
|
858
|
+
-- Premium gate: show inline key-entry UI; dynamically replaced on unlock
|
|
859
|
+
if gb.premium and not _PULSE_PREMIUM then
|
|
860
|
+
local _mountComp = comp
|
|
861
|
+
local _mountCont = container
|
|
862
|
+
local _destroyFns = {}
|
|
863
|
+
|
|
864
|
+
pcall(function()
|
|
865
|
+
local _keyValue = ""
|
|
866
|
+
local _statusPara
|
|
867
|
+
|
|
868
|
+
local function _setStatus(msg)
|
|
869
|
+
pcall(function() if _statusPara then _statusPara:SetDesc(msg) end end)
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
-- Header
|
|
873
|
+
local _header = _mountCont._container:Paragraph({
|
|
874
|
+
Title = "Premium Feature",
|
|
875
|
+
Desc = "Enter your key below to unlock this feature.",
|
|
876
|
+
})
|
|
877
|
+
table.insert(_destroyFns, function() pcall(function() _header:Destroy() end) end)
|
|
878
|
+
|
|
879
|
+
-- Key input (wrapped in pcall — Input may not exist on all Section types)
|
|
880
|
+
local _inputElem
|
|
881
|
+
pcall(function()
|
|
882
|
+
_inputElem = _mountCont._container:Input({
|
|
883
|
+
Title = "Premium Key",
|
|
884
|
+
Placeholder = "Paste your key here…",
|
|
885
|
+
Flag = (gb.mount or "prem") .. "_premKey",
|
|
886
|
+
Callback = function(v) _keyValue = v end,
|
|
887
|
+
})
|
|
888
|
+
table.insert(_destroyFns, function() pcall(function() _inputElem:Destroy() end) end)
|
|
889
|
+
end)
|
|
890
|
+
|
|
891
|
+
-- Validate + unlock
|
|
892
|
+
local function _doCheck()
|
|
893
|
+
local key = (_keyValue or ""):match("^%s*(.-)%s*$")
|
|
894
|
+
if key == "" then _setStatus("Enter a key first."); return end
|
|
895
|
+
local valid = false
|
|
896
|
+
local _pr = _PULSE_LAYOUT.premium
|
|
897
|
+
if _pr then
|
|
898
|
+
if _pr.keys then
|
|
899
|
+
for _, k in ipairs(_pr.keys) do
|
|
900
|
+
if k == key then valid = true; break end
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
if not valid and _pr.validatorUrl and _pr.validatorUrl ~= "" then
|
|
904
|
+
_setStatus("Checking…")
|
|
905
|
+
local ok, resp = pcall(function()
|
|
906
|
+
return game:HttpGet(_pr.validatorUrl .. key)
|
|
907
|
+
end)
|
|
908
|
+
if ok and resp and resp:lower():match("^true") then valid = true end
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
if valid then
|
|
912
|
+
_setStatus("Valid! Unlocking…")
|
|
913
|
+
task.delay(0.4, _activatePremium)
|
|
914
|
+
else
|
|
915
|
+
_setStatus("Invalid key.")
|
|
916
|
+
end
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
-- "Copy Link" button
|
|
920
|
+
local _pKeyUrl = (_PULSE_LAYOUT.premium and _PULSE_LAYOUT.premium.getKeyUrl)
|
|
921
|
+
or (_PULSE_LAYOUT.keySystem and _PULSE_LAYOUT.keySystem.getKeyUrl) or ""
|
|
922
|
+
local _copyBtn = _mountCont._container:Button({
|
|
923
|
+
Title = "Copy Link",
|
|
924
|
+
Desc = "Get your premium key",
|
|
925
|
+
Callback = function()
|
|
926
|
+
if _pKeyUrl == "" then _setStatus("No link configured."); return end
|
|
927
|
+
local ok = false
|
|
928
|
+
pcall(function() setclipboard(_pKeyUrl); ok = true end)
|
|
929
|
+
pcall(function() if not ok then toclipboard(_pKeyUrl); ok = true end end)
|
|
930
|
+
_setStatus(ok and "Link copied!" or "No clipboard API.")
|
|
931
|
+
end,
|
|
932
|
+
})
|
|
933
|
+
table.insert(_destroyFns, function() pcall(function() _copyBtn:Destroy() end) end)
|
|
934
|
+
|
|
935
|
+
-- "Check Key" button
|
|
936
|
+
local _checkBtn = _mountCont._container:Button({
|
|
937
|
+
Title = "Check Key",
|
|
938
|
+
Callback = _doCheck,
|
|
939
|
+
})
|
|
940
|
+
table.insert(_destroyFns, function() pcall(function() _checkBtn:Destroy() end) end)
|
|
941
|
+
|
|
942
|
+
-- Status line
|
|
943
|
+
_statusPara = _mountCont._container:Paragraph({
|
|
944
|
+
Title = "Status",
|
|
945
|
+
Desc = "—",
|
|
946
|
+
})
|
|
947
|
+
table.insert(_destroyFns, function() pcall(function() _statusPara:Destroy() end) end)
|
|
948
|
+
end)
|
|
949
|
+
|
|
950
|
+
-- Register: when premium activates, destroy locked UI and mount real component
|
|
951
|
+
table.insert(_premiumPendingUnlocks, function()
|
|
952
|
+
for _, fn in ipairs(_destroyFns) do fn() end
|
|
953
|
+
task.wait(0.05)
|
|
954
|
+
if _mountComp then
|
|
955
|
+
pcall(function() _UIAdapter:mount(_mountComp, _mountCont) end)
|
|
956
|
+
end
|
|
957
|
+
end)
|
|
958
|
+
elseif comp then
|
|
775
959
|
local ok, err = pcall(function() _UIAdapter:mount(comp, container) end)
|
|
776
960
|
if not ok then
|
|
777
961
|
warn("[Pulse] mount '" .. gb.mount .. "' error: " .. tostring(err))
|
|
@@ -845,6 +1029,43 @@ task.spawn(function()
|
|
|
845
1029
|
end,
|
|
846
1030
|
})
|
|
847
1031
|
|
|
1032
|
+
-- Premium status groupbox — shown only when premium tier is configured
|
|
1033
|
+
if _PULSE_LAYOUT.premium then
|
|
1034
|
+
local gb_prem = _settingsTab:AddRightGroupbox("Premium")
|
|
1035
|
+
if _PULSE_PREMIUM then
|
|
1036
|
+
pcall(function()
|
|
1037
|
+
gb_prem._container:Paragraph({
|
|
1038
|
+
Title = "Premium Active",
|
|
1039
|
+
Desc = "All premium features are unlocked.",
|
|
1040
|
+
})
|
|
1041
|
+
end)
|
|
1042
|
+
else
|
|
1043
|
+
pcall(function()
|
|
1044
|
+
-- Store reference so _activatePremium can update it live
|
|
1045
|
+
_premiumStatusPara = gb_prem._container:Paragraph({
|
|
1046
|
+
Title = "Premium Locked",
|
|
1047
|
+
Desc = "Enter a key in any locked feature to unlock.",
|
|
1048
|
+
})
|
|
1049
|
+
end)
|
|
1050
|
+
local _pKeyUrl = (_PULSE_LAYOUT.premium and _PULSE_LAYOUT.premium.getKeyUrl)
|
|
1051
|
+
or (_PULSE_LAYOUT.keySystem and _PULSE_LAYOUT.keySystem.getKeyUrl)
|
|
1052
|
+
if _pKeyUrl and _pKeyUrl ~= "" then
|
|
1053
|
+
pcall(function()
|
|
1054
|
+
gb_prem._container:Button({
|
|
1055
|
+
Title = "Get Premium Key",
|
|
1056
|
+
Desc = "Copy the link to your key page",
|
|
1057
|
+
Callback = function()
|
|
1058
|
+
local ok = false
|
|
1059
|
+
pcall(function() setclipboard(_pKeyUrl); ok = true end)
|
|
1060
|
+
pcall(function() if not ok then toclipboard(_pKeyUrl); ok = true end end)
|
|
1061
|
+
_PulseNotify(ok and "Link copied!" or _pKeyUrl, 4)
|
|
1062
|
+
end,
|
|
1063
|
+
})
|
|
1064
|
+
end)
|
|
1065
|
+
end
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
|
|
848
1069
|
-- Menu settings groupbox
|
|
849
1070
|
local gb_menu = _settingsTab:AddRightGroupbox("Menu Settings")
|
|
850
1071
|
_UIAdapter:addButton(gb_menu, {
|
|
@@ -1056,12 +1277,6 @@ task.spawn(function()
|
|
|
1056
1277
|
})
|
|
1057
1278
|
end)
|
|
1058
1279
|
|
|
1059
|
-
-- Auto-scan on inject
|
|
1060
|
-
task.spawn(function()
|
|
1061
|
-
if not game:IsLoaded() then game.Loaded:Wait() end
|
|
1062
|
-
task.wait(2)
|
|
1063
|
-
_doScan()
|
|
1064
|
-
end)
|
|
1065
1280
|
end
|
|
1066
1281
|
|
|
1067
1282
|
-- Info section — framework version + signal counts for debugging
|
|
@@ -1091,47 +1306,6 @@ task.spawn(function()
|
|
|
1091
1306
|
end
|
|
1092
1307
|
end
|
|
1093
1308
|
|
|
1094
|
-
-- Heartbeat Monitor section — tick counts per component (left)
|
|
1095
|
-
-- Uses Pulse.Monitor which is independent of the log system.
|
|
1096
|
-
local _monSect
|
|
1097
|
-
pcall(function()
|
|
1098
|
-
_monSect = _devLeft:Section({ Title="Heartbeat Monitor", Icon="activity", Box=true, BoxBorder=true, Opened=true })
|
|
1099
|
-
end)
|
|
1100
|
-
if _monSect then
|
|
1101
|
-
local _monPara
|
|
1102
|
-
pcall(function()
|
|
1103
|
-
_monPara = _monSect:Paragraph({
|
|
1104
|
-
Title = "raw / cond (updates 2s)",
|
|
1105
|
-
Desc = "raw = every HB fire · cond = when() passed",
|
|
1106
|
-
})
|
|
1107
|
-
end)
|
|
1108
|
-
-- Reads _rawHb/_condHb plain-Lua counters set in runtime._hbBind.
|
|
1109
|
-
-- No dependency on Pulse.Monitor — works regardless of log system state.
|
|
1110
|
-
task.spawn(function()
|
|
1111
|
-
while _windWindow do
|
|
1112
|
-
task.wait(2)
|
|
1113
|
-
pcall(function()
|
|
1114
|
-
local lines = {}
|
|
1115
|
-
for cname, comp in pairs(_BundleComponents) do
|
|
1116
|
-
if type(comp) == "table" and comp._rawHb ~= nil then
|
|
1117
|
-
lines[#lines+1] = cname
|
|
1118
|
-
.. " raw=" .. tostring(comp._rawHb)
|
|
1119
|
-
.. " cond=" .. tostring(comp._condHb)
|
|
1120
|
-
end
|
|
1121
|
-
end
|
|
1122
|
-
if _monPara then
|
|
1123
|
-
if #lines == 0 then
|
|
1124
|
-
_monPara:SetDesc("No heartbeat components registered")
|
|
1125
|
-
else
|
|
1126
|
-
table.sort(lines)
|
|
1127
|
-
_monPara:SetDesc(table.concat(lines, "\n"))
|
|
1128
|
-
end
|
|
1129
|
-
end
|
|
1130
|
-
end)
|
|
1131
|
-
end
|
|
1132
|
-
end)
|
|
1133
|
-
end
|
|
1134
|
-
|
|
1135
1309
|
-- Console section — controls for the Roblox developer console (F9).
|
|
1136
1310
|
-- "Clear" wipes the output so only your next prints are visible.
|
|
1137
1311
|
-- "Print Logs" re-dumps all buffered Pulse.Log entries so you can read
|
|
@@ -1177,6 +1351,17 @@ task.spawn(function()
|
|
|
1177
1351
|
print("─────────────────────────────────────────")
|
|
1178
1352
|
end
|
|
1179
1353
|
|
|
1354
|
+
pcall(function()
|
|
1355
|
+
_consoleSect:Button({
|
|
1356
|
+
Title = "Open Console",
|
|
1357
|
+
Desc = "Show the Roblox developer console (F9)",
|
|
1358
|
+
Callback = function()
|
|
1359
|
+
pcall(function()
|
|
1360
|
+
game:GetService("StarterGui"):SetCore("DevConsoleVisible", true)
|
|
1361
|
+
end)
|
|
1362
|
+
end,
|
|
1363
|
+
})
|
|
1364
|
+
end)
|
|
1180
1365
|
pcall(function()
|
|
1181
1366
|
_consoleSect:Button({
|
|
1182
1367
|
Title = "Clear Console",
|
|
@@ -1212,6 +1397,30 @@ task.spawn(function()
|
|
|
1212
1397
|
end
|
|
1213
1398
|
end
|
|
1214
1399
|
|
|
1400
|
+
-- Apply defaults and notify ready — runs after all widgets are created so
|
|
1401
|
+
-- Toggles/Options are populated and SetValue calls land correctly.
|
|
1402
|
+
task.spawn(function()
|
|
1403
|
+
task.wait(0.5)
|
|
1404
|
+
for _, d in ipairs(_PULSE_DEFAULTS) do
|
|
1405
|
+
if d.set then pcall(d.set, d.value) end
|
|
1406
|
+
if d.type == "toggle" then
|
|
1407
|
+
pcall(function()
|
|
1408
|
+
local t = Toggles and Toggles[d.id]
|
|
1409
|
+
if t then t:SetValue(d.value) end
|
|
1410
|
+
end)
|
|
1411
|
+
else
|
|
1412
|
+
pcall(function()
|
|
1413
|
+
local t = Options and Options[d.id]
|
|
1414
|
+
if t then t:SetValue(d.value) end
|
|
1415
|
+
end)
|
|
1416
|
+
end
|
|
1417
|
+
task.wait(0.05)
|
|
1418
|
+
end
|
|
1419
|
+
if #_PULSE_DEFAULTS > 0 and _PulseNotify then
|
|
1420
|
+
_PulseNotify((L.title or "Hub") .. " loaded!", 5)
|
|
1421
|
+
end
|
|
1422
|
+
end)
|
|
1423
|
+
|
|
1215
1424
|
-- Re-select first tab (Home) — Settings is last so Wind UI leaves it active
|
|
1216
1425
|
task.defer(function()
|
|
1217
1426
|
if _UIAdapter._firstTab then
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
|
|
|
56
56
|
var init_constants = __esm({
|
|
57
57
|
"src/constants.ts"() {
|
|
58
58
|
init_cjs_shims();
|
|
59
|
-
RB_VERSION = "1.4.
|
|
59
|
+
RB_VERSION = "1.4.4";
|
|
60
60
|
RB_HOME = path.join(os.homedir(), ".rb");
|
|
61
61
|
path.join(RB_HOME, "bin");
|
|
62
62
|
IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
|
|
@@ -1110,7 +1110,7 @@ function rglob(dir, exts) {
|
|
|
1110
1110
|
walk(dir);
|
|
1111
1111
|
return results;
|
|
1112
1112
|
}
|
|
1113
|
-
var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION,
|
|
1113
|
+
var PULSE_DIR, PULSE_DEV_DIR, PULSE_UI_DIR, REEXEC_GUARD, DESTROY_REGISTRATION, Compiler;
|
|
1114
1114
|
var init_compiler = __esm({
|
|
1115
1115
|
"src/compiler.ts"() {
|
|
1116
1116
|
init_cjs_shims();
|
|
@@ -1130,43 +1130,6 @@ if _G.__AOT_R_DESTROY then pcall(_G.__AOT_R_DESTROY) end
|
|
|
1130
1130
|
`;
|
|
1131
1131
|
DESTROY_REGISTRATION = `-- Register this instance so the next re-execution can clean it up.
|
|
1132
1132
|
_G.__AOT_R_DESTROY = _PulseDestroy
|
|
1133
|
-
`;
|
|
1134
|
-
DEFAULTS_RUNNER = `task.spawn(function()
|
|
1135
|
-
task.wait(0.5)
|
|
1136
|
-
for _, d in ipairs(_PULSE_DEFAULTS) do
|
|
1137
|
-
local effectiveValue = d.value
|
|
1138
|
-
if d.type == "toggle" and Pulse.TestMode and Pulse.TestMode.isActive() then
|
|
1139
|
-
if not Pulse.TestMode.isTarget(d.id) then
|
|
1140
|
-
effectiveValue = false
|
|
1141
|
-
end
|
|
1142
|
-
end
|
|
1143
|
-
if d.set then pcall(d.set, effectiveValue) end
|
|
1144
|
-
if d.type == "toggle" then
|
|
1145
|
-
pcall(function()
|
|
1146
|
-
local t = Toggles and Toggles[d.id]
|
|
1147
|
-
if t then t:SetValue(effectiveValue) end
|
|
1148
|
-
end)
|
|
1149
|
-
else
|
|
1150
|
-
pcall(function()
|
|
1151
|
-
local t = Options and Options[d.id]
|
|
1152
|
-
if t then t:SetValue(d.value) end
|
|
1153
|
-
end)
|
|
1154
|
-
end
|
|
1155
|
-
task.wait(0.05)
|
|
1156
|
-
end
|
|
1157
|
-
if #_PULSE_DEFAULTS > 0 then
|
|
1158
|
-
local _notifyMsg = "AOT:Resistance Hub ready!"
|
|
1159
|
-
if Pulse.TestMode and Pulse.TestMode.isActive() then
|
|
1160
|
-
local _tt = Pulse.TestMode.getTargets()
|
|
1161
|
-
if #_tt > 0 then
|
|
1162
|
-
_notifyMsg = "Test mode: " .. table.concat(_tt, ", ")
|
|
1163
|
-
end
|
|
1164
|
-
end
|
|
1165
|
-
if _PulseNotify then
|
|
1166
|
-
_PulseNotify(_notifyMsg, 5)
|
|
1167
|
-
end
|
|
1168
|
-
end
|
|
1169
|
-
end)
|
|
1170
1133
|
`;
|
|
1171
1134
|
Compiler = class {
|
|
1172
1135
|
root;
|
|
@@ -1193,6 +1156,52 @@ end)
|
|
|
1193
1156
|
}
|
|
1194
1157
|
return "windui";
|
|
1195
1158
|
}
|
|
1159
|
+
// Extract content between matching braces for a named TS field.
|
|
1160
|
+
// Returns null if field is absent or commented out.
|
|
1161
|
+
extractLayoutBlock(src, field) {
|
|
1162
|
+
const rx = new RegExp(`\\b${field}\\s*:\\s*\\{`);
|
|
1163
|
+
const m = src.match(rx);
|
|
1164
|
+
if (!m || m.index === void 0) return null;
|
|
1165
|
+
const before = src.slice(0, m.index);
|
|
1166
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
1167
|
+
if (before.slice(lineStart).trimStart().startsWith("//")) return null;
|
|
1168
|
+
let depth = 0, i = m.index + m[0].length - 1;
|
|
1169
|
+
const start = i + 1;
|
|
1170
|
+
for (; i < src.length; i++) {
|
|
1171
|
+
if (src[i] === "{") depth++;
|
|
1172
|
+
else if (src[i] === "}") {
|
|
1173
|
+
depth--;
|
|
1174
|
+
if (depth === 0) break;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return src.slice(start, i);
|
|
1178
|
+
}
|
|
1179
|
+
// Convert an extracted TS block body to a Lua table literal string.
|
|
1180
|
+
blockToLuaTable(block) {
|
|
1181
|
+
const str = (k) => block.match(new RegExp(`\\b${k}\\s*:\\s*['"]([^'"]+)['"]`))?.at(1) ?? null;
|
|
1182
|
+
const bool = (k) => {
|
|
1183
|
+
const m = block.match(new RegExp(`\\b${k}\\s*:\\s*(true|false)\\b`));
|
|
1184
|
+
return m ? m[1] === "true" : null;
|
|
1185
|
+
};
|
|
1186
|
+
const arr = (k) => {
|
|
1187
|
+
const m = block.match(new RegExp(`\\b${k}\\s*:\\s*\\[([^\\]]*)\\]`));
|
|
1188
|
+
return m ? [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]) : null;
|
|
1189
|
+
};
|
|
1190
|
+
const parts = [];
|
|
1191
|
+
const title = str("title");
|
|
1192
|
+
if (title !== null) parts.push(`title=${JSON.stringify(title)}`);
|
|
1193
|
+
const note = str("note");
|
|
1194
|
+
if (note !== null) parts.push(`note=${JSON.stringify(note)}`);
|
|
1195
|
+
const save = bool("saveKey");
|
|
1196
|
+
if (save !== null) parts.push(`saveKey=${save}`);
|
|
1197
|
+
const gkUrl = str("getKeyUrl");
|
|
1198
|
+
if (gkUrl !== null) parts.push(`getKeyUrl=${JSON.stringify(gkUrl)}`);
|
|
1199
|
+
const valUrl = str("validatorUrl");
|
|
1200
|
+
if (valUrl !== null) parts.push(`validatorUrl=${JSON.stringify(valUrl)}`);
|
|
1201
|
+
const keys = arr("keys");
|
|
1202
|
+
if (keys && keys.length) parts.push(`keys={${keys.map((k) => JSON.stringify(k)).join(",")}}`);
|
|
1203
|
+
return parts.length ? `{${parts.join(",")}}` : null;
|
|
1204
|
+
}
|
|
1196
1205
|
readLayoutConfig() {
|
|
1197
1206
|
const layoutTs = path.join(this.srcDir, "layout.ts");
|
|
1198
1207
|
const cfg = {
|
|
@@ -1361,16 +1370,21 @@ end)
|
|
|
1361
1370
|
if (lc["discord"]) parts.push(`,discord=${JSON.stringify(lc["discord"])}`);
|
|
1362
1371
|
if (lc["version"]) parts.push(`,version=${JSON.stringify(lc["version"])}`);
|
|
1363
1372
|
if (opts.dev) parts.push(`,dev=true`);
|
|
1373
|
+
const layoutSrc = fs.existsSync(path.join(this.srcDir, "layout.ts")) ? fs.readFileSync(path.join(this.srcDir, "layout.ts"), "utf8") : "";
|
|
1374
|
+
const ksBlock = this.extractLayoutBlock(layoutSrc, "keySystem");
|
|
1375
|
+
const prBlock = this.extractLayoutBlock(layoutSrc, "premium");
|
|
1376
|
+
if (ksBlock) {
|
|
1377
|
+
const lua = this.blockToLuaTable(ksBlock);
|
|
1378
|
+
if (lua) parts.push(`,keySystem=${lua}`);
|
|
1379
|
+
}
|
|
1380
|
+
if (prBlock) {
|
|
1381
|
+
const lua = this.blockToLuaTable(prBlock);
|
|
1382
|
+
if (lua) parts.push(`,premium=${lua}`);
|
|
1383
|
+
}
|
|
1364
1384
|
parts.push(`}
|
|
1365
1385
|
`);
|
|
1366
1386
|
parts.push(`local _P=loadstring(game:HttpGet("${base}/pulse.lua"))(_PULSE_LAYOUT)
|
|
1367
1387
|
`);
|
|
1368
|
-
if (opts.dev) {
|
|
1369
|
-
parts.push(`_PULSE_DEV = true
|
|
1370
|
-
`);
|
|
1371
|
-
parts.push(`if _P.Pulse and _P.Pulse.Log then _P.Pulse.Log.enable() end
|
|
1372
|
-
`);
|
|
1373
|
-
}
|
|
1374
1388
|
parts.push(`for _k,_v in pairs(_P) do _G[_k]=_v end
|
|
1375
1389
|
`);
|
|
1376
1390
|
parts.push(
|
|
@@ -1463,16 +1477,6 @@ end
|
|
|
1463
1477
|
}
|
|
1464
1478
|
parts.push("\n");
|
|
1465
1479
|
}
|
|
1466
|
-
if (order.some((f) => f.endsWith(".rblua") || f.endsWith(".ts"))) {
|
|
1467
|
-
parts.push("-- [generated: defaults runner]\n");
|
|
1468
|
-
parts.push(DEFAULTS_RUNNER);
|
|
1469
|
-
parts.push("\n");
|
|
1470
|
-
}
|
|
1471
|
-
if (opts.dev) {
|
|
1472
|
-
parts.push(
|
|
1473
|
-
'-- [dev: log config]\nif Pulse and Pulse.Log then\n Pulse.Log.configure({ level = "debug", console = true })\n Pulse.Log.info("dev", "dev build active")\nend\n\n'
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
1480
|
parts.push("-- [generated: destroy registration]\n");
|
|
1477
1481
|
parts.push(DESTROY_REGISTRATION);
|
|
1478
1482
|
parts.push("\n");
|
|
@@ -1612,7 +1616,12 @@ async function obfuscateSource(source, ib2Dir) {
|
|
|
1612
1616
|
if (stdout.startsWith("ERR:")) throw new Error(stdout.slice(4).trim());
|
|
1613
1617
|
if (result.status !== 0) throw new Error(stderr || stdout || `exit ${result.status}`);
|
|
1614
1618
|
if (!fs.existsSync(outPath)) throw new Error("IronBrew2 CLI produced no output file");
|
|
1615
|
-
|
|
1619
|
+
let out = fs.readFileSync(outPath, "utf8");
|
|
1620
|
+
if (out.startsWith("--[[")) {
|
|
1621
|
+
const closeIdx = out.indexOf("]]");
|
|
1622
|
+
if (closeIdx !== -1) out = out.slice(closeIdx + 2).replace(/^\r?\n/, "");
|
|
1623
|
+
}
|
|
1624
|
+
return out;
|
|
1616
1625
|
} finally {
|
|
1617
1626
|
try {
|
|
1618
1627
|
__require("fs").unlinkSync(tmpIn);
|
|
@@ -1883,7 +1892,7 @@ function makeClaudeMd(name) {
|
|
|
1883
1892
|
function makeAgentsMd(name) {
|
|
1884
1893
|
return read("AGENTS.md").replace(/{NAME}/g, name);
|
|
1885
1894
|
}
|
|
1886
|
-
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS,
|
|
1895
|
+
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS, TEMPLATE_PAGE_COMBAT_TS, TEMPLATE_COMPONENT_TS, TEMPLATE_EX_SPEED_TS, TEMPLATE_EX_FOV_TS, TEMPLATE_EX_ESP_TS, TEMPLATE_EX_AIMBOT_TS;
|
|
1887
1896
|
var init_templates = __esm({
|
|
1888
1897
|
"src/templates.ts"() {
|
|
1889
1898
|
init_cjs_shims();
|
|
@@ -1895,11 +1904,13 @@ var init_templates = __esm({
|
|
|
1895
1904
|
TEMPLATE_GITIGNORE = read("gitignore");
|
|
1896
1905
|
TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
|
|
1897
1906
|
TEMPLATE_LAYOUT_TS = read("layout.ts");
|
|
1898
|
-
|
|
1907
|
+
read("page_home.ts");
|
|
1908
|
+
TEMPLATE_PAGE_COMBAT_TS = read("page_combat.ts");
|
|
1899
1909
|
TEMPLATE_COMPONENT_TS = read("component.ts");
|
|
1900
1910
|
TEMPLATE_EX_SPEED_TS = read("component_speed.ts");
|
|
1901
1911
|
TEMPLATE_EX_FOV_TS = read("component_fov.ts");
|
|
1902
1912
|
TEMPLATE_EX_ESP_TS = read("component_esp.ts");
|
|
1913
|
+
TEMPLATE_EX_AIMBOT_TS = read("component_aimbot.ts");
|
|
1903
1914
|
}
|
|
1904
1915
|
});
|
|
1905
1916
|
|
|
@@ -1988,9 +1999,10 @@ async function cmdInit(args) {
|
|
|
1988
1999
|
["src/misc/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
|
|
1989
2000
|
["src/misc/remotes.lua", TEMPLATE_REMOTES],
|
|
1990
2001
|
["src/layout.ts", TEMPLATE_LAYOUT_TS.replace(/{NAME}/g, name)],
|
|
1991
|
-
["src/pages/
|
|
2002
|
+
["src/pages/1_Combat.ts", TEMPLATE_PAGE_COMBAT_TS],
|
|
1992
2003
|
["src/combat/SpeedHack.ts", TEMPLATE_EX_SPEED_TS],
|
|
1993
2004
|
["src/combat/FOVChanger.ts", TEMPLATE_EX_FOV_TS],
|
|
2005
|
+
["src/combat/Aimbot.ts", TEMPLATE_EX_AIMBOT_TS],
|
|
1994
2006
|
["src/visuals/PlayerESP.ts", TEMPLATE_EX_ESP_TS],
|
|
1995
2007
|
["tsconfig.json", TSCONFIG],
|
|
1996
2008
|
[".rb-deploy.example", TEMPLATE_DEPLOY_EXAMPLE],
|
package/package.json
CHANGED
package/pulse/runtime.lua
CHANGED
|
@@ -343,10 +343,11 @@ local function _signal(default)
|
|
|
343
343
|
_sigCounter = _sigCounter + 1
|
|
344
344
|
local s = Signal(default)
|
|
345
345
|
s._id = ((_currentComponent and _currentComponent._name) or "g") .. "_s" .. _sigCounter
|
|
346
|
-
s.set
|
|
347
|
-
s.
|
|
346
|
+
local _rawSet = s.set -- capture original before any override to avoid infinite recursion
|
|
347
|
+
s.set = function(_, v) _rawSet(s, v) end
|
|
348
|
+
s.toggle = function(_) _rawSet(s, not s:get()) end
|
|
348
349
|
s.watch = function(_, fn) return s:onChange(fn) end
|
|
349
|
-
s.update = function(_, fn) s
|
|
350
|
+
s.update = function(_, fn) _rawSet(s, fn(s:get())) end
|
|
350
351
|
return s
|
|
351
352
|
end
|
|
352
353
|
|
|
@@ -543,6 +544,7 @@ local function _groupbox(side, title, opts)
|
|
|
543
544
|
icon=(opts and opts.icon) or "",
|
|
544
545
|
mount=(opts and opts.mount) or nil,
|
|
545
546
|
plain=(opts and opts.plain) or false,
|
|
547
|
+
premium=(opts and opts.premium) or false,
|
|
546
548
|
widgets=(opts and opts.widgets) or {} }
|
|
547
549
|
end
|
|
548
550
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Example premium component — mark the groupbox as { premium: true } in your page.
|
|
2
|
+
// Users see an inline key-entry form until they unlock with a valid premium key.
|
|
3
|
+
defineComponent('Aimbot', (): WidgetDef[] => {
|
|
4
|
+
const enabled = signal<boolean>(false)
|
|
5
|
+
const fovRadius = signal<number>(90)
|
|
6
|
+
const smoothing = signal<number>(0.15)
|
|
7
|
+
|
|
8
|
+
on.heartbeat({ when: enabled, every: 0.016 }, (): void => {
|
|
9
|
+
// Aimbot logic — find nearest enemy and aim toward them.
|
|
10
|
+
// Use Pulse.World helpers and _PulseGetHRP() for the local root part.
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
toggle('Aimbot').bind(enabled),
|
|
15
|
+
slider('FOV Radius', { min: 10, max: 360, suffix: '°' }).bind(fovRadius),
|
|
16
|
+
slider('Smoothing', { min: 0, max: 1 }).bind(smoothing),
|
|
17
|
+
]
|
|
18
|
+
})
|
package/templates/layout.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
// Pulse layout — configures the script window.
|
|
2
|
-
//
|
|
1
|
+
// Pulse layout — configures the script window and optional key/premium systems.
|
|
2
|
+
// This file is never compiled into the output; the rb CLI reads it at build time.
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
title: '{NAME}',
|
|
6
6
|
author: '',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
description: 'A powerful script for {NAME}. Enable features from the tabs above.',
|
|
9
|
+
discord: '', // shown as "Join Discord" button in Home tab
|
|
7
10
|
toggleKey: 'RightControl',
|
|
8
11
|
size: [850, 560] as [number, number],
|
|
9
12
|
uiLibrary: 'windui' as const,
|
|
@@ -16,4 +19,34 @@ export default {
|
|
|
16
19
|
openButtonIcon: 'code-2',
|
|
17
20
|
themes: [] as LayoutConfig['themes'],
|
|
18
21
|
compatExclude: [] as string[],
|
|
22
|
+
|
|
23
|
+
// ── Key system (optional) ────────────────────────────────────────────────────
|
|
24
|
+
// Blocks the UI until the user enters a valid key.
|
|
25
|
+
// Remove this block entirely to let anyone use the script without a key.
|
|
26
|
+
//
|
|
27
|
+
// keySystem: {
|
|
28
|
+
// title: 'Key Required',
|
|
29
|
+
// note: 'Get your free key from our Discord',
|
|
30
|
+
// saveKey: true, // persist key to executor filesystem
|
|
31
|
+
// getKeyUrl: 'https://discord.gg/example',
|
|
32
|
+
//
|
|
33
|
+
// // Option A — static list (client-side):
|
|
34
|
+
// keys: ['FREE_KEY_1', 'FREE_KEY_2'],
|
|
35
|
+
//
|
|
36
|
+
// // Option B — server-side validator (more secure, any string "true" = valid):
|
|
37
|
+
// // validatorUrl: 'https://yoursite.com/validate?key=',
|
|
38
|
+
// },
|
|
39
|
+
|
|
40
|
+
// ── Premium tier (optional) ──────────────────────────────────────────────────
|
|
41
|
+
// A second key layer on top of the free tier.
|
|
42
|
+
// Mark groupboxes with { premium: true } in your pages to lock them.
|
|
43
|
+
// Users see an inline key-entry UI in each locked groupbox; entering a valid
|
|
44
|
+
// premium key unlocks all of them live — no re-injection needed.
|
|
45
|
+
// Premium keys also grant basic (keySystem) access automatically.
|
|
46
|
+
//
|
|
47
|
+
// premium: {
|
|
48
|
+
// keys: ['PREMIUM_KEY_1'],
|
|
49
|
+
// // OR: validatorUrl: 'https://yoursite.com/premium?key=',
|
|
50
|
+
// getKeyUrl: 'https://yoursite.com/premium', // shown in "Copy Link" / "Get Premium Key" buttons
|
|
51
|
+
// },
|
|
19
52
|
} satisfies LayoutConfig
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Page names must not clash with the built-in "Home" and "Settings" tabs.
|
|
2
|
+
// Rename this page and add more pages as needed.
|
|
3
|
+
definePage('Combat', { icon: 'swords' }, () => [
|
|
4
|
+
groupbox('left', 'Movement', { icon: 'person', mount: 'SpeedHack' }),
|
|
5
|
+
groupbox('left', 'Camera', { icon: 'eye', mount: 'FOVChanger' }),
|
|
6
|
+
groupbox('right', 'Visuals', { icon: 'scan-eye', mount: 'PlayerESP' }),
|
|
7
|
+
// premium: true — shows inline key-entry form until the user unlocks with a premium key.
|
|
8
|
+
groupbox('right', 'Aimbot', { icon: 'crosshair', mount: 'Aimbot', premium: true }),
|
|
9
|
+
])
|