@chrrxs/robloxstudio-mcp 2.16.4 → 2.17.1
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/dist/index.js +234 -151
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +1456 -416
- package/studio-plugin/MCPPlugin.rbxmx +1456 -416
- package/studio-plugin/src/modules/ClientBroker.ts +10 -0
- package/studio-plugin/src/modules/Communication.ts +11 -7
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +36 -10
- package/studio-plugin/src/modules/ServerUrlSettings.ts +62 -9
- package/studio-plugin/src/modules/UI.ts +11 -4
- package/studio-plugin/src/modules/Utils.ts +147 -13
- package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +460 -0
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -33
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +3 -0
- package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +386 -0
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +3 -209
- package/studio-plugin/src/server/index.server.ts +20 -5
|
@@ -16,6 +16,7 @@ local cleanupLegacyEditBridges = _EvalBridges.cleanupLegacyEditBridges
|
|
|
16
16
|
local ensureRuntimeBridgeInstalled = _EvalBridges.ensureRuntimeBridgeInstalled
|
|
17
17
|
local RuntimeLogBuffer = TS.import(script, script, "modules", "RuntimeLogBuffer")
|
|
18
18
|
local StopPlayMonitor = TS.import(script, script, "modules", "StopPlayMonitor")
|
|
19
|
+
local BreakpointHandlers = TS.import(script, script, "modules", "handlers", "BreakpointHandlers")
|
|
19
20
|
local RenderMonitor = TS.import(script, script, "modules", "RenderMonitor")
|
|
20
21
|
-- Track render-loop liveness so input/screenshot tools can report "window
|
|
21
22
|
-- minimized / not rendering" instead of silently no-op'ing. No-op in the
|
|
@@ -29,7 +30,22 @@ RuntimeLogBuffer.install()
|
|
|
29
30
|
-- edit DM (write the flag) and the play-server DM (read+act on the flag) can
|
|
30
31
|
-- access plugin:SetSetting/GetSetting.
|
|
31
32
|
StopPlayMonitor.init(plugin)
|
|
33
|
+
BreakpointHandlers.init(plugin)
|
|
32
34
|
ServerUrlSettings.init(plugin)
|
|
35
|
+
local function applyRememberedServerUrl()
|
|
36
|
+
local rememberedServerUrl = ServerUrlSettings.readServerUrl()
|
|
37
|
+
if rememberedServerUrl == nil then
|
|
38
|
+
return nil
|
|
39
|
+
end
|
|
40
|
+
local conn = State.getActiveConnection()
|
|
41
|
+
conn.serverUrl = rememberedServerUrl
|
|
42
|
+
local port = ServerUrlSettings.extractPort(rememberedServerUrl)
|
|
43
|
+
if port ~= nil then
|
|
44
|
+
conn.port = port
|
|
45
|
+
end
|
|
46
|
+
ClientBroker.setServerUrl(rememberedServerUrl)
|
|
47
|
+
end
|
|
48
|
+
applyRememberedServerUrl()
|
|
33
49
|
UI.init(plugin)
|
|
34
50
|
local elements = UI.getElements()
|
|
35
51
|
local ICON_DISCONNECTED = "rbxassetid://125921838360800"
|
|
@@ -92,17 +108,13 @@ task.delay(2, function()
|
|
|
92
108
|
_condition = ClientBroker.DEFAULT_MCP_URL
|
|
93
109
|
end
|
|
94
110
|
local inheritedServerUrl = _condition
|
|
95
|
-
conn.serverUrl = inheritedServerUrl
|
|
96
|
-
elements.urlInput.Text =
|
|
97
|
-
local
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
if _condition_1 == nil then
|
|
101
|
-
_condition_1 = conn.port
|
|
102
|
-
end
|
|
103
|
-
conn.port = _condition_1
|
|
111
|
+
conn.serverUrl = ServerUrlSettings.normalizeServerUrl(inheritedServerUrl)
|
|
112
|
+
elements.urlInput.Text = conn.serverUrl
|
|
113
|
+
local port = ServerUrlSettings.extractPort(conn.serverUrl)
|
|
114
|
+
if port ~= nil then
|
|
115
|
+
conn.port = port
|
|
104
116
|
end
|
|
105
|
-
ClientBroker.setServerUrl(
|
|
117
|
+
ClientBroker.setServerUrl(conn.serverUrl)
|
|
106
118
|
end
|
|
107
119
|
-- Defensive default: in invisible play-DM UIs, the input field
|
|
108
120
|
-- may not be populated by the time we activate.
|
|
@@ -147,6 +159,8 @@ local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "Scen
|
|
|
147
159
|
local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
|
|
148
160
|
local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
|
|
149
161
|
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
162
|
+
local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
|
|
163
|
+
local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
|
|
150
164
|
local LuauExec = TS.import(script, script.Parent, "LuauExec")
|
|
151
165
|
local State = TS.import(script, script.Parent, "State")
|
|
152
166
|
local HttpDiagnostics = TS.import(script, script.Parent, "HttpDiagnostics")
|
|
@@ -215,6 +229,8 @@ local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
|
|
|
215
229
|
["/api/get-runtime-logs"] = true,
|
|
216
230
|
["/api/get-memory-breakdown"] = true,
|
|
217
231
|
["/api/get-scene-analysis"] = true,
|
|
232
|
+
["/api/breakpoints"] = true,
|
|
233
|
+
["/api/capture-script-profiler"] = true,
|
|
218
234
|
["/api/multiplayer-test-state"] = true,
|
|
219
235
|
["/api/multiplayer-test-leave-client"] = true,
|
|
220
236
|
["/api/capture-begin"] = true,
|
|
@@ -404,6 +420,12 @@ local function setupClientBroker()
|
|
|
404
420
|
if payload and payload.endpoint == "/api/get-scene-analysis" then
|
|
405
421
|
return SceneAnalysisHandlers.getSceneAnalysis(payload.data or {})
|
|
406
422
|
end
|
|
423
|
+
if payload and payload.endpoint == "/api/breakpoints" then
|
|
424
|
+
return BreakpointHandlers.breakpoints(payload.data or {})
|
|
425
|
+
end
|
|
426
|
+
if payload and payload.endpoint == "/api/capture-script-profiler" then
|
|
427
|
+
return ScriptProfilerHandlers.captureScriptProfiler(payload.data or {})
|
|
428
|
+
end
|
|
407
429
|
if payload and payload.endpoint == "/api/multiplayer-test-state" then
|
|
408
430
|
return handleMultiplayerTestState()
|
|
409
431
|
end
|
|
@@ -650,6 +672,8 @@ local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
|
|
|
650
672
|
local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
|
|
651
673
|
local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
|
|
652
674
|
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
675
|
+
local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
|
|
676
|
+
local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
|
|
653
677
|
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
654
678
|
local ClientBroker = TS.import(script, script.Parent, "ClientBroker")
|
|
655
679
|
local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
|
|
@@ -739,7 +763,6 @@ local routeMap = {
|
|
|
739
763
|
["/api/grep-scripts"] = QueryHandlers.grepScripts,
|
|
740
764
|
["/api/get-descendants"] = QueryHandlers.getDescendants,
|
|
741
765
|
["/api/compare-instances"] = QueryHandlers.compareInstances,
|
|
742
|
-
["/api/get-output-log"] = QueryHandlers.getOutputLog,
|
|
743
766
|
["/api/set-property"] = PropertyHandlers.setProperty,
|
|
744
767
|
["/api/set-properties"] = PropertyHandlers.setProperties,
|
|
745
768
|
["/api/mass-set-property"] = PropertyHandlers.massSetProperty,
|
|
@@ -771,13 +794,11 @@ local routeMap = {
|
|
|
771
794
|
["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
|
|
772
795
|
["/api/start-playtest"] = TestHandlers.startPlaytest,
|
|
773
796
|
["/api/stop-playtest"] = TestHandlers.stopPlaytest,
|
|
774
|
-
["/api/get-playtest-output"] = TestHandlers.getPlaytestOutput,
|
|
775
797
|
["/api/multiplayer-test-start"] = TestHandlers.multiplayerTestStart,
|
|
776
798
|
["/api/multiplayer-test-state"] = TestHandlers.multiplayerTestState,
|
|
777
799
|
["/api/multiplayer-test-add-players"] = TestHandlers.multiplayerTestAddPlayers,
|
|
778
800
|
["/api/multiplayer-test-leave-client"] = TestHandlers.multiplayerTestLeaveClient,
|
|
779
801
|
["/api/multiplayer-test-end"] = TestHandlers.multiplayerTestEnd,
|
|
780
|
-
["/api/character-navigation"] = TestHandlers.characterNavigation,
|
|
781
802
|
["/api/export-build"] = BuildHandlers.exportBuild,
|
|
782
803
|
["/api/import-build"] = BuildHandlers.importBuild,
|
|
783
804
|
["/api/import-scene"] = BuildHandlers.importScene,
|
|
@@ -791,6 +812,8 @@ local routeMap = {
|
|
|
791
812
|
["/api/simulate-keyboard-input"] = InputHandlers.simulateKeyboardInput,
|
|
792
813
|
["/api/find-and-replace-in-scripts"] = ScriptHandlers.findAndReplaceInScripts,
|
|
793
814
|
["/api/get-runtime-logs"] = LogHandlers.getRuntimeLogs,
|
|
815
|
+
["/api/breakpoints"] = BreakpointHandlers.breakpoints,
|
|
816
|
+
["/api/capture-script-profiler"] = ScriptProfilerHandlers.captureScriptProfiler,
|
|
794
817
|
["/api/export-rbxm"] = SerializationHandlers.exportRbxm,
|
|
795
818
|
["/api/import-rbxm"] = SerializationHandlers.importRbxm,
|
|
796
819
|
["/api/get-memory-breakdown"] = MemoryHandlers.getMemoryBreakdown,
|
|
@@ -948,6 +971,7 @@ function sendReady(conn)
|
|
|
948
971
|
end
|
|
949
972
|
end
|
|
950
973
|
lastReadyInstanceId = if _condition then readyData.instanceId else instanceId
|
|
974
|
+
ServerUrlSettings.rememberServerUrl(conn.serverUrl)
|
|
951
975
|
local _condition_1 = assignedRole
|
|
952
976
|
if _condition_1 == nil then
|
|
953
977
|
_condition_1 = detectRole()
|
|
@@ -1158,19 +1182,19 @@ local function activatePlugin(connIndex)
|
|
|
1158
1182
|
conn.consecutiveFailures = 0
|
|
1159
1183
|
conn.currentRetryDelay = 0.5
|
|
1160
1184
|
if idx == State.getActiveTabIndex() then
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
if
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1185
|
+
local normalizedUrl = ServerUrlSettings.normalizeServerUrl(ui.urlInput.Text)
|
|
1186
|
+
conn.serverUrl = if normalizedUrl ~= "" then normalizedUrl else conn.serverUrl
|
|
1187
|
+
if conn.serverUrl == "" then
|
|
1188
|
+
conn.serverUrl = ClientBroker.DEFAULT_MCP_URL
|
|
1189
|
+
end
|
|
1190
|
+
ui.urlInput.Text = conn.serverUrl
|
|
1191
|
+
local port = ServerUrlSettings.extractPort(conn.serverUrl)
|
|
1192
|
+
if port ~= nil then
|
|
1193
|
+
conn.port = port
|
|
1169
1194
|
end
|
|
1170
1195
|
UI.updateTabLabel(idx)
|
|
1171
1196
|
UI.updateUIState()
|
|
1172
1197
|
end
|
|
1173
|
-
ServerUrlSettings.rememberServerUrl(conn.serverUrl)
|
|
1174
1198
|
UI.updateTabDot(idx)
|
|
1175
1199
|
if not conn.heartbeatConnection then
|
|
1176
1200
|
conn.heartbeatConnection = RunService.Heartbeat:Connect(function()
|
|
@@ -1411,9 +1435,9 @@ local function computeBridgeStamp()
|
|
|
1411
1435
|
for i = 1, #combined do
|
|
1412
1436
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1413
1437
|
end
|
|
1414
|
-
-- "2.
|
|
1438
|
+
-- "2.17.1" is replaced with the package version at package time
|
|
1415
1439
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1416
|
-
return `{tostring(h)}-2.
|
|
1440
|
+
return `{tostring(h)}-2.17.1`
|
|
1417
1441
|
end
|
|
1418
1442
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1419
1443
|
local function setSource(scriptInst, source)
|
|
@@ -1857,6 +1881,524 @@ return {
|
|
|
1857
1881
|
</Properties>
|
|
1858
1882
|
</Item>
|
|
1859
1883
|
<Item class="ModuleScript" referent="7">
|
|
1884
|
+
<Properties>
|
|
1885
|
+
<string name="Name">BreakpointHandlers</string>
|
|
1886
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
1887
|
+
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
1888
|
+
local Utils = TS.import(script, script.Parent.Parent, "Utils")
|
|
1889
|
+
local _binding = Utils
|
|
1890
|
+
local getInstanceByPath = _binding.getInstanceByPath
|
|
1891
|
+
local HttpService = game:GetService("HttpService")
|
|
1892
|
+
local RunService = game:GetService("RunService")
|
|
1893
|
+
local ServerStorage = game:GetService("ServerStorage")
|
|
1894
|
+
local LOG_PREFIX = "Breakpoint"
|
|
1895
|
+
local REGISTRY_KEY_PREFIX = "MCP_BREAKPOINTS_V1_"
|
|
1896
|
+
local MCP_PLACE_ID_ATTRIBUTE = "__MCPPlaceId"
|
|
1897
|
+
local pluginRef
|
|
1898
|
+
local loadedRegistryKey
|
|
1899
|
+
local loadedRegistryFromSettings = false
|
|
1900
|
+
local breakpoints = {}
|
|
1901
|
+
local function init(p)
|
|
1902
|
+
pluginRef = p
|
|
1903
|
+
end
|
|
1904
|
+
local function breakpointKey(scriptPath, line)
|
|
1905
|
+
return `{scriptPath}:{line}`
|
|
1906
|
+
end
|
|
1907
|
+
local function computeInstanceId()
|
|
1908
|
+
if game.PlaceId ~= 0 then
|
|
1909
|
+
return `place:{tostring(game.PlaceId)}`
|
|
1910
|
+
end
|
|
1911
|
+
local existing = ServerStorage:GetAttribute(MCP_PLACE_ID_ATTRIBUTE)
|
|
1912
|
+
if type(existing) == "string" and existing ~= "" then
|
|
1913
|
+
return `anon:{existing}`
|
|
1914
|
+
end
|
|
1915
|
+
local fresh = HttpService:GenerateGUID(false)
|
|
1916
|
+
pcall(function()
|
|
1917
|
+
return ServerStorage:SetAttribute(MCP_PLACE_ID_ATTRIBUTE, fresh)
|
|
1918
|
+
end)
|
|
1919
|
+
return `anon:{fresh}`
|
|
1920
|
+
end
|
|
1921
|
+
local function detectRole()
|
|
1922
|
+
if not RunService:IsRunning() then
|
|
1923
|
+
return "edit"
|
|
1924
|
+
end
|
|
1925
|
+
if RunService:IsServer() then
|
|
1926
|
+
return "server"
|
|
1927
|
+
end
|
|
1928
|
+
return "client"
|
|
1929
|
+
end
|
|
1930
|
+
local function requestedRole(requestData)
|
|
1931
|
+
local ___mcp_target_role = requestData.__mcp_target_role
|
|
1932
|
+
local _condition = type(___mcp_target_role) == "string"
|
|
1933
|
+
if _condition then
|
|
1934
|
+
_condition = requestData.__mcp_target_role ~= ""
|
|
1935
|
+
end
|
|
1936
|
+
return if _condition then requestData.__mcp_target_role else detectRole()
|
|
1937
|
+
end
|
|
1938
|
+
local function registryScope(requestData)
|
|
1939
|
+
local ___mcp_instance_id = requestData.__mcp_instance_id
|
|
1940
|
+
local _condition = type(___mcp_instance_id) == "string"
|
|
1941
|
+
if _condition then
|
|
1942
|
+
_condition = requestData.__mcp_instance_id ~= ""
|
|
1943
|
+
end
|
|
1944
|
+
local instanceId = if _condition then requestData.__mcp_instance_id else computeInstanceId()
|
|
1945
|
+
local role = requestedRole(requestData)
|
|
1946
|
+
return {
|
|
1947
|
+
key = `{REGISTRY_KEY_PREFIX}{instanceId}:{role}`,
|
|
1948
|
+
}
|
|
1949
|
+
end
|
|
1950
|
+
local function readSetting(key)
|
|
1951
|
+
if not pluginRef then
|
|
1952
|
+
return nil
|
|
1953
|
+
end
|
|
1954
|
+
local ok, value = pcall(function()
|
|
1955
|
+
return pluginRef:GetSetting(key)
|
|
1956
|
+
end)
|
|
1957
|
+
return if ok then value else nil
|
|
1958
|
+
end
|
|
1959
|
+
local function writeSetting(key, value)
|
|
1960
|
+
if not pluginRef then
|
|
1961
|
+
return false
|
|
1962
|
+
end
|
|
1963
|
+
local ok = pcall(function()
|
|
1964
|
+
return pluginRef:SetSetting(key, value)
|
|
1965
|
+
end)
|
|
1966
|
+
return ok
|
|
1967
|
+
end
|
|
1968
|
+
local function decodePersistedBreakpointEntry(value)
|
|
1969
|
+
local _value = value
|
|
1970
|
+
if not (type(_value) == "table") then
|
|
1971
|
+
return nil
|
|
1972
|
+
end
|
|
1973
|
+
local data = value
|
|
1974
|
+
local _script_path = data.script_path
|
|
1975
|
+
local _condition = not (type(_script_path) == "string")
|
|
1976
|
+
if not _condition then
|
|
1977
|
+
_condition = data.script_path == ""
|
|
1978
|
+
end
|
|
1979
|
+
if _condition then
|
|
1980
|
+
return nil
|
|
1981
|
+
end
|
|
1982
|
+
local _line = data.line
|
|
1983
|
+
local _condition_1 = not (type(_line) == "number")
|
|
1984
|
+
if not _condition_1 then
|
|
1985
|
+
_condition_1 = data.line < 1
|
|
1986
|
+
end
|
|
1987
|
+
if _condition_1 then
|
|
1988
|
+
return nil
|
|
1989
|
+
end
|
|
1990
|
+
return {
|
|
1991
|
+
script_path = data.script_path,
|
|
1992
|
+
line = math.floor(data.line),
|
|
1993
|
+
}
|
|
1994
|
+
end
|
|
1995
|
+
local function loadRegistry(requestData)
|
|
1996
|
+
local scope = registryScope(requestData)
|
|
1997
|
+
if loadedRegistryKey ~= scope.key then
|
|
1998
|
+
table.clear(breakpoints)
|
|
1999
|
+
loadedRegistryKey = scope.key
|
|
2000
|
+
loadedRegistryFromSettings = false
|
|
2001
|
+
end
|
|
2002
|
+
if loadedRegistryFromSettings then
|
|
2003
|
+
return scope
|
|
2004
|
+
end
|
|
2005
|
+
loadedRegistryFromSettings = true
|
|
2006
|
+
local stored = readSetting(scope.key)
|
|
2007
|
+
if stored == nil then
|
|
2008
|
+
return scope
|
|
2009
|
+
end
|
|
2010
|
+
local decoded = stored
|
|
2011
|
+
if type(stored) == "string" then
|
|
2012
|
+
local ok, result = pcall(function()
|
|
2013
|
+
return HttpService:JSONDecode(stored)
|
|
2014
|
+
end)
|
|
2015
|
+
if not ok then
|
|
2016
|
+
return scope
|
|
2017
|
+
end
|
|
2018
|
+
decoded = result
|
|
2019
|
+
end
|
|
2020
|
+
local _decoded = decoded
|
|
2021
|
+
if not (type(_decoded) == "table") then
|
|
2022
|
+
return scope
|
|
2023
|
+
end
|
|
2024
|
+
table.clear(breakpoints)
|
|
2025
|
+
for _, item in decoded do
|
|
2026
|
+
local entry = decodePersistedBreakpointEntry(item)
|
|
2027
|
+
if entry then
|
|
2028
|
+
local _arg0 = breakpointKey(entry.script_path, entry.line)
|
|
2029
|
+
breakpoints[_arg0] = entry
|
|
2030
|
+
end
|
|
2031
|
+
end
|
|
2032
|
+
return scope
|
|
2033
|
+
end
|
|
2034
|
+
local function persistRegistry(scope)
|
|
2035
|
+
if not pluginRef then
|
|
2036
|
+
return {
|
|
2037
|
+
ok = false,
|
|
2038
|
+
error = "Plugin settings are unavailable; managed breakpoint registry is memory-only.",
|
|
2039
|
+
}
|
|
2040
|
+
end
|
|
2041
|
+
local out = {}
|
|
2042
|
+
for _, entry in breakpoints do
|
|
2043
|
+
local _arg0 = {
|
|
2044
|
+
script_path = entry.script_path,
|
|
2045
|
+
line = entry.line,
|
|
2046
|
+
}
|
|
2047
|
+
table.insert(out, _arg0)
|
|
2048
|
+
end
|
|
2049
|
+
local encodedOk, encoded = pcall(function()
|
|
2050
|
+
return HttpService:JSONEncode(out)
|
|
2051
|
+
end)
|
|
2052
|
+
if not encodedOk or not (type(encoded) == "string") then
|
|
2053
|
+
return {
|
|
2054
|
+
ok = false,
|
|
2055
|
+
error = `Failed to encode managed breakpoint registry: {tostring(encoded)}`,
|
|
2056
|
+
}
|
|
2057
|
+
end
|
|
2058
|
+
if not writeSetting(scope.key, encoded) then
|
|
2059
|
+
return {
|
|
2060
|
+
ok = false,
|
|
2061
|
+
error = "Failed to persist managed breakpoint registry with plugin:SetSetting.",
|
|
2062
|
+
}
|
|
2063
|
+
end
|
|
2064
|
+
local stored = readSetting(scope.key)
|
|
2065
|
+
if stored ~= encoded then
|
|
2066
|
+
return {
|
|
2067
|
+
ok = false,
|
|
2068
|
+
error = "Failed to verify managed breakpoint registry persistence after plugin:SetSetting.",
|
|
2069
|
+
}
|
|
2070
|
+
end
|
|
2071
|
+
return {
|
|
2072
|
+
ok = true,
|
|
2073
|
+
}
|
|
2074
|
+
end
|
|
2075
|
+
local function attachPersistenceWarning(response, persist)
|
|
2076
|
+
if not persist.ok then
|
|
2077
|
+
response.managed_registry_persisted = false
|
|
2078
|
+
response.registry_error = persist.error
|
|
2079
|
+
end
|
|
2080
|
+
return response
|
|
2081
|
+
end
|
|
2082
|
+
local function serviceError(message)
|
|
2083
|
+
local _object = {
|
|
2084
|
+
error = "script_debugger_unavailable",
|
|
2085
|
+
}
|
|
2086
|
+
local _left = "message"
|
|
2087
|
+
local _condition = message
|
|
2088
|
+
if _condition == nil then
|
|
2089
|
+
_condition = "ScriptDebuggerService is unavailable. Enable the Studio Debugger Luau API beta feature and restart Studio."
|
|
2090
|
+
end
|
|
2091
|
+
_object[_left] = _condition
|
|
2092
|
+
_object.betaFeatureRequired = true
|
|
2093
|
+
return _object
|
|
2094
|
+
end
|
|
2095
|
+
local function operationError(errorCode, operation, raw)
|
|
2096
|
+
return {
|
|
2097
|
+
error = errorCode,
|
|
2098
|
+
message = `{operation} failed. The breakpoints tool requires the Studio Debugger Luau API beta feature. ` .. "Enable it in Studio Beta Features and restart/reload Studio, then retry.",
|
|
2099
|
+
rawMessage = tostring(raw),
|
|
2100
|
+
betaFeatureRequired = true,
|
|
2101
|
+
}
|
|
2102
|
+
end
|
|
2103
|
+
local function getService()
|
|
2104
|
+
local provider = game
|
|
2105
|
+
local ok, service = pcall(function()
|
|
2106
|
+
return provider:GetService("ScriptDebuggerService")
|
|
2107
|
+
end)
|
|
2108
|
+
if not ok or not service then
|
|
2109
|
+
return serviceError(`ScriptDebuggerService unavailable: {tostring(service)}`)
|
|
2110
|
+
end
|
|
2111
|
+
return service
|
|
2112
|
+
end
|
|
2113
|
+
local function luauStringLiteral(value)
|
|
2114
|
+
local escaped = (string.gsub(value, "\\", "\\\\"))
|
|
2115
|
+
escaped = (string.gsub(escaped, "\n", "\\n"))
|
|
2116
|
+
escaped = (string.gsub(escaped, "\r", "\\r"))
|
|
2117
|
+
escaped = (string.gsub(escaped, "\t", "\\t"))
|
|
2118
|
+
escaped = (string.gsub(escaped, '"', '\\"'))
|
|
2119
|
+
return `"{escaped}"`
|
|
2120
|
+
end
|
|
2121
|
+
local function buildLogMessage(scriptPath, line, logMessage)
|
|
2122
|
+
local prefix = { luauStringLiteral(LOG_PREFIX), luauStringLiteral(`{scriptPath}:{line}`) }
|
|
2123
|
+
local _logMessage = logMessage
|
|
2124
|
+
local _condition = type(_logMessage) == "string"
|
|
2125
|
+
if _condition then
|
|
2126
|
+
_condition = logMessage ~= ""
|
|
2127
|
+
end
|
|
2128
|
+
if _condition then
|
|
2129
|
+
local _logMessage_1 = logMessage
|
|
2130
|
+
table.insert(prefix, _logMessage_1)
|
|
2131
|
+
end
|
|
2132
|
+
return table.concat(prefix, ", ")
|
|
2133
|
+
end
|
|
2134
|
+
local function listBreakpoints(requestData)
|
|
2135
|
+
loadRegistry(requestData)
|
|
2136
|
+
local out = {}
|
|
2137
|
+
for _, entry in breakpoints do
|
|
2138
|
+
table.insert(out, entry)
|
|
2139
|
+
end
|
|
2140
|
+
return {
|
|
2141
|
+
breakpoints = out,
|
|
2142
|
+
count = #out,
|
|
2143
|
+
}
|
|
2144
|
+
end
|
|
2145
|
+
local function setBreakpoint(requestData)
|
|
2146
|
+
local scope = loadRegistry(requestData)
|
|
2147
|
+
local serviceOrError = getService()
|
|
2148
|
+
local _value = serviceOrError.IsA
|
|
2149
|
+
if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
|
|
2150
|
+
return serviceOrError
|
|
2151
|
+
end
|
|
2152
|
+
local service = serviceOrError
|
|
2153
|
+
local scriptPath = requestData.script_path
|
|
2154
|
+
local lineRaw = requestData.line
|
|
2155
|
+
if not (type(scriptPath) == "string") or scriptPath == "" or not (type(lineRaw) == "number") then
|
|
2156
|
+
return {
|
|
2157
|
+
error = "invalid_args",
|
|
2158
|
+
message = "breakpoints action=set requires script_path and line",
|
|
2159
|
+
}
|
|
2160
|
+
end
|
|
2161
|
+
local requestedLine = math.floor(lineRaw)
|
|
2162
|
+
if requestedLine < 1 then
|
|
2163
|
+
return {
|
|
2164
|
+
error = "invalid_line",
|
|
2165
|
+
message = "line must be a 1-based positive number",
|
|
2166
|
+
}
|
|
2167
|
+
end
|
|
2168
|
+
local instance = getInstanceByPath(scriptPath)
|
|
2169
|
+
if not instance then
|
|
2170
|
+
return {
|
|
2171
|
+
error = "script_not_found",
|
|
2172
|
+
script_path = scriptPath,
|
|
2173
|
+
}
|
|
2174
|
+
end
|
|
2175
|
+
if not instance:IsA("LuaSourceContainer") then
|
|
2176
|
+
return {
|
|
2177
|
+
error = "not_a_script",
|
|
2178
|
+
message = `{scriptPath} is {instance.ClassName}, not a LuaSourceContainer`,
|
|
2179
|
+
script_path = scriptPath,
|
|
2180
|
+
}
|
|
2181
|
+
end
|
|
2182
|
+
local _log_message = requestData.log_message
|
|
2183
|
+
local rawLogMessage = if type(_log_message) == "string" then requestData.log_message else nil
|
|
2184
|
+
local hasLogMessage = rawLogMessage ~= nil and rawLogMessage ~= ""
|
|
2185
|
+
local _continue_execution = requestData.continue_execution
|
|
2186
|
+
local continueExecution = if type(_continue_execution) == "boolean" then requestData.continue_execution else hasLogMessage
|
|
2187
|
+
local _enabled = requestData.enabled
|
|
2188
|
+
local enabled = if type(_enabled) == "boolean" then requestData.enabled else true
|
|
2189
|
+
local effectiveLogMessage = if hasLogMessage or continueExecution then buildLogMessage(scriptPath, requestedLine, rawLogMessage) else nil
|
|
2190
|
+
local spec = {
|
|
2191
|
+
Line = requestedLine,
|
|
2192
|
+
Enabled = enabled,
|
|
2193
|
+
ContinueExecution = continueExecution,
|
|
2194
|
+
}
|
|
2195
|
+
local _condition = requestData.condition
|
|
2196
|
+
local _condition_1 = type(_condition) == "string"
|
|
2197
|
+
if _condition_1 then
|
|
2198
|
+
_condition_1 = requestData.condition ~= ""
|
|
2199
|
+
end
|
|
2200
|
+
if _condition_1 then
|
|
2201
|
+
spec.Condition = requestData.condition
|
|
2202
|
+
end
|
|
2203
|
+
if effectiveLogMessage ~= nil then
|
|
2204
|
+
spec.LogMessage = effectiveLogMessage
|
|
2205
|
+
end
|
|
2206
|
+
local ok, result = pcall(function()
|
|
2207
|
+
return service:AddBreakpoint(instance, spec)
|
|
2208
|
+
end)
|
|
2209
|
+
if not ok then
|
|
2210
|
+
return operationError("add_breakpoint_failed", "ScriptDebuggerService:AddBreakpoint", result)
|
|
2211
|
+
end
|
|
2212
|
+
local breakpointResult = result
|
|
2213
|
+
local _line = breakpointResult.Line
|
|
2214
|
+
local actualLine = if type(_line) == "number" then breakpointResult.Line else requestedLine
|
|
2215
|
+
local _verified = breakpointResult.Verified
|
|
2216
|
+
local verified = if type(_verified) == "boolean" then breakpointResult.Verified else nil
|
|
2217
|
+
local _message = breakpointResult.Message
|
|
2218
|
+
local message = if type(_message) == "string" then breakpointResult.Message else nil
|
|
2219
|
+
local entry = {
|
|
2220
|
+
script_path = scriptPath,
|
|
2221
|
+
line = actualLine,
|
|
2222
|
+
requested_line = if actualLine ~= requestedLine then requestedLine else nil,
|
|
2223
|
+
enabled = enabled,
|
|
2224
|
+
condition = spec.Condition,
|
|
2225
|
+
log_message = rawLogMessage,
|
|
2226
|
+
continue_execution = continueExecution,
|
|
2227
|
+
verified = if verified == false then false else nil,
|
|
2228
|
+
message = message,
|
|
2229
|
+
created_at = DateTime.now().UnixTimestampMillis,
|
|
2230
|
+
}
|
|
2231
|
+
local _arg0 = breakpointKey(scriptPath, actualLine)
|
|
2232
|
+
breakpoints[_arg0] = entry
|
|
2233
|
+
return attachPersistenceWarning({
|
|
2234
|
+
ok = true,
|
|
2235
|
+
breakpoint = entry,
|
|
2236
|
+
}, persistRegistry(scope))
|
|
2237
|
+
end
|
|
2238
|
+
local function removeBreakpoint(requestData)
|
|
2239
|
+
local scope = loadRegistry(requestData)
|
|
2240
|
+
local serviceOrError = getService()
|
|
2241
|
+
local _value = serviceOrError.IsA
|
|
2242
|
+
if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
|
|
2243
|
+
return serviceOrError
|
|
2244
|
+
end
|
|
2245
|
+
local service = serviceOrError
|
|
2246
|
+
local scriptPath = requestData.script_path
|
|
2247
|
+
local lineRaw = requestData.line
|
|
2248
|
+
if not (type(scriptPath) == "string") or scriptPath == "" or not (type(lineRaw) == "number") then
|
|
2249
|
+
return {
|
|
2250
|
+
error = "invalid_args",
|
|
2251
|
+
message = "breakpoints action=remove requires script_path and line",
|
|
2252
|
+
}
|
|
2253
|
+
end
|
|
2254
|
+
local line = math.floor(lineRaw)
|
|
2255
|
+
if line < 1 then
|
|
2256
|
+
return {
|
|
2257
|
+
error = "invalid_line",
|
|
2258
|
+
message = "line must be a 1-based positive number",
|
|
2259
|
+
}
|
|
2260
|
+
end
|
|
2261
|
+
local instance = getInstanceByPath(scriptPath)
|
|
2262
|
+
if not instance then
|
|
2263
|
+
return {
|
|
2264
|
+
error = "script_not_found",
|
|
2265
|
+
script_path = scriptPath,
|
|
2266
|
+
}
|
|
2267
|
+
end
|
|
2268
|
+
if not instance:IsA("LuaSourceContainer") then
|
|
2269
|
+
return {
|
|
2270
|
+
error = "not_a_script",
|
|
2271
|
+
message = `{scriptPath} is {instance.ClassName}, not a LuaSourceContainer`,
|
|
2272
|
+
script_path = scriptPath,
|
|
2273
|
+
}
|
|
2274
|
+
end
|
|
2275
|
+
local ok, removed = pcall(function()
|
|
2276
|
+
return service:RemoveBreakpoint(instance, line)
|
|
2277
|
+
end)
|
|
2278
|
+
if not ok then
|
|
2279
|
+
return operationError("remove_breakpoint_failed", "ScriptDebuggerService:RemoveBreakpoint", removed)
|
|
2280
|
+
end
|
|
2281
|
+
local _arg0 = breakpointKey(scriptPath, line)
|
|
2282
|
+
breakpoints[_arg0] = nil
|
|
2283
|
+
return attachPersistenceWarning({
|
|
2284
|
+
ok = true,
|
|
2285
|
+
removed = removed,
|
|
2286
|
+
script_path = scriptPath,
|
|
2287
|
+
line = line,
|
|
2288
|
+
}, persistRegistry(scope))
|
|
2289
|
+
end
|
|
2290
|
+
local function clearManagedBreakpoints(requestData)
|
|
2291
|
+
local scope = loadRegistry(requestData)
|
|
2292
|
+
local serviceOrError = getService()
|
|
2293
|
+
local _value = serviceOrError.IsA
|
|
2294
|
+
if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
|
|
2295
|
+
return serviceOrError
|
|
2296
|
+
end
|
|
2297
|
+
local service = serviceOrError
|
|
2298
|
+
local cleared = 0
|
|
2299
|
+
local errors = {}
|
|
2300
|
+
for key, entry in breakpoints do
|
|
2301
|
+
local instance = getInstanceByPath(entry.script_path)
|
|
2302
|
+
if not instance or not instance:IsA("LuaSourceContainer") then
|
|
2303
|
+
breakpoints[key] = nil
|
|
2304
|
+
cleared += 1
|
|
2305
|
+
continue
|
|
2306
|
+
end
|
|
2307
|
+
local ok, removedOrError = pcall(function()
|
|
2308
|
+
return service:RemoveBreakpoint(instance, entry.line)
|
|
2309
|
+
end)
|
|
2310
|
+
if ok then
|
|
2311
|
+
breakpoints[key] = nil
|
|
2312
|
+
cleared += 1
|
|
2313
|
+
else
|
|
2314
|
+
local _arg0 = {
|
|
2315
|
+
script_path = entry.script_path,
|
|
2316
|
+
line = entry.line,
|
|
2317
|
+
error = tostring(removedOrError),
|
|
2318
|
+
}
|
|
2319
|
+
table.insert(errors, _arg0)
|
|
2320
|
+
end
|
|
2321
|
+
end
|
|
2322
|
+
if #errors > 0 then
|
|
2323
|
+
return {
|
|
2324
|
+
ok = false,
|
|
2325
|
+
cleared = cleared,
|
|
2326
|
+
errors = errors,
|
|
2327
|
+
}
|
|
2328
|
+
end
|
|
2329
|
+
return attachPersistenceWarning({
|
|
2330
|
+
ok = true,
|
|
2331
|
+
cleared = cleared,
|
|
2332
|
+
}, persistRegistry(scope))
|
|
2333
|
+
end
|
|
2334
|
+
local function clearAllBreakpoints(requestData)
|
|
2335
|
+
local scope = loadRegistry(requestData)
|
|
2336
|
+
local serviceOrError = getService()
|
|
2337
|
+
local _value = serviceOrError.IsA
|
|
2338
|
+
if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
|
|
2339
|
+
return serviceOrError
|
|
2340
|
+
end
|
|
2341
|
+
local service = serviceOrError
|
|
2342
|
+
-- ▼ ReadonlyMap.size ▼
|
|
2343
|
+
local _size = 0
|
|
2344
|
+
for _ in breakpoints do
|
|
2345
|
+
_size += 1
|
|
2346
|
+
end
|
|
2347
|
+
-- ▲ ReadonlyMap.size ▲
|
|
2348
|
+
local managedCount = _size
|
|
2349
|
+
local ok, err = pcall(function()
|
|
2350
|
+
return service:ClearBreakpoints()
|
|
2351
|
+
end)
|
|
2352
|
+
if not ok then
|
|
2353
|
+
return operationError("clear_breakpoints_failed", "ScriptDebuggerService:ClearBreakpoints", err)
|
|
2354
|
+
end
|
|
2355
|
+
table.clear(breakpoints)
|
|
2356
|
+
return attachPersistenceWarning({
|
|
2357
|
+
ok = true,
|
|
2358
|
+
cleared_managed = managedCount,
|
|
2359
|
+
}, persistRegistry(scope))
|
|
2360
|
+
end
|
|
2361
|
+
local function clearBreakpoints(requestData)
|
|
2362
|
+
if requestData.clear_all == true then
|
|
2363
|
+
return clearAllBreakpoints(requestData)
|
|
2364
|
+
end
|
|
2365
|
+
return clearManagedBreakpoints(requestData)
|
|
2366
|
+
end
|
|
2367
|
+
local function breakpointsTool(requestData)
|
|
2368
|
+
local action = requestData.action
|
|
2369
|
+
if not (type(action) == "string") or action == "" then
|
|
2370
|
+
return {
|
|
2371
|
+
error = "invalid_args",
|
|
2372
|
+
message = "breakpoints requires action=set|remove|clear|list",
|
|
2373
|
+
}
|
|
2374
|
+
end
|
|
2375
|
+
repeat
|
|
2376
|
+
if action == "set" then
|
|
2377
|
+
return setBreakpoint(requestData)
|
|
2378
|
+
end
|
|
2379
|
+
if action == "remove" then
|
|
2380
|
+
return removeBreakpoint(requestData)
|
|
2381
|
+
end
|
|
2382
|
+
if action == "clear" then
|
|
2383
|
+
return clearBreakpoints(requestData)
|
|
2384
|
+
end
|
|
2385
|
+
if action == "list" then
|
|
2386
|
+
return listBreakpoints(requestData)
|
|
2387
|
+
end
|
|
2388
|
+
return {
|
|
2389
|
+
error = "unknown_action",
|
|
2390
|
+
message = `breakpoints action must be one of: set, remove, clear, list (got {action})`,
|
|
2391
|
+
}
|
|
2392
|
+
until true
|
|
2393
|
+
end
|
|
2394
|
+
return {
|
|
2395
|
+
breakpoints = breakpointsTool,
|
|
2396
|
+
init = init,
|
|
2397
|
+
}
|
|
2398
|
+
]]></string>
|
|
2399
|
+
</Properties>
|
|
2400
|
+
</Item>
|
|
2401
|
+
<Item class="ModuleScript" referent="8">
|
|
1860
2402
|
<Properties>
|
|
1861
2403
|
<string name="Name">BuildHandlers</string>
|
|
1862
2404
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -2421,7 +2963,7 @@ return {
|
|
|
2421
2963
|
]]></string>
|
|
2422
2964
|
</Properties>
|
|
2423
2965
|
</Item>
|
|
2424
|
-
<Item class="ModuleScript" referent="
|
|
2966
|
+
<Item class="ModuleScript" referent="9">
|
|
2425
2967
|
<Properties>
|
|
2426
2968
|
<string name="Name">CaptureHandlers</string>
|
|
2427
2969
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -2636,7 +3178,7 @@ return {
|
|
|
2636
3178
|
]]></string>
|
|
2637
3179
|
</Properties>
|
|
2638
3180
|
</Item>
|
|
2639
|
-
<Item class="ModuleScript" referent="
|
|
3181
|
+
<Item class="ModuleScript" referent="10">
|
|
2640
3182
|
<Properties>
|
|
2641
3183
|
<string name="Name">EvalRuntimeHandlers</string>
|
|
2642
3184
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -2790,7 +3332,7 @@ return {
|
|
|
2790
3332
|
]]></string>
|
|
2791
3333
|
</Properties>
|
|
2792
3334
|
</Item>
|
|
2793
|
-
<Item class="ModuleScript" referent="
|
|
3335
|
+
<Item class="ModuleScript" referent="11">
|
|
2794
3336
|
<Properties>
|
|
2795
3337
|
<string name="Name">InputHandlers</string>
|
|
2796
3338
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -2990,7 +3532,7 @@ return {
|
|
|
2990
3532
|
]]></string>
|
|
2991
3533
|
</Properties>
|
|
2992
3534
|
</Item>
|
|
2993
|
-
<Item class="ModuleScript" referent="
|
|
3535
|
+
<Item class="ModuleScript" referent="12">
|
|
2994
3536
|
<Properties>
|
|
2995
3537
|
<string name="Name">InstanceHandlers</string>
|
|
2996
3538
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3497,7 +4039,7 @@ return {
|
|
|
3497
4039
|
]]></string>
|
|
3498
4040
|
</Properties>
|
|
3499
4041
|
</Item>
|
|
3500
|
-
<Item class="ModuleScript" referent="
|
|
4042
|
+
<Item class="ModuleScript" referent="13">
|
|
3501
4043
|
<Properties>
|
|
3502
4044
|
<string name="Name">LogHandlers</string>
|
|
3503
4045
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3523,7 +4065,7 @@ return {
|
|
|
3523
4065
|
]]></string>
|
|
3524
4066
|
</Properties>
|
|
3525
4067
|
</Item>
|
|
3526
|
-
<Item class="ModuleScript" referent="
|
|
4068
|
+
<Item class="ModuleScript" referent="14">
|
|
3527
4069
|
<Properties>
|
|
3528
4070
|
<string name="Name">MemoryHandlers</string>
|
|
3529
4071
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3591,7 +4133,7 @@ return {
|
|
|
3591
4133
|
]]></string>
|
|
3592
4134
|
</Properties>
|
|
3593
4135
|
</Item>
|
|
3594
|
-
<Item class="ModuleScript" referent="
|
|
4136
|
+
<Item class="ModuleScript" referent="15">
|
|
3595
4137
|
<Properties>
|
|
3596
4138
|
<string name="Name">MetadataHandlers</string>
|
|
3597
4139
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -4130,7 +4672,7 @@ return {
|
|
|
4130
4672
|
]]></string>
|
|
4131
4673
|
</Properties>
|
|
4132
4674
|
</Item>
|
|
4133
|
-
<Item class="ModuleScript" referent="
|
|
4675
|
+
<Item class="ModuleScript" referent="16">
|
|
4134
4676
|
<Properties>
|
|
4135
4677
|
<string name="Name">PropertyHandlers</string>
|
|
4136
4678
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -4382,7 +4924,7 @@ return {
|
|
|
4382
4924
|
]]></string>
|
|
4383
4925
|
</Properties>
|
|
4384
4926
|
</Item>
|
|
4385
|
-
<Item class="ModuleScript" referent="
|
|
4927
|
+
<Item class="ModuleScript" referent="17">
|
|
4386
4928
|
<Properties>
|
|
4387
4929
|
<string name="Name">QueryHandlers</string>
|
|
4388
4930
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5347,64 +5889,6 @@ local function compareInstances(requestData)
|
|
|
5347
5889
|
onlyB = onlyB,
|
|
5348
5890
|
}
|
|
5349
5891
|
end
|
|
5350
|
-
local function getOutputLog(requestData)
|
|
5351
|
-
local _condition = (requestData.maxEntries)
|
|
5352
|
-
if _condition == nil then
|
|
5353
|
-
_condition = 100
|
|
5354
|
-
end
|
|
5355
|
-
local maxEntries = _condition
|
|
5356
|
-
local messageTypeFilter = requestData.messageType
|
|
5357
|
-
local success, result = pcall(function()
|
|
5358
|
-
local LogService = game:GetService("LogService")
|
|
5359
|
-
local history = LogService:GetLogHistory()
|
|
5360
|
-
local allEntries = {}
|
|
5361
|
-
for _, entry in history do
|
|
5362
|
-
local msgType = tostring(entry.messageType)
|
|
5363
|
-
local _condition_1 = messageTypeFilter
|
|
5364
|
-
if _condition_1 ~= "" and _condition_1 then
|
|
5365
|
-
_condition_1 = msgType ~= messageTypeFilter
|
|
5366
|
-
end
|
|
5367
|
-
if _condition_1 ~= "" and _condition_1 then
|
|
5368
|
-
continue
|
|
5369
|
-
end
|
|
5370
|
-
local _arg0 = {
|
|
5371
|
-
message = entry.message,
|
|
5372
|
-
messageType = msgType,
|
|
5373
|
-
timestamp = entry.timestamp,
|
|
5374
|
-
}
|
|
5375
|
-
table.insert(allEntries, _arg0)
|
|
5376
|
-
end
|
|
5377
|
-
local startIdx = math.max(0, #allEntries - maxEntries)
|
|
5378
|
-
local finalEntries = {}
|
|
5379
|
-
do
|
|
5380
|
-
local i = startIdx
|
|
5381
|
-
local _shouldIncrement = false
|
|
5382
|
-
while true do
|
|
5383
|
-
if _shouldIncrement then
|
|
5384
|
-
i += 1
|
|
5385
|
-
else
|
|
5386
|
-
_shouldIncrement = true
|
|
5387
|
-
end
|
|
5388
|
-
if not (i < #allEntries) then
|
|
5389
|
-
break
|
|
5390
|
-
end
|
|
5391
|
-
local _arg0 = allEntries[i + 1]
|
|
5392
|
-
table.insert(finalEntries, _arg0)
|
|
5393
|
-
end
|
|
5394
|
-
end
|
|
5395
|
-
return {
|
|
5396
|
-
entries = finalEntries,
|
|
5397
|
-
count = #finalEntries,
|
|
5398
|
-
totalAvailable = #allEntries,
|
|
5399
|
-
}
|
|
5400
|
-
end)
|
|
5401
|
-
if success then
|
|
5402
|
-
return result
|
|
5403
|
-
end
|
|
5404
|
-
return {
|
|
5405
|
-
error = `Failed to get output log: {result}`,
|
|
5406
|
-
}
|
|
5407
|
-
end
|
|
5408
5892
|
return {
|
|
5409
5893
|
getFileTree = getFileTree,
|
|
5410
5894
|
searchFiles = searchFiles,
|
|
@@ -5419,12 +5903,11 @@ return {
|
|
|
5419
5903
|
grepScripts = grepScripts,
|
|
5420
5904
|
getDescendants = getDescendants,
|
|
5421
5905
|
compareInstances = compareInstances,
|
|
5422
|
-
getOutputLog = getOutputLog,
|
|
5423
5906
|
}
|
|
5424
5907
|
]]></string>
|
|
5425
5908
|
</Properties>
|
|
5426
5909
|
</Item>
|
|
5427
|
-
<Item class="ModuleScript" referent="
|
|
5910
|
+
<Item class="ModuleScript" referent="18">
|
|
5428
5911
|
<Properties>
|
|
5429
5912
|
<string name="Name">SceneAnalysisHandlers</string>
|
|
5430
5913
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5673,7 +6156,7 @@ return {
|
|
|
5673
6156
|
]]></string>
|
|
5674
6157
|
</Properties>
|
|
5675
6158
|
</Item>
|
|
5676
|
-
<Item class="ModuleScript" referent="
|
|
6159
|
+
<Item class="ModuleScript" referent="19">
|
|
5677
6160
|
<Properties>
|
|
5678
6161
|
<string name="Name">ScriptHandlers</string>
|
|
5679
6162
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5859,6 +6342,9 @@ local function setScriptSource(requestData)
|
|
|
5859
6342
|
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
5860
6343
|
return sourceToSet
|
|
5861
6344
|
end)
|
|
6345
|
+
if readScriptSource(instance) ~= sourceToSet then
|
|
6346
|
+
error("UpdateSourceAsync completed without updating the script source")
|
|
6347
|
+
end
|
|
5862
6348
|
return {
|
|
5863
6349
|
success = true,
|
|
5864
6350
|
instancePath = instancePath,
|
|
@@ -6335,41 +6821,504 @@ local function findAndReplaceInScripts(requestData)
|
|
|
6335
6821
|
table.insert(changes, _arg0)
|
|
6336
6822
|
end
|
|
6337
6823
|
end
|
|
6338
|
-
for _, child in instance:GetChildren() do
|
|
6339
|
-
if hitLimit then
|
|
6340
|
-
return nil
|
|
6341
|
-
end
|
|
6342
|
-
processInstance(child)
|
|
6824
|
+
for _, child in instance:GetChildren() do
|
|
6825
|
+
if hitLimit then
|
|
6826
|
+
return nil
|
|
6827
|
+
end
|
|
6828
|
+
processInstance(child)
|
|
6829
|
+
end
|
|
6830
|
+
end
|
|
6831
|
+
processInstance(startInstance)
|
|
6832
|
+
if recordingId ~= nil then
|
|
6833
|
+
finishRecording(recordingId, #changes > 0)
|
|
6834
|
+
end
|
|
6835
|
+
return {
|
|
6836
|
+
success = true,
|
|
6837
|
+
dryRun = dryRun,
|
|
6838
|
+
pattern = searchPattern,
|
|
6839
|
+
replacement = replacement,
|
|
6840
|
+
totalReplacements = totalReplacements,
|
|
6841
|
+
scriptsSearched = scriptsSearched,
|
|
6842
|
+
scriptsModified = #changes,
|
|
6843
|
+
changes = changes,
|
|
6844
|
+
truncated = hitLimit,
|
|
6845
|
+
}
|
|
6846
|
+
end
|
|
6847
|
+
return {
|
|
6848
|
+
getScriptSource = getScriptSource,
|
|
6849
|
+
setScriptSource = setScriptSource,
|
|
6850
|
+
editScriptLines = editScriptLines,
|
|
6851
|
+
insertScriptLines = insertScriptLines,
|
|
6852
|
+
deleteScriptLines = deleteScriptLines,
|
|
6853
|
+
findAndReplaceInScripts = findAndReplaceInScripts,
|
|
6854
|
+
}
|
|
6855
|
+
]]></string>
|
|
6856
|
+
</Properties>
|
|
6857
|
+
</Item>
|
|
6858
|
+
<Item class="ModuleScript" referent="20">
|
|
6859
|
+
<Properties>
|
|
6860
|
+
<string name="Name">ScriptProfilerHandlers</string>
|
|
6861
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
6862
|
+
local HttpService = game:GetService("HttpService")
|
|
6863
|
+
local Players = game:GetService("Players")
|
|
6864
|
+
local RunService = game:GetService("RunService")
|
|
6865
|
+
local DEFAULT_DURATION_MS = 1000
|
|
6866
|
+
local MIN_DURATION_MS = 100
|
|
6867
|
+
local MAX_DURATION_MS = 15000
|
|
6868
|
+
local DEFAULT_FREQUENCY = 1000
|
|
6869
|
+
local DEFAULT_MAX_FUNCTIONS = 20
|
|
6870
|
+
local function getProfilerService()
|
|
6871
|
+
local provider = game
|
|
6872
|
+
local ok, service = pcall(function()
|
|
6873
|
+
return provider:GetService("ScriptProfilerService")
|
|
6874
|
+
end)
|
|
6875
|
+
if not ok or not service then
|
|
6876
|
+
return {
|
|
6877
|
+
error = "script_profiler_unavailable",
|
|
6878
|
+
message = `ScriptProfilerService is unavailable: {tostring(service)}`,
|
|
6879
|
+
}
|
|
6880
|
+
end
|
|
6881
|
+
return service
|
|
6882
|
+
end
|
|
6883
|
+
local function normalizeDurationMs(value)
|
|
6884
|
+
local _value = value
|
|
6885
|
+
if not (type(_value) == "number") then
|
|
6886
|
+
return DEFAULT_DURATION_MS
|
|
6887
|
+
end
|
|
6888
|
+
return math.clamp(math.floor(value), MIN_DURATION_MS, MAX_DURATION_MS)
|
|
6889
|
+
end
|
|
6890
|
+
local function normalizeFrequency(value)
|
|
6891
|
+
local _value = value
|
|
6892
|
+
if not (type(_value) == "number") then
|
|
6893
|
+
return DEFAULT_FREQUENCY
|
|
6894
|
+
end
|
|
6895
|
+
return math.clamp(math.floor(value), 1, 10000)
|
|
6896
|
+
end
|
|
6897
|
+
local function normalizeMaxFunctions(value)
|
|
6898
|
+
local _value = value
|
|
6899
|
+
if not (type(_value) == "number") then
|
|
6900
|
+
return DEFAULT_MAX_FUNCTIONS
|
|
6901
|
+
end
|
|
6902
|
+
return math.clamp(math.floor(value), 1, 100)
|
|
6903
|
+
end
|
|
6904
|
+
local function normalizeMinTotalUs(requestData)
|
|
6905
|
+
local value = requestData.min_total_us
|
|
6906
|
+
if type(value) == "number" then
|
|
6907
|
+
return math.max(0, value)
|
|
6908
|
+
end
|
|
6909
|
+
return 0
|
|
6910
|
+
end
|
|
6911
|
+
local function stringContains(haystack, needle)
|
|
6912
|
+
return (string.find(string.lower(haystack), string.lower(needle), 1, true)) ~= nil
|
|
6913
|
+
end
|
|
6914
|
+
local function localPlayer()
|
|
6915
|
+
local player = Players.LocalPlayer
|
|
6916
|
+
local started = tick()
|
|
6917
|
+
while not player and tick() - started < 5 do
|
|
6918
|
+
task.wait(0.05)
|
|
6919
|
+
player = Players.LocalPlayer
|
|
6920
|
+
end
|
|
6921
|
+
return player
|
|
6922
|
+
end
|
|
6923
|
+
local function functionDisplayName(func)
|
|
6924
|
+
local _name = func.Name
|
|
6925
|
+
local _condition = type(_name) == "string"
|
|
6926
|
+
if _condition then
|
|
6927
|
+
_condition = func.Name ~= ""
|
|
6928
|
+
end
|
|
6929
|
+
if _condition then
|
|
6930
|
+
return func.Name
|
|
6931
|
+
end
|
|
6932
|
+
local _source = func.Source
|
|
6933
|
+
local _condition_1 = type(_source) == "string"
|
|
6934
|
+
if _condition_1 then
|
|
6935
|
+
_condition_1 = func.Source ~= ""
|
|
6936
|
+
end
|
|
6937
|
+
if _condition_1 then
|
|
6938
|
+
local _line = func.Line
|
|
6939
|
+
local _condition_2 = type(_line) == "number"
|
|
6940
|
+
if _condition_2 then
|
|
6941
|
+
_condition_2 = func.Line > 0
|
|
6942
|
+
end
|
|
6943
|
+
if _condition_2 then
|
|
6944
|
+
return `{func.Source}:{func.Line}`
|
|
6945
|
+
end
|
|
6946
|
+
return func.Source
|
|
6947
|
+
end
|
|
6948
|
+
return "<anonymous>"
|
|
6949
|
+
end
|
|
6950
|
+
local function flagsOf(func)
|
|
6951
|
+
local _flags = func.Flags
|
|
6952
|
+
return if type(_flags) == "number" then func.Flags else 0
|
|
6953
|
+
end
|
|
6954
|
+
local function isNativeFunction(func)
|
|
6955
|
+
return bit32.band(flagsOf(func), 1) ~= 0
|
|
6956
|
+
end
|
|
6957
|
+
local function isPluginFunction(func)
|
|
6958
|
+
if bit32.band(flagsOf(func), 2) ~= 0 then
|
|
6959
|
+
return true
|
|
6960
|
+
end
|
|
6961
|
+
local _source = func.Source
|
|
6962
|
+
local _condition = type(_source) == "string"
|
|
6963
|
+
if _condition then
|
|
6964
|
+
_condition = (string.find(func.Source, "MCPPlugin", 1, true)) ~= nil
|
|
6965
|
+
end
|
|
6966
|
+
return _condition
|
|
6967
|
+
end
|
|
6968
|
+
local function isDebugLabel(func, filter)
|
|
6969
|
+
if filter == nil then
|
|
6970
|
+
return false
|
|
6971
|
+
end
|
|
6972
|
+
local _name = func.Name
|
|
6973
|
+
local _condition = not (type(_name) == "string")
|
|
6974
|
+
if not _condition then
|
|
6975
|
+
_condition = func.Name == ""
|
|
6976
|
+
end
|
|
6977
|
+
if _condition then
|
|
6978
|
+
return false
|
|
6979
|
+
end
|
|
6980
|
+
local _source = func.Source
|
|
6981
|
+
local _condition_1 = not (type(_source) == "string")
|
|
6982
|
+
if not _condition_1 then
|
|
6983
|
+
_condition_1 = func.Source == "" or func.Source == "[C]" or func.Source == "GC"
|
|
6984
|
+
end
|
|
6985
|
+
if _condition_1 then
|
|
6986
|
+
return false
|
|
6987
|
+
end
|
|
6988
|
+
if func.Line ~= nil and func.Line ~= 0 then
|
|
6989
|
+
return false
|
|
6990
|
+
end
|
|
6991
|
+
if isNativeFunction(func) or isPluginFunction(func) then
|
|
6992
|
+
return false
|
|
6993
|
+
end
|
|
6994
|
+
return stringContains(func.Name, filter) or stringContains(func.Source, filter)
|
|
6995
|
+
end
|
|
6996
|
+
local function pctOfCapture(row, durationMs)
|
|
6997
|
+
local captureUs = durationMs * 1000
|
|
6998
|
+
if captureUs <= 0 then
|
|
6999
|
+
return nil
|
|
7000
|
+
end
|
|
7001
|
+
return math.floor((row.total_us / captureUs) * 10000 + 0.5) / 100
|
|
7002
|
+
end
|
|
7003
|
+
local function compactFunction(row, rank, durationMs)
|
|
7004
|
+
local out = {
|
|
7005
|
+
rank = rank,
|
|
7006
|
+
function_index = row.function_index,
|
|
7007
|
+
name = row.name,
|
|
7008
|
+
total_us = math.floor(row.total_us + 0.5),
|
|
7009
|
+
}
|
|
7010
|
+
local pct = pctOfCapture(row, durationMs)
|
|
7011
|
+
if pct ~= nil then
|
|
7012
|
+
out.pct_of_capture = pct
|
|
7013
|
+
end
|
|
7014
|
+
if row.source ~= nil then
|
|
7015
|
+
out.source = row.source
|
|
7016
|
+
end
|
|
7017
|
+
if row.line ~= nil then
|
|
7018
|
+
out.line = row.line
|
|
7019
|
+
end
|
|
7020
|
+
if row.is_native == true then
|
|
7021
|
+
out.is_native = true
|
|
7022
|
+
end
|
|
7023
|
+
if row.is_plugin == true then
|
|
7024
|
+
out.is_plugin = true
|
|
7025
|
+
end
|
|
7026
|
+
if row.is_debug_label == true then
|
|
7027
|
+
out.is_debug_label = true
|
|
7028
|
+
end
|
|
7029
|
+
return out
|
|
7030
|
+
end
|
|
7031
|
+
local function summarizeProfile(rawJson, profile, requestData, durationMs, frequency, eventPlayerName)
|
|
7032
|
+
local _functions = profile.Functions
|
|
7033
|
+
local funcs = if type(_functions) == "table" then profile.Functions else {}
|
|
7034
|
+
local _nodes = profile.Nodes
|
|
7035
|
+
local nodes = if type(_nodes) == "table" then profile.Nodes else {}
|
|
7036
|
+
local _categories = profile.Categories
|
|
7037
|
+
local categories = if type(_categories) == "table" then profile.Categories else {}
|
|
7038
|
+
local maxFunctions = normalizeMaxFunctions(requestData.max_functions)
|
|
7039
|
+
local minTotalUs = normalizeMinTotalUs(requestData)
|
|
7040
|
+
local includeNative = requestData.include_native == true
|
|
7041
|
+
local includePlugin = requestData.include_plugin == true
|
|
7042
|
+
local _filter = requestData.filter
|
|
7043
|
+
local _condition = type(_filter) == "string"
|
|
7044
|
+
if _condition then
|
|
7045
|
+
_condition = requestData.filter ~= ""
|
|
7046
|
+
end
|
|
7047
|
+
local filter = if _condition then requestData.filter else nil
|
|
7048
|
+
local rows = {}
|
|
7049
|
+
local debugRows = {}
|
|
7050
|
+
local omittedNative = 0
|
|
7051
|
+
local omittedPlugin = 0
|
|
7052
|
+
local omittedBelowThreshold = 0
|
|
7053
|
+
local omittedByFilter = 0
|
|
7054
|
+
for i = 0, #funcs - 1 do
|
|
7055
|
+
local func = funcs[i + 1]
|
|
7056
|
+
if not (type(func) == "table") then
|
|
7057
|
+
continue
|
|
7058
|
+
end
|
|
7059
|
+
local info = func
|
|
7060
|
+
local _totalDuration = info.TotalDuration
|
|
7061
|
+
local totalUs = if type(_totalDuration) == "number" then info.TotalDuration else 0
|
|
7062
|
+
local name = functionDisplayName(info)
|
|
7063
|
+
local _object = {
|
|
7064
|
+
function_index = i + 1,
|
|
7065
|
+
name = name,
|
|
7066
|
+
}
|
|
7067
|
+
local _left = "source"
|
|
7068
|
+
local _source = info.Source
|
|
7069
|
+
_object[_left] = if type(_source) == "string" then info.Source else nil
|
|
7070
|
+
local _left_1 = "line"
|
|
7071
|
+
local _line = info.Line
|
|
7072
|
+
_object[_left_1] = if type(_line) == "number" then info.Line else nil
|
|
7073
|
+
_object.total_us = totalUs
|
|
7074
|
+
_object.is_native = if isNativeFunction(info) then true else nil
|
|
7075
|
+
_object.is_plugin = if isPluginFunction(info) then true else nil
|
|
7076
|
+
_object.is_debug_label = if isDebugLabel(info, filter) then true else nil
|
|
7077
|
+
local row = _object
|
|
7078
|
+
if not includeNative and row.is_native == true then
|
|
7079
|
+
omittedNative += 1
|
|
7080
|
+
continue
|
|
7081
|
+
end
|
|
7082
|
+
if not includePlugin and row.is_plugin == true then
|
|
7083
|
+
omittedPlugin += 1
|
|
7084
|
+
continue
|
|
7085
|
+
end
|
|
7086
|
+
if totalUs < minTotalUs then
|
|
7087
|
+
omittedBelowThreshold += 1
|
|
7088
|
+
continue
|
|
7089
|
+
end
|
|
7090
|
+
if filter ~= nil then
|
|
7091
|
+
local _exp = row.name
|
|
7092
|
+
local _condition_1 = row.source
|
|
7093
|
+
if _condition_1 == nil then
|
|
7094
|
+
_condition_1 = ""
|
|
7095
|
+
end
|
|
7096
|
+
local text = `{_exp} {_condition_1}`
|
|
7097
|
+
if not stringContains(text, filter) then
|
|
7098
|
+
omittedByFilter += 1
|
|
7099
|
+
continue
|
|
7100
|
+
end
|
|
7101
|
+
end
|
|
7102
|
+
table.insert(rows, row)
|
|
7103
|
+
if row.is_debug_label == true then
|
|
7104
|
+
table.insert(debugRows, row)
|
|
7105
|
+
end
|
|
7106
|
+
end
|
|
7107
|
+
table.sort(rows, function(a, b)
|
|
7108
|
+
return a.total_us > b.total_us
|
|
7109
|
+
end)
|
|
7110
|
+
table.sort(debugRows, function(a, b)
|
|
7111
|
+
return a.total_us > b.total_us
|
|
7112
|
+
end)
|
|
7113
|
+
local topFunctions = {}
|
|
7114
|
+
do
|
|
7115
|
+
local i = 0
|
|
7116
|
+
local _shouldIncrement = false
|
|
7117
|
+
while true do
|
|
7118
|
+
if _shouldIncrement then
|
|
7119
|
+
i += 1
|
|
7120
|
+
else
|
|
7121
|
+
_shouldIncrement = true
|
|
7122
|
+
end
|
|
7123
|
+
if not (i < math.min(maxFunctions, #rows)) then
|
|
7124
|
+
break
|
|
7125
|
+
end
|
|
7126
|
+
local _arg0 = compactFunction(rows[i + 1], i + 1, durationMs)
|
|
7127
|
+
table.insert(topFunctions, _arg0)
|
|
7128
|
+
end
|
|
7129
|
+
end
|
|
7130
|
+
local debugLabels = {}
|
|
7131
|
+
do
|
|
7132
|
+
local i = 0
|
|
7133
|
+
local _shouldIncrement = false
|
|
7134
|
+
while true do
|
|
7135
|
+
if _shouldIncrement then
|
|
7136
|
+
i += 1
|
|
7137
|
+
else
|
|
7138
|
+
_shouldIncrement = true
|
|
7139
|
+
end
|
|
7140
|
+
if not (i < math.min(maxFunctions, #debugRows)) then
|
|
7141
|
+
break
|
|
7142
|
+
end
|
|
7143
|
+
local _arg0 = compactFunction(debugRows[i + 1], i + 1, durationMs)
|
|
7144
|
+
table.insert(debugLabels, _arg0)
|
|
7145
|
+
end
|
|
7146
|
+
end
|
|
7147
|
+
local categoryNames = {}
|
|
7148
|
+
for i = 0, #categories - 1 do
|
|
7149
|
+
local category = categories[i + 1]
|
|
7150
|
+
if type(category) == "table" then
|
|
7151
|
+
local name = category.Name
|
|
7152
|
+
if type(name) == "string" then
|
|
7153
|
+
table.insert(categoryNames, name)
|
|
7154
|
+
end
|
|
7155
|
+
end
|
|
7156
|
+
end
|
|
7157
|
+
local omitted = {}
|
|
7158
|
+
local hasOmitted = false
|
|
7159
|
+
if omittedNative > 0 then
|
|
7160
|
+
omitted.native = omittedNative
|
|
7161
|
+
hasOmitted = true
|
|
7162
|
+
end
|
|
7163
|
+
if omittedPlugin > 0 then
|
|
7164
|
+
omitted.plugin = omittedPlugin
|
|
7165
|
+
hasOmitted = true
|
|
7166
|
+
end
|
|
7167
|
+
if omittedBelowThreshold > 0 then
|
|
7168
|
+
omitted.below_min_total_us = omittedBelowThreshold
|
|
7169
|
+
hasOmitted = true
|
|
7170
|
+
end
|
|
7171
|
+
if omittedByFilter > 0 then
|
|
7172
|
+
omitted.filtered_out = omittedByFilter
|
|
7173
|
+
hasOmitted = true
|
|
7174
|
+
end
|
|
7175
|
+
local _object = {
|
|
7176
|
+
ok = true,
|
|
7177
|
+
duration_ms = durationMs,
|
|
7178
|
+
frequency = frequency,
|
|
7179
|
+
}
|
|
7180
|
+
local _left = "applied"
|
|
7181
|
+
local _object_1 = {}
|
|
7182
|
+
local _left_1 = "filter"
|
|
7183
|
+
local _condition_1 = filter
|
|
7184
|
+
if _condition_1 == nil then
|
|
7185
|
+
_condition_1 = nil
|
|
7186
|
+
end
|
|
7187
|
+
_object_1[_left_1] = _condition_1
|
|
7188
|
+
_object_1.min_total_us = minTotalUs
|
|
7189
|
+
_object_1.include_native = includeNative
|
|
7190
|
+
_object_1.include_plugin = includePlugin
|
|
7191
|
+
_object_1.max_functions = maxFunctions
|
|
7192
|
+
_object_1.sort = "total_us_desc"
|
|
7193
|
+
_object[_left] = _object_1
|
|
7194
|
+
_object.json_bytes = #rawJson
|
|
7195
|
+
_object.counts = {
|
|
7196
|
+
categories = #categories,
|
|
7197
|
+
nodes = #nodes,
|
|
7198
|
+
functions = #funcs,
|
|
7199
|
+
}
|
|
7200
|
+
_object.top_functions = topFunctions
|
|
7201
|
+
_object.debug_labels = debugLabels
|
|
7202
|
+
local result = _object
|
|
7203
|
+
if #categoryNames > 0 then
|
|
7204
|
+
result.categories = categoryNames
|
|
7205
|
+
end
|
|
7206
|
+
if hasOmitted then
|
|
7207
|
+
result.omitted = omitted
|
|
7208
|
+
end
|
|
7209
|
+
if profile.Version ~= nil then
|
|
7210
|
+
result.version = profile.Version
|
|
7211
|
+
end
|
|
7212
|
+
if profile.SessionStartTime ~= nil or profile.SessionEndTime ~= nil then
|
|
7213
|
+
result.session = {
|
|
7214
|
+
start_time = profile.SessionStartTime,
|
|
7215
|
+
end_time = profile.SessionEndTime,
|
|
7216
|
+
}
|
|
7217
|
+
end
|
|
7218
|
+
if eventPlayerName ~= nil then
|
|
7219
|
+
result.player = eventPlayerName
|
|
7220
|
+
end
|
|
7221
|
+
if requestData.__mcp_include_raw_json == true then
|
|
7222
|
+
result.raw_json = rawJson
|
|
7223
|
+
end
|
|
7224
|
+
return result
|
|
7225
|
+
end
|
|
7226
|
+
local function captureScriptProfiler(requestData)
|
|
7227
|
+
if not RunService:IsRunning() then
|
|
7228
|
+
return {
|
|
7229
|
+
error = "runtime_target_required",
|
|
7230
|
+
message = 'Script profiler capture requires a running playtest target such as target=\"server\" or target=\"client-1\".',
|
|
7231
|
+
}
|
|
7232
|
+
end
|
|
7233
|
+
local serviceOrError = getProfilerService()
|
|
7234
|
+
local _value = serviceOrError.IsA
|
|
7235
|
+
if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
|
|
7236
|
+
return serviceOrError
|
|
7237
|
+
end
|
|
7238
|
+
local service = serviceOrError
|
|
7239
|
+
local durationMs = normalizeDurationMs(requestData.duration_ms)
|
|
7240
|
+
local frequency = normalizeFrequency(requestData.frequency)
|
|
7241
|
+
local isServer = RunService:IsServer()
|
|
7242
|
+
local isClient = RunService:IsClient() and not isServer
|
|
7243
|
+
local player = if isClient then localPlayer() else nil
|
|
7244
|
+
if not isServer and not player then
|
|
7245
|
+
return {
|
|
7246
|
+
error = "client_player_unavailable",
|
|
7247
|
+
message = "Could not resolve Players.LocalPlayer for client profiling.",
|
|
7248
|
+
}
|
|
7249
|
+
end
|
|
7250
|
+
local rawJson
|
|
7251
|
+
local eventPlayerName
|
|
7252
|
+
local connection = service.OnNewData:Connect(function(playerArg, jsonString)
|
|
7253
|
+
if rawJson ~= nil then
|
|
7254
|
+
return nil
|
|
7255
|
+
end
|
|
7256
|
+
rawJson = jsonString
|
|
7257
|
+
if playerArg then
|
|
7258
|
+
eventPlayerName = playerArg.Name
|
|
7259
|
+
end
|
|
7260
|
+
end)
|
|
7261
|
+
local startOk, startErr = pcall(function()
|
|
7262
|
+
if isServer then
|
|
7263
|
+
service:ServerStart(frequency)
|
|
7264
|
+
else
|
|
7265
|
+
service:ClientStart(player, frequency)
|
|
7266
|
+
end
|
|
7267
|
+
end)
|
|
7268
|
+
if not startOk then
|
|
7269
|
+
connection:Disconnect()
|
|
7270
|
+
return {
|
|
7271
|
+
error = "script_profiler_start_failed",
|
|
7272
|
+
message = tostring(startErr),
|
|
7273
|
+
}
|
|
7274
|
+
end
|
|
7275
|
+
task.wait(durationMs / 1000)
|
|
7276
|
+
local stopOk, stopErr = pcall(function()
|
|
7277
|
+
if isServer then
|
|
7278
|
+
service:ServerStop()
|
|
7279
|
+
service:ServerRequestData()
|
|
7280
|
+
else
|
|
7281
|
+
service:ClientStop(player)
|
|
7282
|
+
service:ClientRequestData(player)
|
|
6343
7283
|
end
|
|
7284
|
+
end)
|
|
7285
|
+
if not stopOk then
|
|
7286
|
+
connection:Disconnect()
|
|
7287
|
+
return {
|
|
7288
|
+
error = "script_profiler_stop_failed",
|
|
7289
|
+
message = tostring(stopErr),
|
|
7290
|
+
}
|
|
6344
7291
|
end
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
7292
|
+
local requestedAt = tick()
|
|
7293
|
+
while rawJson == nil and tick() - requestedAt < 5 do
|
|
7294
|
+
task.wait(0.05)
|
|
6348
7295
|
end
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
7296
|
+
connection:Disconnect()
|
|
7297
|
+
if rawJson == nil then
|
|
7298
|
+
return {
|
|
7299
|
+
error = "script_profiler_data_timeout",
|
|
7300
|
+
message = "ScriptProfilerService did not emit OnNewData after requesting profiler data.",
|
|
7301
|
+
}
|
|
7302
|
+
end
|
|
7303
|
+
local decodeOk, decoded = pcall(function()
|
|
7304
|
+
return HttpService:JSONDecode(rawJson)
|
|
7305
|
+
end)
|
|
7306
|
+
if not decodeOk then
|
|
7307
|
+
return {
|
|
7308
|
+
error = "script_profiler_decode_failed",
|
|
7309
|
+
message = tostring(decoded),
|
|
7310
|
+
json_bytes = #rawJson,
|
|
7311
|
+
}
|
|
7312
|
+
end
|
|
7313
|
+
return summarizeProfile(rawJson, decoded, requestData, durationMs, frequency, eventPlayerName)
|
|
6360
7314
|
end
|
|
6361
7315
|
return {
|
|
6362
|
-
|
|
6363
|
-
setScriptSource = setScriptSource,
|
|
6364
|
-
editScriptLines = editScriptLines,
|
|
6365
|
-
insertScriptLines = insertScriptLines,
|
|
6366
|
-
deleteScriptLines = deleteScriptLines,
|
|
6367
|
-
findAndReplaceInScripts = findAndReplaceInScripts,
|
|
7316
|
+
captureScriptProfiler = captureScriptProfiler,
|
|
6368
7317
|
}
|
|
6369
7318
|
]]></string>
|
|
6370
7319
|
</Properties>
|
|
6371
7320
|
</Item>
|
|
6372
|
-
<Item class="ModuleScript" referent="
|
|
7321
|
+
<Item class="ModuleScript" referent="21">
|
|
6373
7322
|
<Properties>
|
|
6374
7323
|
<string name="Name">SerializationHandlers</string>
|
|
6375
7324
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6555,33 +7504,18 @@ return {
|
|
|
6555
7504
|
]]></string>
|
|
6556
7505
|
</Properties>
|
|
6557
7506
|
</Item>
|
|
6558
|
-
<Item class="ModuleScript" referent="
|
|
7507
|
+
<Item class="ModuleScript" referent="22">
|
|
6559
7508
|
<Properties>
|
|
6560
7509
|
<string name="Name">TestHandlers</string>
|
|
6561
7510
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
6562
7511
|
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
6563
7512
|
local _services = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
6564
7513
|
local HttpService = _services.HttpService
|
|
6565
|
-
local LogService = _services.LogService
|
|
6566
7514
|
local Players = _services.Players
|
|
6567
7515
|
local RunService = _services.RunService
|
|
6568
7516
|
local StopPlayMonitor = TS.import(script, script.Parent.Parent, "StopPlayMonitor")
|
|
6569
7517
|
local StudioTestService = game:GetService("StudioTestService")
|
|
6570
|
-
local ServerScriptService = game:GetService("ServerScriptService")
|
|
6571
|
-
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
6572
|
-
-- NAV_SIGNAL flows from the edit DM to the play-server DM via the injected
|
|
6573
|
-
-- __MCP_CommandListener Script + LogService.MessageOut. Stop signaling moved
|
|
6574
|
-
-- off this path entirely (see StopPlayMonitor) because cross-DM MessageOut
|
|
6575
|
-
-- reflection from edit -> play-server does not work in practice.
|
|
6576
|
-
local NAV_SIGNAL = "__MCP_NAV__"
|
|
6577
|
-
local NAV_RESULT = "__MCP_NAV_RESULT__"
|
|
6578
7518
|
local testRunning = false
|
|
6579
|
-
local outputBuffer = {}
|
|
6580
|
-
local logConnection
|
|
6581
|
-
local testResult
|
|
6582
|
-
local testError
|
|
6583
|
-
local stopListenerScript
|
|
6584
|
-
local navResultCallback
|
|
6585
7519
|
local multiplayerState = {
|
|
6586
7520
|
phase = "idle",
|
|
6587
7521
|
}
|
|
@@ -6639,94 +7573,6 @@ local function normalizeNumPlayers(value)
|
|
|
6639
7573
|
end
|
|
6640
7574
|
return n
|
|
6641
7575
|
end
|
|
6642
|
-
local function buildCommandListenerSource()
|
|
6643
|
-
return `local LogService = game:GetService("LogService")\
|
|
6644
|
-
local PathfindingService = game:GetService("PathfindingService")\
|
|
6645
|
-
local Players = game:GetService("Players")\
|
|
6646
|
-
local HttpService = game:GetService("HttpService")\
|
|
6647
|
-
local NAV_SIG = "{NAV_SIGNAL}"\
|
|
6648
|
-
local NAV_RES = "{NAV_RESULT}"\
|
|
6649
|
-
LogService.MessageOut:Connect(function(msg)\
|
|
6650
|
-
if string.sub(msg, 1, #NAV_SIG + 1) == NAV_SIG .. ":" then\
|
|
6651
|
-
local json = string.sub(msg, #NAV_SIG + 2)\
|
|
6652
|
-
task.spawn(function()\
|
|
6653
|
-
local ok, d = pcall(function() return HttpService:JSONDecode(json) end)\
|
|
6654
|
-
if not ok or not d then\
|
|
6655
|
-
print(NAV_RES .. ':\{"success":false,"error":"parse_error"\}')\
|
|
6656
|
-
return\
|
|
6657
|
-
end\
|
|
6658
|
-
local ps = Players:GetPlayers()\
|
|
6659
|
-
if #ps == 0 then\
|
|
6660
|
-
print(NAV_RES .. ':\{"success":false,"error":"no_players"\}')\
|
|
6661
|
-
return\
|
|
6662
|
-
end\
|
|
6663
|
-
local char = ps[1].Character or ps[1].CharacterAdded:Wait()\
|
|
6664
|
-
local hum = char:FindFirstChildOfClass("Humanoid")\
|
|
6665
|
-
local root = char:FindFirstChild("HumanoidRootPart")\
|
|
6666
|
-
if not hum or not root then\
|
|
6667
|
-
print(NAV_RES .. ':\{"success":false,"error":"no_humanoid"\}')\
|
|
6668
|
-
return\
|
|
6669
|
-
end\
|
|
6670
|
-
local target\
|
|
6671
|
-
if d.instancePath then\
|
|
6672
|
-
local parts = string.split(d.instancePath, ".")\
|
|
6673
|
-
local cur = game\
|
|
6674
|
-
for i = 2, #parts do\
|
|
6675
|
-
cur = cur:FindFirstChild(parts[i])\
|
|
6676
|
-
if not cur then\
|
|
6677
|
-
print(NAV_RES .. ':\{"success":false,"error":"instance_not_found"\}')\
|
|
6678
|
-
return\
|
|
6679
|
-
end\
|
|
6680
|
-
end\
|
|
6681
|
-
if cur:IsA("BasePart") then target = cur.Position\
|
|
6682
|
-
elseif cur:IsA("Model") and cur.PrimaryPart then target = cur.PrimaryPart.Position\
|
|
6683
|
-
else target = cur:GetPivot().Position end\
|
|
6684
|
-
else\
|
|
6685
|
-
target = Vector3.new(d.x or 0, d.y or 0, d.z or 0)\
|
|
6686
|
-
end\
|
|
6687
|
-
local path = PathfindingService:CreatePath(\{AgentRadius=2,AgentHeight=5,AgentCanJump=true\})\
|
|
6688
|
-
local pok = pcall(function() path:ComputeAsync(root.Position, target) end)\
|
|
6689
|
-
local method = "direct"\
|
|
6690
|
-
if pok and path.Status == Enum.PathStatus.Success then\
|
|
6691
|
-
method = "pathfinding"\
|
|
6692
|
-
for _, wp in ipairs(path:GetWaypoints()) do\
|
|
6693
|
-
hum:MoveTo(wp.Position)\
|
|
6694
|
-
if wp.Action == Enum.PathWaypointAction.Jump then hum.Jump = true end\
|
|
6695
|
-
hum.MoveToFinished:Wait()\
|
|
6696
|
-
end\
|
|
6697
|
-
else\
|
|
6698
|
-
hum:MoveTo(target)\
|
|
6699
|
-
hum.MoveToFinished:Wait()\
|
|
6700
|
-
end\
|
|
6701
|
-
local fp = root.Position\
|
|
6702
|
-
print(NAV_RES .. ':\{"success":true,"method":"' .. method .. '","position":[' .. fp.X .. ',' .. fp.Y .. ',' .. fp.Z .. ']\}')\
|
|
6703
|
-
end)\
|
|
6704
|
-
end\
|
|
6705
|
-
end)`
|
|
6706
|
-
end
|
|
6707
|
-
local function injectStopListener()
|
|
6708
|
-
local listener = Instance.new("Script")
|
|
6709
|
-
listener.Name = "__MCP_CommandListener"
|
|
6710
|
-
listener.Parent = ServerScriptService
|
|
6711
|
-
local source = buildCommandListenerSource()
|
|
6712
|
-
local seOk = pcall(function()
|
|
6713
|
-
ScriptEditorService:UpdateSourceAsync(listener, function()
|
|
6714
|
-
return source
|
|
6715
|
-
end)
|
|
6716
|
-
end)
|
|
6717
|
-
if not seOk then
|
|
6718
|
-
listener.Source = source
|
|
6719
|
-
end
|
|
6720
|
-
stopListenerScript = listener
|
|
6721
|
-
end
|
|
6722
|
-
local function cleanupStopListener()
|
|
6723
|
-
if stopListenerScript then
|
|
6724
|
-
pcall(function()
|
|
6725
|
-
return stopListenerScript:Destroy()
|
|
6726
|
-
end)
|
|
6727
|
-
stopListenerScript = nil
|
|
6728
|
-
end
|
|
6729
|
-
end
|
|
6730
7576
|
local function startPlaytest(requestData)
|
|
6731
7577
|
local mode = requestData.mode
|
|
6732
7578
|
local numPlayers = requestData.numPlayers
|
|
@@ -6746,11 +7592,6 @@ local function startPlaytest(requestData)
|
|
|
6746
7592
|
-- Reset it so subsequent starts don't hit a false "already running".
|
|
6747
7593
|
if testRunning and not RunService:IsRunning() then
|
|
6748
7594
|
testRunning = false
|
|
6749
|
-
if logConnection then
|
|
6750
|
-
logConnection:Disconnect()
|
|
6751
|
-
logConnection = nil
|
|
6752
|
-
end
|
|
6753
|
-
cleanupStopListener()
|
|
6754
7595
|
-- Runtime eval bridges are created by the play server/client plugin
|
|
6755
7596
|
-- peers and disappear with the play DataModels.
|
|
6756
7597
|
end
|
|
@@ -6760,41 +7601,6 @@ local function startPlaytest(requestData)
|
|
|
6760
7601
|
}
|
|
6761
7602
|
end
|
|
6762
7603
|
testRunning = true
|
|
6763
|
-
outputBuffer = {}
|
|
6764
|
-
testResult = nil
|
|
6765
|
-
testError = nil
|
|
6766
|
-
cleanupStopListener()
|
|
6767
|
-
logConnection = LogService.MessageOut:Connect(function(message, messageType)
|
|
6768
|
-
local _message = message
|
|
6769
|
-
local _arg1 = #NAV_SIGNAL
|
|
6770
|
-
if string.sub(_message, 1, _arg1) == NAV_SIGNAL then
|
|
6771
|
-
return nil
|
|
6772
|
-
end
|
|
6773
|
-
local _message_1 = message
|
|
6774
|
-
local _arg1_1 = #NAV_RESULT + 1
|
|
6775
|
-
if string.sub(_message_1, 1, _arg1_1) == `{NAV_RESULT}:` then
|
|
6776
|
-
if navResultCallback then
|
|
6777
|
-
local _fn = navResultCallback
|
|
6778
|
-
local _message_2 = message
|
|
6779
|
-
local _arg0 = #NAV_RESULT + 2
|
|
6780
|
-
_fn(string.sub(_message_2, _arg0))
|
|
6781
|
-
end
|
|
6782
|
-
return nil
|
|
6783
|
-
end
|
|
6784
|
-
local _outputBuffer = outputBuffer
|
|
6785
|
-
local _arg0 = {
|
|
6786
|
-
message = message,
|
|
6787
|
-
messageType = messageType.Name,
|
|
6788
|
-
timestamp = tick(),
|
|
6789
|
-
}
|
|
6790
|
-
table.insert(_outputBuffer, _arg0)
|
|
6791
|
-
end)
|
|
6792
|
-
local injected, injErr = pcall(function()
|
|
6793
|
-
return injectStopListener()
|
|
6794
|
-
end)
|
|
6795
|
-
if not injected then
|
|
6796
|
-
warn(`[robloxstudio-mcp] Failed to inject stop listener: {injErr}`)
|
|
6797
|
-
end
|
|
6798
7604
|
task.spawn(function()
|
|
6799
7605
|
local ok, result = pcall(function()
|
|
6800
7606
|
if mode == "play" then
|
|
@@ -6802,17 +7608,10 @@ local function startPlaytest(requestData)
|
|
|
6802
7608
|
end
|
|
6803
7609
|
return StudioTestService:ExecuteRunModeAsync({})
|
|
6804
7610
|
end)
|
|
6805
|
-
if ok then
|
|
6806
|
-
|
|
6807
|
-
else
|
|
6808
|
-
testError = tostring(result)
|
|
6809
|
-
end
|
|
6810
|
-
if logConnection then
|
|
6811
|
-
logConnection:Disconnect()
|
|
6812
|
-
logConnection = nil
|
|
7611
|
+
if not ok then
|
|
7612
|
+
warn(`[robloxstudio-mcp] Playtest ended with error: {result}`)
|
|
6813
7613
|
end
|
|
6814
7614
|
testRunning = false
|
|
6815
|
-
cleanupStopListener()
|
|
6816
7615
|
end)
|
|
6817
7616
|
local response = {
|
|
6818
7617
|
success = true,
|
|
@@ -6887,20 +7686,6 @@ local function stopPlaytest(_requestData)
|
|
|
6887
7686
|
message = "Playtest stopped.",
|
|
6888
7687
|
}
|
|
6889
7688
|
end
|
|
6890
|
-
local function getPlaytestOutput(_requestData)
|
|
6891
|
-
local _object = {
|
|
6892
|
-
isRunning = testRunning,
|
|
6893
|
-
}
|
|
6894
|
-
local _left = "output"
|
|
6895
|
-
local _array = {}
|
|
6896
|
-
local _length = #_array
|
|
6897
|
-
table.move(outputBuffer, 1, #outputBuffer, _length + 1, _array)
|
|
6898
|
-
_object[_left] = _array
|
|
6899
|
-
_object.outputCount = #outputBuffer
|
|
6900
|
-
_object.testResult = if testResult ~= nil then tostring(testResult) else nil
|
|
6901
|
-
_object.testError = testError
|
|
6902
|
-
return _object
|
|
6903
|
-
end
|
|
6904
7689
|
local function multiplayerTestStart(requestData)
|
|
6905
7690
|
if RunService:IsRunning() then
|
|
6906
7691
|
return {
|
|
@@ -7085,89 +7870,20 @@ local function multiplayerTestEnd(requestData)
|
|
|
7085
7870
|
value = value,
|
|
7086
7871
|
}
|
|
7087
7872
|
end
|
|
7088
|
-
local function characterNavigation(requestData)
|
|
7089
|
-
if not testRunning then
|
|
7090
|
-
return {
|
|
7091
|
-
error = "Playtest must be running. Start a playtest in 'play' mode first.",
|
|
7092
|
-
}
|
|
7093
|
-
end
|
|
7094
|
-
local position = requestData.position
|
|
7095
|
-
local instancePath = requestData.instancePath
|
|
7096
|
-
local _condition = (requestData.waitForCompletion)
|
|
7097
|
-
if _condition == nil then
|
|
7098
|
-
_condition = true
|
|
7099
|
-
end
|
|
7100
|
-
local waitForCompletion = _condition
|
|
7101
|
-
local _condition_1 = (requestData.timeout)
|
|
7102
|
-
if _condition_1 == nil then
|
|
7103
|
-
_condition_1 = 25
|
|
7104
|
-
end
|
|
7105
|
-
local timeout = _condition_1
|
|
7106
|
-
if not position and not (instancePath ~= "" and instancePath) then
|
|
7107
|
-
return {
|
|
7108
|
-
error = "Either position [x, y, z] or instancePath is required",
|
|
7109
|
-
}
|
|
7110
|
-
end
|
|
7111
|
-
local navData
|
|
7112
|
-
if position then
|
|
7113
|
-
navData = HttpService:JSONEncode({
|
|
7114
|
-
x = position[1],
|
|
7115
|
-
y = position[2],
|
|
7116
|
-
z = position[3],
|
|
7117
|
-
})
|
|
7118
|
-
else
|
|
7119
|
-
navData = HttpService:JSONEncode({
|
|
7120
|
-
instancePath = instancePath,
|
|
7121
|
-
})
|
|
7122
|
-
end
|
|
7123
|
-
warn(`{NAV_SIGNAL}:{navData}`)
|
|
7124
|
-
if not waitForCompletion then
|
|
7125
|
-
return {
|
|
7126
|
-
success = true,
|
|
7127
|
-
message = "Navigation command sent",
|
|
7128
|
-
}
|
|
7129
|
-
end
|
|
7130
|
-
local result
|
|
7131
|
-
navResultCallback = function(json)
|
|
7132
|
-
result = json
|
|
7133
|
-
end
|
|
7134
|
-
local startTime = tick()
|
|
7135
|
-
while not (result ~= "" and result) and tick() - startTime < timeout do
|
|
7136
|
-
task.wait(0.2)
|
|
7137
|
-
end
|
|
7138
|
-
navResultCallback = nil
|
|
7139
|
-
if result ~= "" and result then
|
|
7140
|
-
local ok, parsed = pcall(function()
|
|
7141
|
-
return HttpService:JSONDecode(result)
|
|
7142
|
-
end)
|
|
7143
|
-
if ok then
|
|
7144
|
-
return parsed
|
|
7145
|
-
end
|
|
7146
|
-
return {
|
|
7147
|
-
success = true,
|
|
7148
|
-
rawResult = result,
|
|
7149
|
-
}
|
|
7150
|
-
end
|
|
7151
|
-
return {
|
|
7152
|
-
error = `Navigation timed out after {timeout} seconds`,
|
|
7153
|
-
}
|
|
7154
|
-
end
|
|
7155
7873
|
return {
|
|
7156
7874
|
startPlaytest = startPlaytest,
|
|
7157
7875
|
stopPlaytest = stopPlaytest,
|
|
7158
|
-
getPlaytestOutput = getPlaytestOutput,
|
|
7159
7876
|
multiplayerTestStart = multiplayerTestStart,
|
|
7160
7877
|
multiplayerTestState = multiplayerTestState,
|
|
7161
7878
|
multiplayerTestAddPlayers = multiplayerTestAddPlayers,
|
|
7162
7879
|
multiplayerTestLeaveClient = multiplayerTestLeaveClient,
|
|
7163
7880
|
multiplayerTestEnd = multiplayerTestEnd,
|
|
7164
|
-
characterNavigation = characterNavigation,
|
|
7165
7881
|
}
|
|
7166
7882
|
]]></string>
|
|
7167
7883
|
</Properties>
|
|
7168
7884
|
</Item>
|
|
7169
7885
|
</Item>
|
|
7170
|
-
<Item class="ModuleScript" referent="
|
|
7886
|
+
<Item class="ModuleScript" referent="23">
|
|
7171
7887
|
<Properties>
|
|
7172
7888
|
<string name="Name">HttpDiagnostics</string>
|
|
7173
7889
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7248,7 +7964,7 @@ return {
|
|
|
7248
7964
|
]]></string>
|
|
7249
7965
|
</Properties>
|
|
7250
7966
|
</Item>
|
|
7251
|
-
<Item class="ModuleScript" referent="
|
|
7967
|
+
<Item class="ModuleScript" referent="24">
|
|
7252
7968
|
<Properties>
|
|
7253
7969
|
<string name="Name">LuauExec</string>
|
|
7254
7970
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7682,7 +8398,7 @@ return {
|
|
|
7682
8398
|
]]></string>
|
|
7683
8399
|
</Properties>
|
|
7684
8400
|
</Item>
|
|
7685
|
-
<Item class="ModuleScript" referent="
|
|
8401
|
+
<Item class="ModuleScript" referent="25">
|
|
7686
8402
|
<Properties>
|
|
7687
8403
|
<string name="Name">Recording</string>
|
|
7688
8404
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7712,7 +8428,7 @@ return {
|
|
|
7712
8428
|
]]></string>
|
|
7713
8429
|
</Properties>
|
|
7714
8430
|
</Item>
|
|
7715
|
-
<Item class="ModuleScript" referent="
|
|
8431
|
+
<Item class="ModuleScript" referent="26">
|
|
7716
8432
|
<Properties>
|
|
7717
8433
|
<string name="Name">RenderMonitor</string>
|
|
7718
8434
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7780,7 +8496,7 @@ return {
|
|
|
7780
8496
|
]]></string>
|
|
7781
8497
|
</Properties>
|
|
7782
8498
|
</Item>
|
|
7783
|
-
<Item class="ModuleScript" referent="
|
|
8499
|
+
<Item class="ModuleScript" referent="27">
|
|
7784
8500
|
<Properties>
|
|
7785
8501
|
<string name="Name">RuntimeLogBuffer</string>
|
|
7786
8502
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7833,6 +8549,43 @@ local function dropOldestUntilFits(incomingBytes)
|
|
|
7833
8549
|
totalDropped += 1
|
|
7834
8550
|
end
|
|
7835
8551
|
end
|
|
8552
|
+
local function pushEntry(msg, t, ts)
|
|
8553
|
+
if ts == nil then
|
|
8554
|
+
ts = nowSec()
|
|
8555
|
+
end
|
|
8556
|
+
local bytes = #msg
|
|
8557
|
+
dropOldestUntilFits(bytes)
|
|
8558
|
+
local _arg0 = {
|
|
8559
|
+
seq = nextSeq,
|
|
8560
|
+
ts = ts,
|
|
8561
|
+
level = levelTag(t),
|
|
8562
|
+
message = msg,
|
|
8563
|
+
}
|
|
8564
|
+
table.insert(entries, _arg0)
|
|
8565
|
+
nextSeq += 1
|
|
8566
|
+
totalBytes += bytes
|
|
8567
|
+
end
|
|
8568
|
+
local function seedRuntimeHistory()
|
|
8569
|
+
if not RunService:IsRunning() then
|
|
8570
|
+
return nil
|
|
8571
|
+
end
|
|
8572
|
+
local ok, history = pcall(function()
|
|
8573
|
+
return LogService:GetLogHistory()
|
|
8574
|
+
end)
|
|
8575
|
+
if not ok then
|
|
8576
|
+
return nil
|
|
8577
|
+
end
|
|
8578
|
+
for _, entry in history do
|
|
8579
|
+
local _message = entry.message
|
|
8580
|
+
if not (type(_message) == "string") then
|
|
8581
|
+
continue
|
|
8582
|
+
end
|
|
8583
|
+
local _exp = entry.message
|
|
8584
|
+
local _exp_1 = entry.messageType
|
|
8585
|
+
local _timestamp = entry.timestamp
|
|
8586
|
+
pushEntry(_exp, _exp_1, if type(_timestamp) == "number" then entry.timestamp else nil)
|
|
8587
|
+
end
|
|
8588
|
+
end
|
|
7836
8589
|
local function install()
|
|
7837
8590
|
if installed then
|
|
7838
8591
|
return nil
|
|
@@ -7841,18 +8594,12 @@ local function install()
|
|
|
7841
8594
|
return nil
|
|
7842
8595
|
end
|
|
7843
8596
|
installed = true
|
|
8597
|
+
-- Play peers can emit startup logs before the plugin finishes loading.
|
|
8598
|
+
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see
|
|
8599
|
+
-- those early messages; skip edit mode to avoid stale prior-session logs.
|
|
8600
|
+
seedRuntimeHistory()
|
|
7844
8601
|
LogService.MessageOut:Connect(function(msg, t)
|
|
7845
|
-
|
|
7846
|
-
dropOldestUntilFits(bytes)
|
|
7847
|
-
local _arg0 = {
|
|
7848
|
-
seq = nextSeq,
|
|
7849
|
-
ts = nowSec(),
|
|
7850
|
-
level = levelTag(t),
|
|
7851
|
-
message = msg,
|
|
7852
|
-
}
|
|
7853
|
-
table.insert(entries, _arg0)
|
|
7854
|
-
nextSeq += 1
|
|
7855
|
-
totalBytes += bytes
|
|
8602
|
+
pushEntry(msg, t)
|
|
7856
8603
|
end)
|
|
7857
8604
|
end
|
|
7858
8605
|
local function detectPeer()
|
|
@@ -7961,7 +8708,7 @@ return {
|
|
|
7961
8708
|
]]></string>
|
|
7962
8709
|
</Properties>
|
|
7963
8710
|
</Item>
|
|
7964
|
-
<Item class="ModuleScript" referent="
|
|
8711
|
+
<Item class="ModuleScript" referent="28">
|
|
7965
8712
|
<Properties>
|
|
7966
8713
|
<string name="Name">ServerUrlSettings</string>
|
|
7967
8714
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7969,11 +8716,37 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
|
7969
8716
|
local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
7970
8717
|
local HttpService = _services.HttpService
|
|
7971
8718
|
local ServerStorage = _services.ServerStorage
|
|
7972
|
-
local
|
|
8719
|
+
local LEGACY_SETTING_KEY_PREFIX = "MCP_SERVER_URL_"
|
|
8720
|
+
local SETTING_KEY_PREFIX = "MCP_LAST_SUCCESSFUL_SERVER_URL_"
|
|
8721
|
+
local GLOBAL_SETTING_KEY = "MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1"
|
|
7973
8722
|
local pluginRef
|
|
7974
8723
|
local function init(p)
|
|
7975
8724
|
pluginRef = p
|
|
7976
8725
|
end
|
|
8726
|
+
local function normalizeServerUrl(serverUrl)
|
|
8727
|
+
local _condition = serverUrl
|
|
8728
|
+
if _condition == nil then
|
|
8729
|
+
_condition = ""
|
|
8730
|
+
end
|
|
8731
|
+
local normalized = (string.gsub((string.gsub(_condition, "^%s+", "")), "%s+$", ""))
|
|
8732
|
+
if normalized == "" then
|
|
8733
|
+
return ""
|
|
8734
|
+
end
|
|
8735
|
+
if (string.match(normalized, "^%a[%w+.-]*://")) == nil then
|
|
8736
|
+
normalized = `http://{normalized}`
|
|
8737
|
+
end
|
|
8738
|
+
while #normalized > 0 and string.sub(normalized, -1) == "/" and (string.match(normalized, "^%a[%w+.-]*://$")) == nil do
|
|
8739
|
+
normalized = string.sub(normalized, 1, -2)
|
|
8740
|
+
end
|
|
8741
|
+
return normalized
|
|
8742
|
+
end
|
|
8743
|
+
local function extractPort(serverUrl)
|
|
8744
|
+
local portStr = string.match(serverUrl, ":(%d+)$")
|
|
8745
|
+
if portStr == nil then
|
|
8746
|
+
return nil
|
|
8747
|
+
end
|
|
8748
|
+
return tonumber(portStr)
|
|
8749
|
+
end
|
|
7977
8750
|
local function addUnique(values, value)
|
|
7978
8751
|
local _values = values
|
|
7979
8752
|
local _value = value
|
|
@@ -8003,15 +8776,39 @@ end
|
|
|
8003
8776
|
local function settingKey(instanceId)
|
|
8004
8777
|
return SETTING_KEY_PREFIX .. instanceId
|
|
8005
8778
|
end
|
|
8779
|
+
local function legacySettingKey(instanceId)
|
|
8780
|
+
return LEGACY_SETTING_KEY_PREFIX .. instanceId
|
|
8781
|
+
end
|
|
8782
|
+
local function readSettingString(key)
|
|
8783
|
+
if not pluginRef then
|
|
8784
|
+
return nil
|
|
8785
|
+
end
|
|
8786
|
+
local ok, value = pcall(function()
|
|
8787
|
+
return pluginRef:GetSetting(key)
|
|
8788
|
+
end)
|
|
8789
|
+
if not ok or not (type(value) == "string") then
|
|
8790
|
+
return nil
|
|
8791
|
+
end
|
|
8792
|
+
local normalized = normalizeServerUrl(value)
|
|
8793
|
+
return if normalized ~= "" then normalized else nil
|
|
8794
|
+
end
|
|
8795
|
+
local function writeSettingString(key, serverUrl)
|
|
8796
|
+
if not pluginRef then
|
|
8797
|
+
return nil
|
|
8798
|
+
end
|
|
8799
|
+
pcall(function()
|
|
8800
|
+
return pluginRef:SetSetting(key, serverUrl)
|
|
8801
|
+
end)
|
|
8802
|
+
end
|
|
8006
8803
|
local function rememberServerUrl(serverUrl)
|
|
8007
|
-
|
|
8804
|
+
local normalized = normalizeServerUrl(serverUrl)
|
|
8805
|
+
if not pluginRef or normalized == "" then
|
|
8008
8806
|
return nil
|
|
8009
8807
|
end
|
|
8808
|
+
writeSettingString(GLOBAL_SETTING_KEY, normalized)
|
|
8010
8809
|
for _, instanceId in computeInstanceIds() do
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
return pluginRef:SetSetting(key, serverUrl)
|
|
8014
|
-
end)
|
|
8810
|
+
writeSettingString(settingKey(instanceId), normalized)
|
|
8811
|
+
writeSettingString(legacySettingKey(instanceId), normalized)
|
|
8015
8812
|
end
|
|
8016
8813
|
end
|
|
8017
8814
|
local function readServerUrl()
|
|
@@ -8019,29 +8816,38 @@ local function readServerUrl()
|
|
|
8019
8816
|
return nil
|
|
8020
8817
|
end
|
|
8021
8818
|
for _, instanceId in computeInstanceIds() do
|
|
8022
|
-
local
|
|
8023
|
-
|
|
8024
|
-
return
|
|
8025
|
-
end
|
|
8026
|
-
|
|
8027
|
-
|
|
8819
|
+
local remembered = readSettingString(settingKey(instanceId))
|
|
8820
|
+
if remembered ~= nil then
|
|
8821
|
+
return remembered
|
|
8822
|
+
end
|
|
8823
|
+
end
|
|
8824
|
+
local globalRemembered = readSettingString(GLOBAL_SETTING_KEY)
|
|
8825
|
+
if globalRemembered ~= nil then
|
|
8826
|
+
return globalRemembered
|
|
8827
|
+
end
|
|
8828
|
+
for _, instanceId in computeInstanceIds() do
|
|
8829
|
+
local legacyRemembered = readSettingString(legacySettingKey(instanceId))
|
|
8830
|
+
if legacyRemembered ~= nil then
|
|
8831
|
+
return legacyRemembered
|
|
8028
8832
|
end
|
|
8029
8833
|
end
|
|
8030
8834
|
return nil
|
|
8031
8835
|
end
|
|
8032
8836
|
return {
|
|
8033
8837
|
init = init,
|
|
8838
|
+
normalizeServerUrl = normalizeServerUrl,
|
|
8839
|
+
extractPort = extractPort,
|
|
8034
8840
|
rememberServerUrl = rememberServerUrl,
|
|
8035
8841
|
readServerUrl = readServerUrl,
|
|
8036
8842
|
}
|
|
8037
8843
|
]]></string>
|
|
8038
8844
|
</Properties>
|
|
8039
8845
|
</Item>
|
|
8040
|
-
<Item class="ModuleScript" referent="
|
|
8846
|
+
<Item class="ModuleScript" referent="29">
|
|
8041
8847
|
<Properties>
|
|
8042
8848
|
<string name="Name">State</string>
|
|
8043
8849
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
8044
|
-
local CURRENT_VERSION = "2.
|
|
8850
|
+
local CURRENT_VERSION = "2.17.1"
|
|
8045
8851
|
local PLUGIN_VARIANT = "inspector"
|
|
8046
8852
|
local MAX_CONNECTIONS = 5
|
|
8047
8853
|
local BASE_PORT = 58741
|
|
@@ -8135,7 +8941,7 @@ return {
|
|
|
8135
8941
|
]]></string>
|
|
8136
8942
|
</Properties>
|
|
8137
8943
|
</Item>
|
|
8138
|
-
<Item class="ModuleScript" referent="
|
|
8944
|
+
<Item class="ModuleScript" referent="30">
|
|
8139
8945
|
<Properties>
|
|
8140
8946
|
<string name="Name">StopPlayMonitor</string>
|
|
8141
8947
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -8433,13 +9239,14 @@ return {
|
|
|
8433
9239
|
]]></string>
|
|
8434
9240
|
</Properties>
|
|
8435
9241
|
</Item>
|
|
8436
|
-
<Item class="ModuleScript" referent="
|
|
9242
|
+
<Item class="ModuleScript" referent="31">
|
|
8437
9243
|
<Properties>
|
|
8438
9244
|
<string name="Name">UI</string>
|
|
8439
9245
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
8440
9246
|
local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
8441
9247
|
local TweenService = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services").TweenService
|
|
8442
9248
|
local State = TS.import(script, script.Parent, "State")
|
|
9249
|
+
local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
|
|
8443
9250
|
local elements = nil
|
|
8444
9251
|
local pulseAnimation
|
|
8445
9252
|
local buttonHover = false
|
|
@@ -8878,7 +9685,7 @@ local function init(pluginRef)
|
|
|
8878
9685
|
urlInput.Size = UDim2.new(1, 0, 0, 26)
|
|
8879
9686
|
urlInput.BackgroundColor3 = C.bg
|
|
8880
9687
|
urlInput.BorderSizePixel = 0
|
|
8881
|
-
urlInput.Text =
|
|
9688
|
+
urlInput.Text = State.getActiveConnection().serverUrl
|
|
8882
9689
|
urlInput.TextColor3 = C.label
|
|
8883
9690
|
urlInput.TextSize = 11
|
|
8884
9691
|
urlInput.Font = Enum.Font.GothamMedium
|
|
@@ -8899,14 +9706,16 @@ local function init(pluginRef)
|
|
|
8899
9706
|
if not conn or conn.isActive then
|
|
8900
9707
|
return nil
|
|
8901
9708
|
end
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
9709
|
+
local normalizedUrl = ServerUrlSettings.normalizeServerUrl(urlInput.Text)
|
|
9710
|
+
if normalizedUrl == "" then
|
|
9711
|
+
urlInput.Text = conn.serverUrl
|
|
9712
|
+
return nil
|
|
9713
|
+
end
|
|
9714
|
+
conn.serverUrl = normalizedUrl
|
|
9715
|
+
urlInput.Text = normalizedUrl
|
|
9716
|
+
local port = ServerUrlSettings.extractPort(conn.serverUrl)
|
|
9717
|
+
if port ~= nil then
|
|
9718
|
+
conn.port = port
|
|
8910
9719
|
end
|
|
8911
9720
|
updateTabLabel(State.getActiveTabIndex())
|
|
8912
9721
|
end)
|
|
@@ -9204,11 +10013,37 @@ return {
|
|
|
9204
10013
|
]]></string>
|
|
9205
10014
|
</Properties>
|
|
9206
10015
|
</Item>
|
|
9207
|
-
<Item class="ModuleScript" referent="
|
|
10016
|
+
<Item class="ModuleScript" referent="32">
|
|
9208
10017
|
<Properties>
|
|
9209
10018
|
<string name="Name">Utils</string>
|
|
9210
10019
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
9211
10020
|
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
10021
|
+
local LUAU_KEYWORDS = {
|
|
10022
|
+
["and"] = true,
|
|
10023
|
+
["break"] = true,
|
|
10024
|
+
["continue"] = true,
|
|
10025
|
+
["do"] = true,
|
|
10026
|
+
["else"] = true,
|
|
10027
|
+
["elseif"] = true,
|
|
10028
|
+
["end"] = true,
|
|
10029
|
+
["export"] = true,
|
|
10030
|
+
["false"] = true,
|
|
10031
|
+
["for"] = true,
|
|
10032
|
+
["function"] = true,
|
|
10033
|
+
["if"] = true,
|
|
10034
|
+
["in"] = true,
|
|
10035
|
+
["local"] = true,
|
|
10036
|
+
["nil"] = true,
|
|
10037
|
+
["not"] = true,
|
|
10038
|
+
["or"] = true,
|
|
10039
|
+
["repeat"] = true,
|
|
10040
|
+
["return"] = true,
|
|
10041
|
+
["then"] = true,
|
|
10042
|
+
["true"] = true,
|
|
10043
|
+
["type"] = true,
|
|
10044
|
+
["until"] = true,
|
|
10045
|
+
["while"] = true,
|
|
10046
|
+
}
|
|
9212
10047
|
local function safeCall(func, ...)
|
|
9213
10048
|
local args = { ... }
|
|
9214
10049
|
local success, result = pcall(func, unpack(args))
|
|
@@ -9219,6 +10054,194 @@ local function safeCall(func, ...)
|
|
|
9219
10054
|
return nil
|
|
9220
10055
|
end
|
|
9221
10056
|
end
|
|
10057
|
+
local function isSimplePathSegment(segment)
|
|
10058
|
+
local _condition = (string.match(segment, "^[%a_][%w_]*$")) ~= nil
|
|
10059
|
+
if _condition then
|
|
10060
|
+
local _segment = segment
|
|
10061
|
+
_condition = not (LUAU_KEYWORDS[_segment] ~= nil)
|
|
10062
|
+
end
|
|
10063
|
+
return _condition
|
|
10064
|
+
end
|
|
10065
|
+
local function quotePathSegment(segment)
|
|
10066
|
+
local escaped = (string.gsub(segment, "\\", "\\\\"))
|
|
10067
|
+
escaped = (string.gsub(escaped, "\n", "\\n"))
|
|
10068
|
+
escaped = (string.gsub(escaped, "\r", "\\r"))
|
|
10069
|
+
escaped = (string.gsub(escaped, "\t", "\\t"))
|
|
10070
|
+
escaped = (string.gsub(escaped, '"', '\\"'))
|
|
10071
|
+
return `"{escaped}"`
|
|
10072
|
+
end
|
|
10073
|
+
local function unescapePathSegment(segment)
|
|
10074
|
+
local chars = {}
|
|
10075
|
+
local i = 1
|
|
10076
|
+
while i <= #segment do
|
|
10077
|
+
local _segment = segment
|
|
10078
|
+
local _i = i
|
|
10079
|
+
local _i_1 = i
|
|
10080
|
+
local ch = string.sub(_segment, _i, _i_1)
|
|
10081
|
+
if ch == "\\" and i < #segment then
|
|
10082
|
+
local _segment_1 = segment
|
|
10083
|
+
local _arg0 = i + 1
|
|
10084
|
+
local _arg1 = i + 1
|
|
10085
|
+
local nextChar = string.sub(_segment_1, _arg0, _arg1)
|
|
10086
|
+
if nextChar == "n" then
|
|
10087
|
+
table.insert(chars, "\n")
|
|
10088
|
+
elseif nextChar == "r" then
|
|
10089
|
+
table.insert(chars, "\r")
|
|
10090
|
+
elseif nextChar == "t" then
|
|
10091
|
+
table.insert(chars, "\t")
|
|
10092
|
+
else
|
|
10093
|
+
table.insert(chars, nextChar)
|
|
10094
|
+
end
|
|
10095
|
+
i += 2
|
|
10096
|
+
else
|
|
10097
|
+
table.insert(chars, ch)
|
|
10098
|
+
i += 1
|
|
10099
|
+
end
|
|
10100
|
+
end
|
|
10101
|
+
return table.concat(chars, "")
|
|
10102
|
+
end
|
|
10103
|
+
local function isCanonicalBracketStart(path, index)
|
|
10104
|
+
local _path = path
|
|
10105
|
+
local _arg0 = index + 1
|
|
10106
|
+
local _arg1 = index + 1
|
|
10107
|
+
local quote = string.sub(_path, _arg0, _arg1)
|
|
10108
|
+
local _condition = (quote == '"' or quote == "'")
|
|
10109
|
+
if _condition then
|
|
10110
|
+
local _path_1 = path
|
|
10111
|
+
local _arg0_1 = index - 1
|
|
10112
|
+
local _arg1_1 = index - 1
|
|
10113
|
+
_condition = string.sub(_path_1, _arg0_1, _arg1_1) ~= "."
|
|
10114
|
+
end
|
|
10115
|
+
return _condition
|
|
10116
|
+
end
|
|
10117
|
+
local function parseInstancePath(path)
|
|
10118
|
+
local i = 1
|
|
10119
|
+
local len = #path
|
|
10120
|
+
local parts = {}
|
|
10121
|
+
local current = ""
|
|
10122
|
+
if path == "" or path == "game" then
|
|
10123
|
+
return parts
|
|
10124
|
+
end
|
|
10125
|
+
if string.sub(path, 1, 5) == "game." then
|
|
10126
|
+
i = 6
|
|
10127
|
+
elseif string.sub(path, 1, 5) == "game[" then
|
|
10128
|
+
i = 5
|
|
10129
|
+
end
|
|
10130
|
+
while i <= len do
|
|
10131
|
+
local _path = path
|
|
10132
|
+
local _i = i
|
|
10133
|
+
local _i_1 = i
|
|
10134
|
+
local ch = string.sub(_path, _i, _i_1)
|
|
10135
|
+
if ch == "." then
|
|
10136
|
+
if current ~= "" then
|
|
10137
|
+
local _current = current
|
|
10138
|
+
table.insert(parts, _current)
|
|
10139
|
+
current = ""
|
|
10140
|
+
i += 1
|
|
10141
|
+
else
|
|
10142
|
+
local _condition = i > 1
|
|
10143
|
+
if _condition then
|
|
10144
|
+
local _path_1 = path
|
|
10145
|
+
local _arg0 = i - 1
|
|
10146
|
+
local _arg1 = i - 1
|
|
10147
|
+
_condition = string.sub(_path_1, _arg0, _arg1) == "."
|
|
10148
|
+
if _condition then
|
|
10149
|
+
_condition = i < len
|
|
10150
|
+
if _condition then
|
|
10151
|
+
local _path_2 = path
|
|
10152
|
+
local _arg0_1 = i + 1
|
|
10153
|
+
local _arg1_1 = i + 1
|
|
10154
|
+
_condition = string.sub(_path_2, _arg0_1, _arg1_1) ~= "["
|
|
10155
|
+
end
|
|
10156
|
+
end
|
|
10157
|
+
end
|
|
10158
|
+
if _condition then
|
|
10159
|
+
-- Back-compat for previously emitted paths such as
|
|
10160
|
+
-- game.ServerScriptService..dir.ReproScript, where ".dir"
|
|
10161
|
+
-- was an actual instance name.
|
|
10162
|
+
current = "."
|
|
10163
|
+
i += 1
|
|
10164
|
+
else
|
|
10165
|
+
i += 1
|
|
10166
|
+
end
|
|
10167
|
+
end
|
|
10168
|
+
elseif ch == "[" and i < len and isCanonicalBracketStart(path, i) then
|
|
10169
|
+
if current ~= "" then
|
|
10170
|
+
local _current = current
|
|
10171
|
+
table.insert(parts, _current)
|
|
10172
|
+
current = ""
|
|
10173
|
+
end
|
|
10174
|
+
local _path_1 = path
|
|
10175
|
+
local _arg0 = i + 1
|
|
10176
|
+
local _arg1 = i + 1
|
|
10177
|
+
local quote = string.sub(_path_1, _arg0, _arg1)
|
|
10178
|
+
if quote ~= '"' and quote ~= "'" then
|
|
10179
|
+
return nil
|
|
10180
|
+
end
|
|
10181
|
+
local j = i + 2
|
|
10182
|
+
local raw = ""
|
|
10183
|
+
while j <= len do
|
|
10184
|
+
local _path_2 = path
|
|
10185
|
+
local _j = j
|
|
10186
|
+
local _j_1 = j
|
|
10187
|
+
local c = string.sub(_path_2, _j, _j_1)
|
|
10188
|
+
if c == "\\" then
|
|
10189
|
+
if j >= len then
|
|
10190
|
+
return nil
|
|
10191
|
+
end
|
|
10192
|
+
local _path_3 = path
|
|
10193
|
+
local _arg0_1 = j + 1
|
|
10194
|
+
local _arg1_1 = j + 1
|
|
10195
|
+
raw ..= c .. string.sub(_path_3, _arg0_1, _arg1_1)
|
|
10196
|
+
j += 2
|
|
10197
|
+
elseif c == quote then
|
|
10198
|
+
break
|
|
10199
|
+
else
|
|
10200
|
+
raw ..= c
|
|
10201
|
+
j += 1
|
|
10202
|
+
end
|
|
10203
|
+
end
|
|
10204
|
+
local _condition = j > len
|
|
10205
|
+
if not _condition then
|
|
10206
|
+
local _path_2 = path
|
|
10207
|
+
local _j = j
|
|
10208
|
+
local _j_1 = j
|
|
10209
|
+
_condition = string.sub(_path_2, _j, _j_1) ~= quote
|
|
10210
|
+
if not _condition then
|
|
10211
|
+
local _path_3 = path
|
|
10212
|
+
local _arg0_1 = j + 1
|
|
10213
|
+
local _arg1_1 = j + 1
|
|
10214
|
+
_condition = string.sub(_path_3, _arg0_1, _arg1_1) ~= "]"
|
|
10215
|
+
end
|
|
10216
|
+
end
|
|
10217
|
+
if _condition then
|
|
10218
|
+
return nil
|
|
10219
|
+
end
|
|
10220
|
+
local _arg0_1 = unescapePathSegment(raw)
|
|
10221
|
+
table.insert(parts, _arg0_1)
|
|
10222
|
+
i = j + 2
|
|
10223
|
+
else
|
|
10224
|
+
current ..= ch
|
|
10225
|
+
i += 1
|
|
10226
|
+
end
|
|
10227
|
+
end
|
|
10228
|
+
if current ~= "" then
|
|
10229
|
+
local _current = current
|
|
10230
|
+
table.insert(parts, _current)
|
|
10231
|
+
end
|
|
10232
|
+
return parts
|
|
10233
|
+
end
|
|
10234
|
+
local function getRootSegment(instance)
|
|
10235
|
+
if instance.Parent == game then
|
|
10236
|
+
local ok, service = pcall(function()
|
|
10237
|
+
return game:GetService(instance.ClassName)
|
|
10238
|
+
end)
|
|
10239
|
+
if ok and service == instance then
|
|
10240
|
+
return instance.ClassName
|
|
10241
|
+
end
|
|
10242
|
+
end
|
|
10243
|
+
return instance.Name
|
|
10244
|
+
end
|
|
9222
10245
|
local function getInstancePath(instance)
|
|
9223
10246
|
if not instance or instance == game then
|
|
9224
10247
|
return "game"
|
|
@@ -9226,23 +10249,40 @@ local function getInstancePath(instance)
|
|
|
9226
10249
|
local pathParts = {}
|
|
9227
10250
|
local current = instance
|
|
9228
10251
|
while current and current ~= game do
|
|
9229
|
-
local
|
|
9230
|
-
table.insert(pathParts, 1,
|
|
10252
|
+
local _arg0 = getRootSegment(current)
|
|
10253
|
+
table.insert(pathParts, 1, _arg0)
|
|
9231
10254
|
current = current.Parent
|
|
9232
10255
|
end
|
|
9233
|
-
|
|
10256
|
+
local path = "game"
|
|
10257
|
+
for _, part in pathParts do
|
|
10258
|
+
if isSimplePathSegment(part) then
|
|
10259
|
+
path ..= `.{part}`
|
|
10260
|
+
else
|
|
10261
|
+
path ..= `[{quotePathSegment(part)}]`
|
|
10262
|
+
end
|
|
10263
|
+
end
|
|
10264
|
+
return path
|
|
10265
|
+
end
|
|
10266
|
+
local function getRootInstance(segment)
|
|
10267
|
+
local ok, service = pcall(function()
|
|
10268
|
+
return game:GetService(segment)
|
|
10269
|
+
end)
|
|
10270
|
+
if ok and service then
|
|
10271
|
+
return service
|
|
10272
|
+
end
|
|
10273
|
+
return game:FindFirstChild(segment)
|
|
9234
10274
|
end
|
|
9235
10275
|
local function getInstanceByPath(path)
|
|
9236
|
-
|
|
9237
|
-
|
|
10276
|
+
local parts = parseInstancePath(path)
|
|
10277
|
+
if parts == nil then
|
|
10278
|
+
return nil
|
|
9238
10279
|
end
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
for part in string.gmatch(cleaned, "[^%.]+") do
|
|
9242
|
-
table.insert(parts, part)
|
|
10280
|
+
if #parts == 0 then
|
|
10281
|
+
return game
|
|
9243
10282
|
end
|
|
9244
|
-
local current =
|
|
9245
|
-
for
|
|
10283
|
+
local current = getRootInstance(parts[1])
|
|
10284
|
+
for i = 1, #parts - 1 do
|
|
10285
|
+
local part = parts[i + 1]
|
|
9246
10286
|
if not current then
|
|
9247
10287
|
return nil
|
|
9248
10288
|
end
|
|
@@ -9734,11 +10774,11 @@ return {
|
|
|
9734
10774
|
</Properties>
|
|
9735
10775
|
</Item>
|
|
9736
10776
|
</Item>
|
|
9737
|
-
<Item class="Folder" referent="
|
|
10777
|
+
<Item class="Folder" referent="36">
|
|
9738
10778
|
<Properties>
|
|
9739
10779
|
<string name="Name">include</string>
|
|
9740
10780
|
</Properties>
|
|
9741
|
-
<Item class="ModuleScript" referent="
|
|
10781
|
+
<Item class="ModuleScript" referent="33">
|
|
9742
10782
|
<Properties>
|
|
9743
10783
|
<string name="Name">Promise</string>
|
|
9744
10784
|
<string name="Source"><![CDATA[--[[
|
|
@@ -11812,7 +12852,7 @@ return Promise
|
|
|
11812
12852
|
]]></string>
|
|
11813
12853
|
</Properties>
|
|
11814
12854
|
</Item>
|
|
11815
|
-
<Item class="ModuleScript" referent="
|
|
12855
|
+
<Item class="ModuleScript" referent="34">
|
|
11816
12856
|
<Properties>
|
|
11817
12857
|
<string name="Name">RuntimeLib</string>
|
|
11818
12858
|
<string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
|
|
@@ -12079,15 +13119,15 @@ return TS
|
|
|
12079
13119
|
</Properties>
|
|
12080
13120
|
</Item>
|
|
12081
13121
|
</Item>
|
|
12082
|
-
<Item class="Folder" referent="
|
|
13122
|
+
<Item class="Folder" referent="37">
|
|
12083
13123
|
<Properties>
|
|
12084
13124
|
<string name="Name">node_modules</string>
|
|
12085
13125
|
</Properties>
|
|
12086
|
-
<Item class="Folder" referent="
|
|
13126
|
+
<Item class="Folder" referent="38">
|
|
12087
13127
|
<Properties>
|
|
12088
13128
|
<string name="Name">@rbxts</string>
|
|
12089
13129
|
</Properties>
|
|
12090
|
-
<Item class="ModuleScript" referent="
|
|
13130
|
+
<Item class="ModuleScript" referent="35">
|
|
12091
13131
|
<Properties>
|
|
12092
13132
|
<string name="Name">services</string>
|
|
12093
13133
|
<string name="Source"><![CDATA[return setmetatable({}, {
|