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 --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- local formatting: Format = require(script.Formatting) type FormatFunc = formatting.FormatFunc type Format = formatting.Format local formatLinks = { ["int"] = "Int", ["long"] = "Int", ["float"] = "Float", ["byte[]"] = "Bytes", ["double"] = "Double", ["boolean"] = "Bool", ["string"] = "String", ["Content"] = "String", ["Color3uint8"] = "Color3", ["ProtectedString"] = "String", } local function getFormatFunction(valueType: string): FormatFunc if not formatting[valueType] then valueType = formatLinks[valueType] end return formatting[valueType] or formatting.Null 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/MaximumADHD/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 class.PropertyMap = propMap local ancestor = class local diffProps = {} while object do ancestor = classes[ancestor.Superclass] if not ancestor then break end local inheritProps = ancestor.PropertyMap local inherited = ancestor.Inherited local baseObject = if inherited then inherited.Object else nil if inheritProps and baseObject then for name, prop in pairs(inheritProps) do local tags = getTags(prop) if tags.ReadOnly then continue end local gotPropValue, propValue = pcall(function() return object[name] end) local gotBaseValue, baseValue = pcall(function() return baseObject[name] end) if gotBaseValue and gotPropValue then if propValue ~= baseValue then diffProps[name] = propValue end end end end end if classTags.Service or next(diffProps) then local headerFormat = "public %s()" if next(diffProps) then headerFormat ..= " : base()" end writeLine(headerFormat, className) openStack() if classTags.Service then writeLine("IsService = true;") if next(diffProps) then writeLine() end end if next(diffProps) then local diffNames = {} for name in pairs(diffProps) do table.insert(diffNames, name) end table.sort(diffNames) for i, name in ipairs(diffNames) do local value = diffProps[name] local valueType = typeof(value) local formatFunc = getFormatFunction(valueType) if formatFunc ~= formatting.Null then local result = formatFunc(value) if result == "" then result = tostring(value) end writeLine("%s = %s;", name, result) end end end closeStack() end table.sort(propNames) 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 or next(diffProps)) 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 formatFunc == formatting.Null then local literal = typeof(value) formatFunc = getFormatFunction(literal) end local result if formatFunc then if typeof(formatFunc) == "string" then result = formatFunc else result = formatFunc(value) end end if result == "" then result = nil 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