local Selection = game:GetService("Selection") local HttpService = game:GetService("HttpService") local StarterPlayer = game:GetService("StarterPlayer") local StudioService = game:GetService("StudioService") local TextChatService = game:GetService("TextChatService") local classes = {} local outStream = "" local stackLevel = 0 local singletons = { Speaker = Instance.new("Sound"); -- close enough Terrain = workspace:WaitForChild("Terrain", 1000); ParabolaAdornment = Instance.new("BoxHandleAdornment"); -- close enough StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts"); StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts"); ChatWindowConfiguration = TextChatService:WaitForChild("ChatWindowConfiguration", 10); ChatInputBarConfiguration = TextChatService:WaitForChild("ChatInputBarConfiguration", 10); } local exceptionClasses = { PackageLink = true; ScriptDebugger = true; ChatWindowConfiguration = true; ChatInputBarConfiguration = true; } local numberTypes = { int = true; long = true; int64 = true; float = true; double = true; } local stringTypes = { string = true; Content = true; BinaryString = true; ProtectedString = true; } local isCoreScript = pcall(function () local restricted = game:GetService("RobloxPluginGuiService") return tostring(restricted) end) local function write(formatString, ...) local tabs = string.rep(' ', stackLevel * 4) local fmt = formatString or "" local value = tabs .. fmt:format(...) outStream = outStream .. value end local function writeLine(formatString, ...) if not formatString then outStream = outStream .. '\n' return end write(formatString .. '\n', ...) end local function openStack() writeLine('{') stackLevel += 1 end local function closeStack() stackLevel -= 1 writeLine('}') end local function clearStream() stackLevel = 0 outStream = "" end local function exportStream(label) local results = outStream:gsub("\n\n\n", "\n\n") local export if plugin then export = Instance.new("Script") export.Archivable = false export.Source = results export.Name = label export.Parent = workspace Selection:Add{export} end if isCoreScript then StudioService:CopyToClipboard(results) elseif not plugin then warn(label) print(results) else wait() plugin:OpenScript(export) end end local function getTags(object) local tags = {} if object.Tags ~= nil then for _,tag in pairs(object.Tags) do tags[tag] = true end end if object.Name == "Terrain" then tags.NotCreatable = nil end return tags end local function upcastInheritance(class, root) local superClass = classes[class.Superclass] if not superClass then return end if not root then root = class end if not superClass.Inherited then superClass.Inherited = root end upcastInheritance(superClass, root) end local function canCreateClass(class) local tags = getTags(class) local canCreate = true if tags.NotCreatable then canCreate = false end if tags.Service then canCreate = true end if tags.Settings then canCreate = false end if singletons[class.Name] then canCreate = true end return canCreate end local function collectProperties(class) local propMap = {} for _,member in ipairs(class.Members) do if member.MemberType == "Property" then local propName = member.Name propMap[propName] = member end end return propMap end local function createProperty(propName, propType) local category = "DataType"; local name = propType if propType:find(':') then local data = string.split(propType, ':') category = data[1] name = data[2] end return { Name = propName; Serialization = { CanSave = true; CanLoad = true; }; ValueType = { Category = category; Name = name; }; Security = "None"; } end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Formatting --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- type FormatFunc = (any) -> string; type Format = { [string]: FormatFunc } local formatting: Format = require(script.Formatting) local formatLinks = { ["int"] = "Int"; ["nil"] = "Null"; ["long"] = "Int"; ["float"] = "Float"; ["byte[]"] = "Bytes"; ["double"] = "Double"; ["boolean"] = "Bool"; ["string"] = "String"; ["Content"] = "String"; ["Instance"] = "Null"; ["Color3uint8"] = "Color3"; ["OptionalCFrame"] = "Null"; ["ProtectedString"] = "String"; } local function getFormatFunction(valueType: string): FormatFunc? if not formatting[valueType] then valueType = formatLinks[valueType] end return formatting[valueType] end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Property Patches --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- local patches = require(script.PropertyPatches) local patchIndex = {} function patchIndex:__index(key) if not rawget(self, key) then rawset(self, key, {}) end return self[key] end local function getPatches(className) local classPatches = patches[className] return setmetatable(classPatches, patchIndex) end setmetatable(patches, patchIndex) --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Main --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- local baseUrl = "https://raw.githubusercontent.com/CloneTrooper1019/Roblox-Client-Tracker/roblox/" local toolbar, button if plugin then toolbar = plugin:CreateToolbar("C# API Dump") button = toolbar:CreateButton( "Dump API", "Generates a C# dump of Roblox's Class/Enum API.", "rbxasset://textures/Icon_Stream_Off@2x.png" ) button.ClickableWhenViewportHidden = true end local function getAsync(url) local enabled if isCoreScript then enabled = HttpService:GetHttpEnabled() HttpService:SetHttpEnabled(true) end local result = HttpService:GetAsync(url) if isCoreScript then HttpService:SetHttpEnabled(enabled) end return result end local function generateClasses() local env = getfenv() local version = getAsync(baseUrl .. "version.txt") local apiDump = getAsync(baseUrl .. "API-Dump.json") apiDump = HttpService:JSONDecode(apiDump) local classNames = {} classes = {} local enumMap = { Axis = true; FontSize = true; FontStyle = true; FontWeight = true; } for _,class in ipairs(apiDump.Classes) do local className = class.Name local superClass = classes[class.Superclass] if singletons[className] then class.Singleton = true class.Object = singletons[className] end if superClass and canCreateClass(class) then local classTags = getTags(class) if classTags.Service then pcall(function () if not className:find("Network") then class.Object = game:GetService(className) end end) elseif not classTags.NotCreatable then pcall(function () local dumpFolder = game:FindFirstChild("DumpFolder") class.Object = Instance.new(className) if dumpFolder then local old = dumpFolder:FindFirstChildOfClass(className) if old then old:Destroy() end class.Object.Name = className class.Object.Parent = dumpFolder end end) end upcastInheritance(class) end classes[className] = class table.insert(classNames, className) end outStream = "" writeLine("// Auto-generated list of creatable Roblox classes.") writeLine("// Updated as of %s", version) writeLine() writeLine("using System;") writeLine() writeLine("using RobloxFiles.DataTypes;") writeLine("using RobloxFiles.Enums;") writeLine("using RobloxFiles.Utility;") writeLine() writeLine("#pragma warning disable IDE1006 // Naming Styles") writeLine() writeLine("namespace RobloxFiles") openStack() for i, className in ipairs(classNames) do local class = classes[className] local classTags = getTags(class) local registerClass = canCreateClass(class) local object = class.Object if class.Inherited then registerClass = true end if class.Name == "Instance" or class.Name == "Studio" then registerClass = false end local noSecurityCheck = pcall(function () if not classTags.Service then return tostring(object) end end) if not noSecurityCheck then object = nil end if not object then if class.Inherited then object = class.Inherited.Object elseif singletons[className] then object = singletons[className] else registerClass = false end end if exceptionClasses[class.Name] then registerClass = true end if registerClass then local objectType if classTags.NotCreatable and class.Inherited and not class.Singleton then objectType = "abstract class" else objectType = "class" end writeLine("public %s %s : %s", objectType, className, class.Superclass) openStack() local classPatches = getPatches(className) local redirectProps = classPatches.Redirect local propMap = collectProperties(class) local propNames = {} for _,propName in pairs(classPatches.Remove) do propMap[propName] = nil end for propName in pairs(propMap) do table.insert(propNames, propName) end for propName, propType in pairs(classPatches.Add) do local prop = propMap[propName] if prop then local serial = prop.Serialization serial.CanSave = true serial.CanLoad = true else propMap[propName] = createProperty(propName, propType) table.insert(propNames, propName) end end local firstLine = true table.sort(propNames) if classTags.Service then writeLine("public %s()", className) openStack() writeLine("IsService = true;") closeStack() end for j, propName in ipairs(propNames) do local prop = propMap[propName] local propTags = getTags(prop) local serial = prop.Serialization local typeData = prop.ValueType local category = typeData.Category local valueType = typeData.Name local redirect = redirectProps[propName] local couldSave = (serial.CanSave or propTags.Deprecated or redirect) if serial.CanLoad and couldSave then if firstLine and classTags.Service then writeLine() end local name = propName local default = "" if propName == className then name = name .. '_' end if valueType == "int64" then valueType = "long" elseif valueType == "BinaryString" then valueType = "byte[]" elseif valueType == "Font" and category ~= "Enum" then valueType = "FontFace" end local first = name:sub(1, 1) if first == first:lower() then local pascal = first:upper() .. name:sub(2) if propMap[pascal] ~= nil and propTags.Deprecated then redirect = pascal end end if redirect then local get, set, flag if typeof(redirect) == "string" then get = redirect set = redirect .. " = value" if redirect == "value" then set = "this." .. set end else get = redirect.Get set = redirect.Set flag = redirect.Flag end if not firstLine and set then writeLine() end if propTags.Deprecated then writeLine("[Obsolete]") end if set then if flag then writeLine("public %s %s %s", flag, valueType, name) else writeLine("public %s %s", valueType, name) end openStack() writeLine("get => %s;", get) if set:find('\n') then writeLine() writeLine("set") openStack() for line in set:gmatch("[^\r\n]+") do writeLine(line) end closeStack() else writeLine("set => %s;", set) end closeStack() else writeLine("public %s %s => %s;", valueType, name, get) end if j ~= #propNames and set then writeLine() end else local value = classPatches.Defaults[propName] local gotValue = (value ~= nil) if not gotValue then gotValue, value = pcall(function () return object[propName] end) end if not gotValue and category ~= "Class" then -- Fallback to implicit defaults if numberTypes[valueType] then value = 0 gotValue = true elseif stringTypes[valueType] then value = "" gotValue = true elseif valueType == "SharedString" then value = "yuZpQdnvvUBOTYh1jqZ2cA==" gotValue = true elseif category == "DataType" then local DataType = env[valueType] if DataType and typeof(DataType) == "table" and not rawget(env, valueType) then pcall(function () value = DataType.new() gotValue = true end) end elseif category == "Enum" then local enum = Enum[valueType] local lowestId = math.huge local lowest for _,item in pairs(enum:GetEnumItems()) do local itemValue = item.Value if itemValue < lowestId then lowest = item lowestId = itemValue end end if lowest then value = lowest gotValue = true end end local id = string.format("%s.%s", className, propName) local src = string.format("[%s]", script.Parent:GetFullName()) if gotValue then warn(src, "Fell back to implicit value for property:", id) else warn(src, "!! Could not figure out default value for property:", id, "value error was:", value) end end if gotValue then local formatKey = if category == "Enum" then "Enum" else valueType local formatFunc = getFormatFunction(formatKey) if not formatFunc then local literal = typeof(value) formatFunc = getFormatFunction(literal) end if not formatFunc then formatFunc = tostring end local result if formatFunc then if typeof(formatFunc) == "string" then result = formatFunc else result = formatFunc(value) end end if result ~= nil then default = " = " .. result end if formatFunc == formatting.EnumItem then local enumName = tostring(value.EnumType) enumMap[enumName] = true end end if className == "Sound" and propName == "EmitterSize" then -- .____. propTags.Deprecated = false end if propTags.Deprecated then if not firstLine then writeLine() end writeLine("[Obsolete]") end writeLine("public %s %s%s;", valueType, name, default) if propTags.Deprecated and j ~= #propNames then writeLine() end end firstLine = false end end closeStack() if (i ~= #classNames) then writeLine() end end end closeStack() exportStream("Classes") return enumMap end local function generateEnums(whiteList) local version = getfenv().version():gsub("%. ", ".") clearStream() writeLine("// Auto-generated list of Roblox enums.") writeLine("// Updated as of %s", version) writeLine() writeLine("namespace RobloxFiles.Enums") openStack() local enums = Enum:GetEnums() for i, enum in ipairs(enums) do local enumName = tostring(enum) if whiteList and not whiteList[enumName] then continue end writeLine("public enum %s", enumName) openStack() local enumItems = enum:GetEnumItems() local lastValue = -1 local mapped = {} table.sort(enumItems, function (a, b) return a.Value < b.Value end) for j, enumItem in ipairs(enumItems) do local text = "" local comma = ',' local name = enumItem.Name local value = enumItem.Value if not mapped[value] then if (value - lastValue) ~= 1 then text = " = " .. value; end if j == #enumItems then comma = "" end lastValue = value mapped[value] = true writeLine("%s%s%s", name, text, comma) end end closeStack() if i ~= #enums then writeLine() end end closeStack() exportStream("Enums") end local function generateAll() local enumList = generateClasses() generateEnums(enumList) end if plugin then button.Click:Connect(generateAll) else generateAll() end if game.Name:sub(1, 9) == "Null.rbxl" then generateAll() end