Last active
September 8, 2023 15:20
-
-
Save grilme99/361574912f06384f490f3d02e9b17afe to your computer and use it in GitHub Desktop.
Roblox elevated/internal plugin demoter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
ELEVATED PLUGIN DEMOTER | |
"One-click" script to demote elevated (usually internal) plugins by removing or | |
replacing their elevated API calls. | |
NOTE: | |
I've only tested this on the "Developer" Storybook plugin (the irony). It will probably | |
need some modification for other internal plugins, like the Roact Inspector. | |
STEPS TO USE: | |
1. Find the plugin .rbxm file in Studio's BuiltInPlugins folder. Windows with Mod Manager: | |
%LOCALAPPDATA%\Roblox Studio\BuiltInPlugins | |
2. Drop into a Studio place (empy baseplate, doesn't matter) and select the top-level folder. | |
3. Copy this script into the command bar and run! Your plugin has now been demoted and you can | |
save it as a local plugin to use. | |
NOTE FOR THE STORYBOOK PLUGIN: | |
There is a restriction which prevents plugins from requiring normal modules. You should use the | |
Mod Manager to enable the "FFlagEnableLoadModule" flag so that the plugin can use debug.loadmodule | |
to bypass this restriction. | |
This script is mostly a fork of the "DeveloperFramework" (ironic, I know) Embed module, but with | |
things added on top for our needs. | |
Happy developing! | |
]] | |
local CONFIG = { | |
FallbackFlagValue = false, | |
SpoofInternal = true, -- You're gonna wanna leave this on for most plugins to run | |
} | |
local root = game:GetService("Selection"):Get()[1] | |
if (root == nil) then | |
warn("You must select the plugin before running this script.") | |
end | |
local pluginName = root.Name | |
local function rewriteMethodCall(name: string, value: any) | |
return string.format('select(2, "%s", %s)', name, tostring(value)) | |
end | |
local function tryCall(fn: () -> any, default: any) | |
local ok, result = pcall(function() | |
return fn() | |
end) | |
if ok then | |
return result | |
else | |
return default | |
end | |
end | |
local function getFlag(flagName: string) | |
return tryCall(function() | |
return game:GetFastFlag(flagName) | |
end, CONFIG.FallbackFlagValue) | |
end | |
local function getFeature(featureName: string) | |
return tryCall(function() | |
return game:GetEngineFeature(featureName) | |
end, featureName) | |
end | |
local function getString(stringName: string) | |
return tryCall(function() | |
return game:GetFastString(stringName) | |
end, stringName) | |
end | |
local function getInt(intName: string) | |
return tryCall(function() | |
return game:GetFastInt(intName) | |
end, intName) | |
end | |
local function getFVariable(settingName: string) | |
return tryCall(function() | |
return settings():GetFVariable(settingName) | |
end, CONFIG.FallbackFlagValue) | |
end | |
local function rewriteFlag(flagName: string) | |
return rewriteMethodCall(flagName, getFlag(flagName)) | |
end | |
local function rewriteEngineFeature(featureName: string) | |
return rewriteMethodCall(featureName, getFeature(featureName)) | |
end | |
local function rewriteString(stringName: string) | |
return rewriteMethodCall(stringName, getString(stringName)) | |
end | |
local function rewriteInt(intName: string) | |
return rewriteMethodCall(intName, getInt(intName)) | |
end | |
local function rewriteSetting(settingName: string) | |
return rewriteMethodCall(settingName, getFVariable(settingName)) | |
end | |
local function rewriteFlagsForScript(script: ModuleScript) | |
-- These API calls are roblox-security only, so cannot be used in a normal plugin | |
local src = script.Source | |
:gsub([[game:DefineFastFlag%(%s*"([^"]+)"[^)]*%)]], rewriteFlag) | |
:gsub([[game:DefineFastString%(%s*"([^"]+)"[^)]*%)]], rewriteString) | |
:gsub([[game:GetEngineFeature%(%s*"([^"]+)"[^)]*%)]], rewriteEngineFeature) | |
:gsub([[game:GetFastFlag%(%s*"([^"]+)"[^)]*%)]], rewriteFlag) | |
:gsub([[game:GetFastString%(%s*"([^"]+)"[^)]*%)]], rewriteString) | |
:gsub([[game:GetFastInt%(%s*"([^"]+)"[^)]*%)]], rewriteInt) | |
:gsub([[settings%(%):GetFFlag%(%s*"([^"]+)"%)]], rewriteFlag) | |
:gsub([[settings%(%):GetFVariable%(%s*"([^"]+)"%)]], rewriteSetting) | |
:gsub([[settings%(%):GetFVariable%(%s*"([^"]+)"%)]], rewriteSetting) | |
:gsub([[game:GetService%("StudioService"%):HasInternalPermission%(%)]], tostring(CONFIG.SpoofInternal)) | |
:gsub([[game:GetService%("StudioService"%)]], [[({StudioLocaleId = "en-us", GetPropertyChangedSignal = function() return {Connect = function() return {Disconnect = function() end} end} end, HasInternalPermission = function() return true end, FindFirstChild = function(n) game:GetService("StudioService"):FindFirstChild(n) end})]]) | |
:gsub([[game:GetService%("ABTestService"%)]], [[({GetVariant = function() return "Control" end})]]) | |
-- The "Flags" util module produces some annoying warnings, remove them. | |
:gsub([[warn%("An error occurred defining the fast flag: " .. flagName%)]], "") | |
:gsub([[warn%(err%)]], "") | |
-- Places a bindable event under StudioService, doesn't work with this bypass. Put it somewhere else. | |
:gsub([[BindableEventBridge%.new%(Services.getStudioService%(%)%)]], [[BindableEventBridge.new(game:GetService("CoreGui"))]]) | |
-- Used by the Roact Inspector to get plugin GUIs, but we probably won't need this - keeping it will error. | |
:gsub([[Services%.getRobloxPluginGuiService%(%):WaitForChild%(pluginName, 10%)]], "nil") | |
-- There's not really any good way to replace the dialog setup, so just disable it entirely | |
:gsub([[local Dialog = createPluginWidget%("Dialog", function%(props%)+.+%send%)]], "local Dialog = function() end") | |
:gsub([[Typecheck%.wrap%(Dialog, script%)]], "") | |
-- Prevent elevated plugins from setting PluginAction.Enabled | |
:gsub([[action%.Enabled = %a+]], "") | |
:gsub([[action%.DefaultShortcut = definition%.defaultShortcut]], "") | |
-- PLUGIN SPECIFIC FIXES | |
if (pluginName == "DeveloperStorybook") then | |
src = src | |
:gsub([[local STORYBOOK_SOURCES]], [[local STORYBOOK_SOURCES = {"ReplicatedStorage","ReplicatedFirst","ServerScriptService","StarterGui","StarterPlayer"} local STORYBOOK_SOURCES_OLD]]) | |
elseif (pluginName == "DeveloperInspector") then | |
src = src | |
:gsub([[toggleProfile.Enabled = %w+]], "") | |
end | |
local success = pcall(function() | |
script.Source = src | |
end) | |
if not success then | |
warn("Failed to write source of module:", script:GetFullName()) | |
end | |
end | |
local function visitInstance(obj: Instance) | |
if (not (obj:IsA("ModuleScript") or obj:IsA("BaseScript"))) then return end | |
if (obj == script) then return end | |
if (obj.Name == "runTests") then | |
(obj :: Script).Disabled = true | |
return | |
end | |
rewriteFlagsForScript(obj :: ModuleScript) | |
end | |
local function run() | |
local instances = root:GetDescendants() | |
local instanceCount = #instances | |
print(("Demoting plugin: %s\nInstances to scan:%s"):format(pluginName, instanceCount)) | |
for _, obj in ipairs(instances) do | |
visitInstance(obj) | |
end | |
print(("Demoted plugin %s"):format(pluginName)) | |
end | |
run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment