diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0ed398f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing + +## Guidelines +* Adding a new function can be done by copying TEMPLATE.md, renaming it, and editing it as needed. +* You may provide edits of existing functions, just edit and submit it. +* When possible, organise files in their respective category folders. +* When writing the function signature, please make sure it is **valid typed Luau syntax**. Documentation for typed luau can be found [here](https://luau-lang.org/typecheck#union-types). +* When referencing arguments of a function in the description, please use `` in order to make `it look nice like so`. +* Please confirm a function has not already been added to the API before you submit it. + +* Functions must be named appropriately, if you are contributing one - the following criteria applies for the naming: + * No brand names should be visible in your documentation. + * This also includes function alias' - UNS aims to be a vanilla naming convention, not a branded one + * The function name must be **descriptive of what the function does**. + * Aliases for shortening function names without good reason are *not* allowed. For example, `hookfunc` is not a alias supported by UNC. Function names should be written out in full. + +* The description must be coherent, if the function is basic enough to not warrant one - you may put "N/A" in the description. +* Functions don't always require an alias, you may just also put N/A in that field. + +## Submitting your edits and/or additions +This can be done through [github pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). Clone or Fork the repository, make your changes, then submit a pull request. The pull request will be reviewed by the UNS council (aka [Master Oogway](https://discord.com/users/830031741225009203)) of the UNS before it is merged. \ No newline at end of file diff --git a/EnvCheck.luau b/EnvCheck.luau new file mode 100644 index 0000000..d9b0d70 --- /dev/null +++ b/EnvCheck.luau @@ -0,0 +1,1083 @@ +local passes, fails, undefined = 0, 0, 0 +local running = 0 + +local function getGlobal(path) + local value = getfenv(0) + + while value ~= nil and path ~= "" do + local name, nextValue = string.match(path, "^([^.]+)%.?(.*)$") + value = value[name] + path = nextValue + end + + return value +end + +local function test(name, aliases, callback) + if rconsoleinfo then + rconsoleinfo(name) -- debug which function thats going to get tested + end + running += 1 + + task.spawn(function() + if not callback then + print(`⏺️ {name}`) + elseif not getGlobal(name) then + fails += 1 + warn(`⛔ {name}`) + else + local success, message = pcall(callback) + + if success then + passes += 1 + print(`✅ {name}{message and ` • {message}` or ""}`) + else + fails += 1 + warn(`⛔ {name} failed: {message}`) + end + end + + local undefinedAliases = {} + + for _, alias in next, aliases do + if getGlobal(alias) == nil then + undefinedAliases[#undefinedAliases + 1] = alias + end + end + + if #undefinedAliases > 0 then + undefined += 1 + warn(`⚠️ {table.concat(undefinedAliases, ", ")}`) + end + + running -= 1 + end) +end + +-- Header and summary + +print("\n") + +print("UNS Environment Check") +print("✅ - Pass, ⛔ - Fail, ⏺️ - No test, ⚠️ - Missing aliases\n") + +task.defer(function() + repeat task.wait() until running == 0 + + local total = passes + fails + warn(passes / total) + local rate = math.round(passes / total * 10000) / 100 + local outOf = `{passes} out of {total}` + + print("\n") + + print("UNS Summary") + print(`✅ Tested with a {rate}% success rate ({outOf})`) + print(`⛔ {fails} tests failed`) + print(`⚠️ {undefined} globals are missing aliases`) +end) + +-- Cache + +test("cache.invalidate", {}, function() + local container = Instance.new("Folder") + local part = Instance.new("Part", container) + cache.invalidate(container:FindFirstChild("Part")) + assert(part ~= container:FindFirstChild("Part"), "Reference `part` could not be invalidated") +end) + +test("cache.iscached", {}, function() + local part = Instance.new("Part") + assert(cache.iscached(part), "Part should be cached") + cache.invalidate(part) + assert(not cache.iscached(part), "Part shouldn't be cached") +end) + +test("cache.replace", {}, function() + local part = Instance.new("Part") + local fire = Instance.new("Fire") + cache.replace(part, fire) + assert(part ~= fire, "Part was not replaced with Fire") +end) + +test("clonereference", {"cloneref"}, function() + local part = Instance.new("Part") + local clone = clonereference(part) + assert(part ~= clone, "Clone shouldn't be equal to original") + clone.Name = "Test" + assert(part.Name == "Test", "Clone should have updated the original") +end) + +test("compareinstances", {}, function() + local part = Instance.new("Part") + local clone = clonereference(part) + assert(part ~= clone, "Clone shouldn't be equal to original") + assert(compareinstances(part, clone), "Clone should be equal to original when using compareinstances()") +end) + +-- Closures + +local function shallowEqual(t1, t2) + if t1 == t2 then + return true + end + + local UNIQUE_TYPES = { + ["function"] = true, + ["table"] = true, + ["userdata"] = true, + ["thread"] = true, + } + + for k, v in next, t1 do + if UNIQUE_TYPES[type(v)] then + if type(t2[k]) ~= type(v) then + return false + end + elseif t2[k] ~= v then + return false + end + end + + for k, v in next, t2 do + if UNIQUE_TYPES[type(v)] then + if type(t2[k]) ~= type(v) then + return false + end + elseif t1[k] ~= v then + return false + end + end + + return true +end + +test("checkcaller", {}, function() + assert(checkcaller(), "Main scope should return true") +end) + +test("clonefunction", {"clonefunc"}, function() + local function test() + return "success" + end + local copy = clonefunction(test) + assert(test() == copy(), "The clone should return the same value as the original") + assert(test ~= copy, "The clone shouldn't be equal to the original") +end) + +test("getcallingscript", {}) + +test("getscriptclosure", {"getscriptfunction"}, function() + local module = game:GetService("CoreGui").RobloxGui.Modules.Common.Constants + local constants = getrenv().require(module) + local generated = getscriptclosure(module)() + assert(constants ~= generated, "Generated module shouldn't match the original") + assert(shallowEqual(constants, generated), "Generated constant table should be shallow equal to the original") +end) + +test("hookfunction", {"replaceclosure"}, function() + local function test() + return true + end + local ref = hookfunction(test, function() + return false + end) + assert(test() == false, "Function should return false") + assert(ref() == true, "Original function should return true") + assert(test ~= ref, "Original function shouldn't be same as the reference") +end) + +test("isfunctionhooked", {}, function() + local function test() + return true + end + local ref = hookfunction(test, function() + return false + end) + assert(test() == false, "Function should return false") + assert(ref() == true, "Original function should return true") + assert(test ~= ref, "Original function shouldn't be same as the reference") + assert(isfunctionhooked(test), "Function should be marked as hooked") +end) + +test("restorefunction", {}, function() + local function test() + return true + end + local ref = hookfunction(test, function() + return false + end) + assert(test() == false, "Function should return false") + assert(ref() == true, "Original function should return true") + assert(test ~= ref, "Original function shouldn't be same as the reference") + assert(isfunctionhooked(test), "Function should be marked as hooked") + restorefunction(test) + assert(test() ~= false, "Function should return false") + assert(ref() ~= true, "Original function should return true") + assert(not isfunctionhooked(test), "Function shouldn't be marked as hooked anymore") +end) + +test("hooksignal", {"replacecon"}, function() + local part, changedPropNewName = Instance.new("Part") + part.Changed:Connect(function(prop) + changedPropNewName = prop + end) + hooksignal(part.Changed, function(info, prop) + return true, "Hooked" + end) + part.Name = "NewName" + assert(changedPropNewName == "Hooked", "Signal should be hook") +end) + +test("issignalhooked", {}, function() + local part, changedPropNewName = Instance.new("Part") + part.Changed:Connect(function(prop) + changedPropNewName = prop + end) + hooksignal(part.Changed, function(info, prop) + return true, "Hooked" + end) + part.Name = "NewName" + assert(changedPropNewName == "Hooked", "Signal should be hook") + assert(issignalhooked(part.Changed), "Signal should be marked as hooked") +end) + +test("restoresignal", {}, function() + local part, changedPropNewName = Instance.new("Part") + part.Changed:Connect(function(prop) + changedPropNewName = prop + end) + hooksignal(part.Changed, function(info, prop) + return true, "Hooked" + end) + part.Name = "NewName" + assert(changedPropNewName == "Hooked", "Signal should be hook") + assert(issignalhooked(part.Changed), "Signal should be marked as hooked") + restoresignal(part.Changed) + part.Name = "NewName2" + assert(changedPropNewName ~= "Hooked", "Signal shouldn't be hook") + assert(not issignalhooked(part.Changed), "Signal shouldn't be marked as hooked") +end) + +test("iscclosure", {"iscc"}, function() + assert(iscclosure(print) == true, "Function 'print' should be a C closure") + assert(iscclosure(function() end) == false, "Executor function shouldn't be a C closure") +end) + +test("islclosure", {"islc"}, function() + assert(islclosure(print) == false, "Function 'print' shouldn't be a Lua closure") + assert(islclosure(function() end) == true, "Executor function should be a Lua closure") +end) + +test("isexecutorclosure", {"checkclosure", "isourclosure"}, function() + assert(isexecutorclosure(isexecutorclosure) == true, "Did not return true for an executor global") + assert(isexecutorclosure(newcclosure(function() end)) == true, "Did not return true for an executor C closure") + assert(isexecutorclosure(function() end) == true, "Did not return true for an executor Luau closure") + assert(isexecutorclosure(print) == false, "Did not return false for a Roblox global") +end) + +test("loadstring", {}, function() + local bytecode = "\003\002\005print\vHello World\001\002\000\000\001\006A\000\000\000\f\000\001\000\000\000\000@\005\001\002\000\021\000\002\001\022\000\001\000\003\003\001\004\000\000\000@\003\002\000\001\000\001\024\000\000\000\000\000\001\001\000\000\000\000\000" -- luau version of print("Hello World") + local func = loadstring(bytecode) + assert(type(func) ~= "function", "Luau bytecode shouldn't be loadable!") + assert(assert(loadstring("return ... + 1"))(1) == 2, "Failed to do simple math") + assert(type(select(2, loadstring("f"))) == "string", "Loadstring did not return anything for a compiler error") +end) + +test("newcclosure", {}, function() + local function test() + return true + end + local testC = newcclosure(test) + assert(test() == testC(), "New C closure should return the same value as the original") + assert(test ~= testC, "New C closure shouldn't be same as the original") + assert(iscclosure(testC), "New C closure should be a C closure") +end) + +-- Console + +test("rconsoleclear", {"consoleclear"}) + +test("rconsolecreate", {"consolecreate"}) + +test("rconsoledestroy", {"consoledestroy"}) + +test("rconsoleinput", {"consoleinput"}) + +test("rconsoleprint", {"consoleprint"}) + +test("rconsoleinfo", {"consoleinfo"}) + +test("rconsoleerror", {"consoleerror"}) + +test("rconsolesettitle", {"rconsolename", "consolesettitle"}) + +-- Crypt + +test("crypt.base64encode", {"crypt.base64.encode", "crypt.base64_encode", "base64.encode", "base64_encode"}, function() + assert(crypt.base64encode("test") == "dGVzdA==", "Base64 encoding failed") +end) + +test("crypt.base64decode", {"crypt.base64.decode", "crypt.base64_decode", "base64.decode", "base64_decode"}, function() + assert(crypt.base64decode("dGVzdA==") == "test", "Base64 decoding failed") +end) + +test("crypt.encrypt", {}, function() + local key = crypt.generatekey() + local encrypted, iv = crypt.encrypt("test", key, nil, "CBC") + assert(iv, "crypt.encrypt should return an IV") + local decrypted = crypt.decrypt(encrypted, key, iv, "CBC") + assert(decrypted == "test", "Failed to decrypt raw string from encrypted data") +end) + +test("crypt.decrypt", {}, function() + local key, iv = crypt.generatekey(), crypt.generatekey() + local encrypted = crypt.encrypt("test", key, iv, "CBC") + local decrypted = crypt.decrypt(encrypted, key, iv, "CBC") + assert(decrypted == "test", "Failed to decrypt raw string from encrypted data") +end) + +test("crypt.generatebytes", {}, function() + local size = math.random(10, 100) + local bytes = crypt.generatebytes(size) + assert(#crypt.base64decode(bytes) == size, `The decoded result should be {size} bytes long (got {#crypt.base64decode(bytes)} decoded, {#bytes} raw)`) +end) + +test("crypt.generatekey", {}, function() + assert(#crypt.base64decode(crypt.generatekey()) == 32, "crypt.generatekey should be 32 bytes long when decoded") +end) + +test("crypt.hash", {}, function() + local algorithms = { + ["sha1"] = "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", + ["sha224"] = "90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809", + ["sha256"] = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", + ["sha384"] = "768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9", + ["sha512"] = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + ["sha3-224"] = "3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b", + ["sha3-256"] = "36f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80", + ["sha3-384"] = "e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd", + ["sha3-512"] = "9ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14", + ["md5"] = "098f6bcd4621d373cade4e832627b4f6", + ["halfmd5"] = "098f6bcd4621d373", + ["doublemd5"] = "fb469d7ef430b0baf0cab6c436e70375", + ["ripemd160"] = "5e52fee47e6b070565f74372468cdc699de89107", + ["ripemd256"] = "fe0289110d07daeee9d9500e14c57787d9083f6ba10e6bcb256f86bb4fe7b981", + ["ripemd320"] = "3b0a2e841e589cf583634a5dd265d2b5d497c4cc44b241e34e0f62d03e98c1b9dc72970b9bc20eb5", + ["keccak-224"] = "3be30a9ff64f34a5861116c5198987ad780165f8366e67aff4760b5e", + ["keccak-256"] = "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658", + ["keccak-384"] = "53d0ba137307d4c2f9b6674c83edbd58b70c0f4340133ed0adc6fba1d2478a6a03b7788229e775d2de8ae8c0759d0527", + ["keccak-512"] = "1e2e9fc2002b002d75198b7503210c05a1baac4560916a3c6d93bcce3a50d7f00fd395bf1647b9abb8d1afcc9c76c289b0c9383ba386a956da4b38934417789e", + ["shake128"] = "d3b0aa9cd8b7255622cebc631e867d4093d6f6010191a53973c45fec9b07c774", + ["shake256"] = "b54ff7255705a71ee2925e4a3e30e41aed489a579d5595e0df13e32e1e4dd202a7c7f68b31d6418d9845eb4d757adda6ab189e1bb340db818e5b3bc725d992fa" + } + + for algorithm, hashed_value in next, algorithms do + local hash = crypt.hash("test", algorithm) + assert(hash == hashed_value, `crypt.hash on algorithm '{algorithm}' returned the wrong hash`) + end +end) + +--- Debug + +test("debug.setmetatable", {"setrawmetatable"}, function() + local object = setmetatable({}, { __index = function() return false end, __metatable = "Locked!" }) + local objectReturned = debug.setmetatable(object, { __index = function() return true end }) + assert(object, "Did not return the original object") + assert(object.test == true, "Failed to change the metatable") + if objectReturned then + return objectReturned == object and "Returned the original object" or "Did not return the original object" + end +end) + +test("debug.getmetatable", {"getrawmetatable"}, function() + local metatable = { __metatable = "Locked!" } + local object = setmetatable({}, metatable) + assert(debug.getmetatable(object) == metatable, "Did not return the metatable") +end) + +test("debug.getconstant", {"getconstant"}, function() + local function test() + print("Hello, world!") + end + assert(debug.getconstant(test, 1) == "print", "First constant must be print") + assert(debug.getconstant(test, 2) == nil, "Second constant must be nil") + assert(debug.getconstant(test, 3) == "Hello, world!", "Third constant must be 'Hello, world!'") +end) + +test("debug.getconstants", {"getconstants"}, function() + local function test() + local num = 5000 .. 50000 + print("Hello, world!", num, warn) + end + local constants = debug.getconstants(test) + assert(constants[1] == 50000, "First constant must be 50000") + assert(constants[2] == "print", "Second constant must be print") + assert(constants[3] == nil, "Third constant must be nil") + assert(constants[4] == "Hello, world!", "Fourth constant must be 'Hello, world!'") + assert(constants[5] == "warn", "Fifth constant must be warn") +end) + +test("debug.getinfo", {"getinfo"}, function() + local types = { + source = "string", + short_src = "string", + func = "function", + what = "string", + currentline = "number", + name = "string", + nups = "number", + numparams = "number", + is_vararg = "number", + } + local function test(...) + print(...) + end + local info = debug.getinfo(test) + for k, v in next, types do + assert(info[k] ~= nil, `Did not return a table with a '{k}' field`) + assert(type(info[k]) == v, `Did not return a table with {k} as a {v} (got {type(info[k])})`) + end +end) + +test("debug.getproto", {"getproto"}, function() + local function test() + local function proto() + return true + end + end + local proto = debug.getproto(test, 1, true)[1] + local realproto = debug.getproto(test, 1) + assert(proto, "Failed to get the inner function") + assert(proto() == true, "The inner function did not return anything") + if not realproto() then + return "Proto return values are disabled on this executor" + end +end) + +test("debug.getprotos", {"getprotos"}, function() + local function test() + local function _1() + return true + end + local function _2() + return true + end + local function _3() + return true + end + end + for i in next, debug.getprotos(test) do + local proto = debug.getproto(test, i, true)[1] + local realproto = debug.getproto(test, i) + assert(proto(), `Failed to get inner function {i}`) + if not realproto() then + return "Proto return values are disabled on this executor" + end + end +end) + +test("debug.getstack", {"getstack"}, function() + local _ = "a" .. "b" + assert(debug.getstack(1, 1) == "ab", "The first item in the stack should be 'ab'") + assert(debug.getstack(1)[1] == "ab", "The first item in the stack table should be 'ab'") +end) + +test("debug.getupvalue", {"getupvalue"}, function() + local upvalue = function() end + local function test() + print(upvalue) + end + assert(debug.getupvalue(test, 1) == upvalue, "Unexpected value returned from debug.getupvalue") +end) + +test("debug.getregistery", {"getregistery", "getreg"}, function() + local reg = debug.getregistery() + assert(type(reg) == "table", "Did not return a table") + assert(#reg > 0, "Did not return a table with any values") +end) + +test("debug.getupvalues", {"getupvalues"}, function() + local upvalue = function() end + local function test() + print(upvalue) + end + local upvalues = debug.getupvalues(test) + assert(upvalues[1] == upvalue, "Unexpected value returned from debug.getupvalues") +end) + +test("debug.setconstant", {"setconstant"}, function() + local function test() + return "fail" + end + debug.setconstant(test, 1, "success") + assert(test() == "success", "debug.setconstant did not set the first constant") +end) + +test("debug.setstack", {"setstack"}, function() + local function test() + return "fail", debug.setstack(1, 1, "success") + end + assert(test() == "success", "debug.setstack did not set the first stack item") +end) + +test("debug.setupvalue", {"setupvalue"}, function() + local function upvalue() + return "fail" + end + local function test() + return upvalue() + end + debug.setupvalue(test, 1, function() + return "success" + end) + assert(test() == "success", "debug.setupvalue did not set the first upvalue") +end) + +-- Filesystem + +if isfolder and makefolder and delfolder then + if isfolder(".tests") then + delfolder(".tests") + end + makefolder(".tests") +end + +test("readfile", {}, function() + writefile(".tests/readfile.txt", "success") + assert(readfile(".tests/readfile.txt") == "success", "Did not return the contents of the file") +end) + +test("listfiles", {}, function() + makefolder(".tests/listfiles") + writefile(".tests/listfiles/test_1.txt", "success") + writefile(".tests/listfiles/test_2.txt", "success") + local files = listfiles(".tests/listfiles") + assert(#files == 2, "Did not return the correct number of files") + assert(isfile(files[1]), "Did not return a file path") + assert(readfile(files[1]) == "success", "Did not return the correct files") + makefolder(".tests/listfiles_2") + makefolder(".tests/listfiles_2/test_1") + makefolder(".tests/listfiles_2/test_2") + local folders = listfiles(".tests/listfiles_2") + assert(#folders == 2, "Did not return the correct number of folders") + assert(isfolder(folders[1]), "Did not return a folder path") +end) + +test("writefile", {}, function() + writefile(".tests/writefile.txt", "success") + assert(readfile(".tests/writefile.txt") == "success", "Did not write the file") + local requiresFileExt = pcall(function() + writefile(".tests/writefile", "success") + assert(isfile(".tests/writefile.txt")) + end) + if not requiresFileExt then + return "This executor requires a file extension in writefile" + end +end) + +test("makefolder", {}, function() + makefolder(".tests/makefolder") + assert(isfolder(".tests/makefolder"), "Did not create the folder") +end) + +test("appendfile", {}, function() + writefile(".tests/appendfile.txt", "su") + appendfile(".tests/appendfile.txt", "cce") + appendfile(".tests/appendfile.txt", "ss") + assert(readfile(".tests/appendfile.txt") == "success", "Did not append the file") +end) + +test("isfile", {}, function() + writefile(".tests/isfile.txt", "success") + assert(isfile(".tests/isfile.txt") == true, "Did not return true for a file") + assert(isfile(".tests") == false, "Did not return false for a folder") + assert(isfile(".tests/doesnotexist.exe") == false, `Did not return false for a nonexistent path (got {isfile(".tests/doesnotexist.exe")})`) +end) + +test("isfolder", {}, function() + assert(isfolder(".tests") == true, "Did not return false for a folder") + assert(isfolder(".tests/doesnotexist.exe") == false, `Did not return false for a nonexistent path (got {isfolder(".tests/doesnotexist.exe")})`) +end) + +test("delfolder", {}, function() + makefolder(".tests/delfolder") + delfolder(".tests/delfolder") + assert(isfolder(".tests/delfolder") == false, `Failed to delete folder (isfolder = {isfolder(".tests/delfolder")})`) +end) + +test("delfile", {}, function() + writefile(".tests/delfile.txt", "Hello, world!") + delfile(".tests/delfile.txt") + assert(isfile(".tests/delfile.txt") == false, `Failed to delete folder (isfile = {isfile(".tests/delfolder")})`) +end) + +test("loadfile", {}, function() + writefile(".tests/loadfile.txt", "return ... + 1") + assert(assert(loadfile(".tests/loadfile.txt"))(1) == 2, "Failed to load a file with arguments") + writefile(".tests/loadfile.txt", "f") + local callback, err = loadfile(".tests/loadfile.txt") + assert(err and not callback, "Did not return an error message for a compiler error") +end) + +test("dofile", {}, function() + writefile(".tests/dofile.txt", "getfenv().__UNS_DOFILE_TEST = true return 'UNC on Top!'") + local Response = dofile(".tests/dofile.txt") + local DidFile = getfenv().__UNS_DOFILE_TEST + getfenv().__UNS_DOFILE_TEST = nil + assert(DidFile, "Did not execute the contents of the file") + assert(Response == "UNC on Top!", "Did return anything or the wrong value") +end) + +-- Input + +test("isrbxactive", {"isgameactive"}, function() + assert(type(isrbxactive()) == "boolean", "Did not return a boolean value") +end) + +test("mouse1click", {}) + +test("mouse1press", {}) + +test("mouse1release", {}) + +test("mouse2click", {}) + +test("mouse2press", {}) + +test("mouse2release", {}) + +test("mousemoveabs", {}) + +test("mousemoverel", {}) + +test("mousescroll", {}) + +-- Instances + +test("fireclickdetector", {}, function() + local detector = Instance.new("ClickDetector") + fireclickdetector(detector, 50, "MouseHoverEnter") +end) + +test("firesignal", {}, function() + local btn, MovedWheelForward = Instance.new("TextButton"), false + btn.MouseWheelForward:Once(function(x, y) + MovedWheelForward = x == -51.2151 and y == 515.1251 + end) + firesignal(btn.MouseWheelForward, -51.2151, 515.1251) + assert(MovedWheelForward, "Did not correctly fire the MouseWheelForward Signal of TextButton") +end) + +test("getcallbackvalue", {}, function() + local bindable = Instance.new("BindableFunction") + local function test() + end + bindable.OnInvoke = test + assert(getcallbackvalue(bindable, "OnInvoke") == test, "Did not return the correct value") +end) + +test("getconnections", {}, function() + local types = { + Enabled = "boolean", + ForeignState = "boolean", + LuaConnection = "boolean", + Function = "function", + Thread = "thread", + Fire = "function", + Defer = "function", + Disconnect = "function", + Disable = "function", + Enable = "function", + } + game.Players.LocalPlayer.Event:Connect(function() end) + local connection = getconnections(game.Players.LocalPlayer.Event)[1] + + for k, v in next, types do + assert(connection[k] ~= nil, `Did not return a table with a '{k}' field`) + assert(type(connection[k]) == v, `Did not return a table with {k} as a {v} (got {type(connection[k])})`) + end +end) + +test("getcustomasset", {}, function() + writefile(".tests/getcustomasset.txt", "success") + local contentId = getcustomasset(".tests/getcustomasset.txt") + assert(type(contentId) == "string", "Did not return a string") + assert(#contentId > 0, "Returned an empty string") + assert(string.match(contentId, "rbxasset://") == "rbxasset://", "Did not return an rbxasset url") +end) + +test("gethiddenproperty", {}, function() + local fire = Instance.new("Fire") + local property, isHidden = gethiddenproperty(fire, "size_xml") + assert(property == 5, "Did not return the correct value") + assert(isHidden == true, "Did not return whether the property was hidden") +end) + +test("getproperties", {"getprops"}, function() + local part = Instance.new("Part") + local props = getproperties(part) + assert(props.Position ~= part.Position, "Did not return the correct value") +end) + +test("sethiddenproperty", {}, function() + local fire = Instance.new("Fire") + local hidden = sethiddenproperty(fire, "size_xml", 10) + assert(hidden, "Did not return true for the hidden property") + assert(gethiddenproperty(fire, "size_xml") == 10, "Did not set the hidden property") +end) + +test("gethui", {}, function() + assert(typeof(gethui()) == "Instance" and gethui().Parent == game:GetService("CoreGui"), "Did not return an Instance") +end) + +test("getinstances", {}, function() + assert(getinstances()[1]:IsA("Instance"), "The first value is not an Instance") +end) + +test("getnilinstances", {}, function() + assert(getnilinstances()[1]:IsA("Instance"), "The first value is not an Instance") + assert(getnilinstances()[1].Parent == nil, "The first value is not parented to nil") +end) + +test("getinstancecache", {}, function() + assert(getinstancecache()[1]:IsA("Instance"), "The first value is not an Instance") +end) + +test("isscriptable", {}, function() + local fire = Instance.new("Fire") + assert(isscriptable(fire, "size_xml") == false, "Did not return false for a non-scriptable property (size_xml)") + assert(isscriptable(fire, "Size") == true, "Did not return true for a scriptable property (Size)") +end) + +test("setscriptable", {}, function() + local fire = Instance.new("Fire") + local wasScriptable = setscriptable(fire, "size_xml", true) + assert(wasScriptable == false, "Did not return false for a non-scriptable property (size_xml)") + assert(isscriptable(fire, "size_xml") == true, "Did not set the scriptable property") +end) + +-- Metatable + +test("hookmetamethod", {}, function() + local object = setmetatable({}, { __index = newcclosure(function() return false end), __metatable = "Locked!" }) + local ref = hookmetamethod(object, "__index", function() return true end) + assert(object.test == true, "Failed to hook a metamethod and change the return value") + assert(ref() == false, "Did not return the original function") +end) + +test("getnamecallmethod", {}, function() + local method + local ref + ref = hookmetamethod(game, "__namecall", function(...) + if not method then + method = getnamecallmethod() + end + return ref(...) + end) + game:GetService("Lighting") + assert(method == "GetService", "Did not get the correct method (GetService)") +end) + +test("isreadonly", {}, function() + local object = {} + table.freeze(object) + assert(isreadonly(object), "Did not return true for a read-only table") +end) + +test("setreadonly", {}, function() + local object = { success = false } + table.freeze(object) + setreadonly(object, false) + object.success = true + assert(object.success, "Did not allow the table to be modified") +end) + +-- Miscellaneous + +test("identifyexecutor", {"getexecutorname"}, function() + local name, version = identifyexecutor() + assert(type(name) == "string", "Did not return a string for the name") + return type(version) == "string" and "Returns version as a string" or "Does not return version" +end) + +test("lz4compress", {"lz4c"}, function() + local raw = "Hello, world!" + local compressed = lz4compress(raw) + assert(type(compressed) == "string", "Compression did not return a string") + assert(lz4decompress(compressed, #raw) == raw, "Decompression did not return the original string") +end) + +test("lz4decompress", {"lz4d"}, function() + local raw = "Hello, world!" + local compressed = lz4compress(raw) + assert(type(compressed) == "string", "Compression did not return a string") + assert(lz4decompress(compressed, #raw) == raw, "Decompression did not return the original string") +end) + +test("gethwid", {}, function() + local hwid = gethwid() + assert(type(hwid) == "string", "Did not return a string for the hwid") + +end) + +test("messagebox", {}) + +test("request", {"http.request", "http_request"}, function() + local response = request({ + Url = "http://httpbin.org/user-agent", + Method = "GET", + }) + assert(type(response) == "table", "Response must be a table") + assert(response.StatusCode == 200, "Did not return a 200 status code") + local data = game:GetService("HttpService"):JSONDecode(response.Body) + assert(type(data) == "table" and type(data["user-agent"]) == "string", "Did not return a table with a user-agent key") + return `User-Agent: {data["user-agent"]}` +end) + +test("setfpscap", {}, function() + local renderStepped = game:GetService("RunService").RenderStepped + local function step() + renderStepped:Wait() + local sum = 0 + for _ = 1, 5 do + sum += 1 / renderStepped:Wait() + end + return math.round(sum / 5) + end + setfpscap(60) + local step60 = step() + setfpscap(20) + local step0 = step() + return `{step60}fps @60 • {step0}fps @20` +end) + +test("getfpscap", {}) + +test("isnetworkowner", {"isowner"}, function() + assert(isnetworkowner(game.Players.LocalPlayer.Character.HumanoidRootPart), "Did not return true") +end) + +-- Scripts + +test("getgc", {}, function() + local gc = getgc() + assert(type(gc) == "table", "Did not return a table") + assert(#gc > 0, "Did not return a table with any values") +end) + +test("filtergc", {}, function() + local tbl = { + UNCs = "Testing..." + } + local metatbl = {__idiv = function() return 0.1515 end, __div = function() return "Ballers" end, __metatable = "Locked for testing UNS..."} + setmetatable({}, metatbl) + local filtered_gc = filtergc("table", { + KeyValuePairs = tbl, + Keys = {"UNCs"}, + Values = {"Testing..."}, + Metatable = metatbl + }) + + assert(#filtered_gc > 0, "Did not return a table with the filtered table(s)") + assert(type(filtered_gc[1]) == "table", "Did not return a table") + assert(filtered_gc[1] == tbl, "Did not return the correct table") +end) + +test("getgenv", {}, function() + getgenv().__TEST_GLOBAL = true + assert(__TEST_GLOBAL, "Failed to set a global variable") + getgenv().__TEST_GLOBAL = nil +end) + +test("getloadedmodules", {}, function() + local modules = getloadedmodules() + assert(type(modules) == "table", "Did not return a table") + assert(#modules > 0, "Did not return a table with any values") + assert(typeof(modules[1]) == "Instance", "First value is not an Instance") + assert(modules[1]:IsA("ModuleScript"), "First value is not a ModuleScript") +end) + +test("getrenv", {}, function() + assert(shared ~= getrenv().shared, "The variable shared in the executor is identical to shared in the game") +end) + +test("getrunningscripts", {}, function() + local scripts = getrunningscripts() + assert(type(scripts) == "table", "Did not return a table") + assert(#scripts > 0, "Did not return a table with any values") + assert(typeof(scripts[1]) == "Instance", "First value is not an Instance") + assert(scripts[1]:IsA("ModuleScript") or scripts[1]:IsA("LocalScript"), "First value is not a ModuleScript or LocalScript") +end) + +test("getscriptbytecode", {"dumpstring"}, function() + local animate = game:GetService("Players").LocalPlayer.Character.Animate + local bytecode = getscriptbytecode(animate) + assert(type(bytecode) == "string", `Did not return a string for Character.Animate (a {animate.ClassName})`) +end) + +test("getscripthash", {}, function() + local animate = game:GetService("Players").LocalPlayer.Character.Animate:Clone() + local hash = getscripthash(animate) + local source = animate.Source + animate.Source = "print('Hello, world!')" + task.defer(function() + animate.Source = source + end) + local newHash = getscripthash(animate) + assert(hash ~= newHash, "Did not return a different hash for a modified script") + assert(newHash == getscripthash(animate), "Did not return the same hash for a script with the same source") +end) + +test("getscriptthread", {}, function() + assert(typeof(getscriptthread(game:GetService("Players").LocalPlayer.Character.Animate)) == "thread", "Did not return a thread") + assert(getscriptthread(script) == nil, "Returned a thread for the executor's script") +end) + +test("getscripts", {}, function() + local scripts = getscripts() + assert(type(scripts) == "table", "Did not return a table") + assert(#scripts > 0, "Did not return a table with any values") + assert(typeof(scripts[1]) == "Instance", "First value is not an Instance") + assert(scripts[1]:IsA("ModuleScript") or scripts[1]:IsA("LocalScript"), "First value is not a ModuleScript or LocalScript") +end) + +test("getsenv", {}, function() + local animate = game:GetService("Players").LocalPlayer.Character.Animate + local env = getsenv(animate) + assert(type(env) == "table", `Did not return a table for Character.Animate (a {animate.ClassName})`) + assert(env.script == animate, "The script global is not identical to Character.Animate") +end) + +test("gettenv", {}, function() + local Thread = task.defer(function() end) + local env = gettenv(Thread) + assert(type(env) == "table", `Did not return a table for a Thread`) + assert(env.script == script, "The script global is not identical to executor's one") +end) + +test("getthreadidentity", {"getidentity", "getthreadcontext"}, function() + assert(type(getthreadidentity()) == "number", "Did not return a number") +end) + +test("setthreadidentity", {"setidentity", "setthreadcontext"}, function() + local oldidentity = getthreadidentity() + setthreadidentity(3) + assert(getthreadidentity() == 3, "Did not set the thread identity") + setthreadidentity(oldidentity) + assert(getthreadidentity() == oldidentity, "Did not reset the thread identity") +end) + +-- Drawing Library + +test("Drawing", {}) + +test("Drawing.new", {}, function() + local drawing = Drawing.new("Square") + if isrenderobj == nil then + notes["Drawing.new"] = "Exploit should support isrenderobj" + else + assert(isrenderobj(drawing) == true, "Did not return a valid render object or the 'isrenderobj' function returned the wrong value") + end + drawing.Visible = false + local canDestroy = pcall(function() + drawing:Destroy() + end) + drawing = Drawing.new("Square") + drawing.Visible = false + local canRemove = pcall(function() + drawing:Remove() + end) + assert(canDestroy, "Drawing:Destroy() shouldn't throw an error") + assert(canRemove, "Drawing:Remove() shouldn't throw an error") +end) + +test("Drawing.Fonts", {}, function() + assert(Drawing.Fonts.UI == 0, `Did not return the correct id '0' for Fonts.UI, got '{Drawing.Fonts.UI}'`) + assert(Drawing.Fonts.System == 1, `Did not return the correct id '1' for Fonts.System, got '{Drawing.Fonts.System}'`) + assert(Drawing.Fonts.Plex == 2, `Did not return the correct id '2' for Fonts.Plex, got '{Drawing.Fonts.Plex}'`) + assert(Drawing.Fonts.Monospace == 3, `Did not return the correct id '3' for Fonts.Monospace, got '{Drawing.Fonts.Monospace}'`) +end) + +test("Drawing.Fonts", {}, function() + assert(Drawing.Fonts.UI == 0, `Did not return the correct id '0' for Fonts.UI, got '{Drawing.Fonts.UI}'`) + assert(Drawing.Fonts.System == 1, `Did not return the correct id '1' for Fonts.System, got '{Drawing.Fonts.System}'`) + assert(Drawing.Fonts.Plex == 2, `Did not return the correct id '2' for Fonts.Plex, got '{Drawing.Fonts.Plex}'`) + assert(Drawing.Fonts.Monospace == 3, `Did not return the correct id '3' for Fonts.Monospace, got '{Drawing.Fonts.Monospace}'`) +end) + +test("Drawing.Clear", {"cleardrawcache"}, function() + local drawing = Drawing.new("Image") + drawing.Visible = false + Drawing.Clear() +end) + +test("getrenderproperty", {}, function() + local drawing = Drawing.new("Image") + drawing.Visible = true + assert(type(getrenderproperty(drawing, "Visible")) == "boolean", "Did not return a boolean value for Image.Visible") + local success, result = pcall(function() + return getrenderproperty(drawing, "Color") + end) + if not success or not result then + return "Image.Color is not supported" + end +end) + +test("setrenderproperty", {}, function() + local drawing = Drawing.new("Square") + drawing.Visible = true + setrenderproperty(drawing, "Visible", false) + assert(drawing.Visible == false, "Did not set the value for Square.Visible") +end) + +-- WebSocket Library + +test("WebSocket", {}) + +test("WebSocket.connect", {}, function() + local types = { + Send = "function", + Close = "function", + OnMessage = {"table", "userdata"}, + OnClose = {"table", "userdata"}, + } + local ws = WebSocket.connect("ws://echo.websocket.events") + assert(type(ws) == "table" or type(ws) == "userdata", "Did not return a table or userdata") + for k, v in next, types do + if type(v) == "table" then + assert(table.find(v, type(ws[k])), `Did not return a {table.concat(v, ", ")} for {k} (a {typeof(ws[k])})`) + else + assert(type(ws[k]) == v, `Did not return a {v} for {k} (a {typeof(ws[k])})`) + end + end + ws:Close() +end) + +-- Teleport Queue Library + +test("Queue", {}, function() + Queue("return") +end) + +test("Queue.Get", {"Queue_Get"}, function() + assert(Queue.Get()[1] == "return", "Did not return correct value when getting the Teleport Queue") +end) + +test("Queue.Clear", {"Queue_Clear"}, function() + Queue.Clear() + assert(Queue.Get()[1] ~= "return", "Did not clear Teleport Queue") +end) + +-- Clipboard Library + +test("Clipboard.Set", {"Clipboard_Set"}, function() + Clipboard.Set(game:GetService("Players").LocalPlayer.Character) + Clipboard.Set("UNS Testing...") +end) + +test("Clipboard.Get", {"Clipboard_Get"}, function() + assert(Clipboard.Get() == "UNS Testing...", "Did not return the correct value for a copied string") + Clipboard.Set(game:GetService("Players").LocalPlayer.Character) + assert(Clipboard.Get():find(" + +

+ +Unified Naming Standard (short UNS), is a unified scripting API inspire by [UNC](https://github.com/unified-naming-convention/NamingStandard) for our loved developers who put their hard work and alot of their time to reverse engineer roblox games and create the scripts we all know and love 💘 + +> [!NOTE] +> ### We do not provide any DLL file(s) (yet 👀). +> You would have to make the DLL yourself, we only provide basic information on how to use the functions! + +![Consider-Starring](https://img.shields.io/badge/consider%20giving%20this%20repo%20a-%20star%21-FFBF00) + + + + + + Star History Chart + + + +--- + +## Why all of this? +Over the decades, scripting has gotten more and more complex to support as many executors as possible. This is because of many unique naming conventions [all the executors](https://github.com/luau/Executor-API-Docs/blob/master/Full-Executor-List) use. + +Consider the following scenario. You want to know if a function belongs to the executor or not. In order for this code to be cross compatiable with all executors code like this is needed: +```lua +local is_executor_closure = is_syn_closure or is_fluxus_closure or is_sentinel_closure or is_krnl_closure or is_proto_closure or is_calamari_closure or is_electron_closure or is_elysian_closure or is_valyse_closure or is_sona_closure or is_oxygen_closure or is_krampus_closure or is_sirhut_closure or is_arceus_closure or is_codex_closure or is_wave_closure +``` +This is reality for scripters who want cross compatibilty in their scripts. Scripters shouldn't have to do such laborous work just to attain cross compatability. The UNS seeks to solve this problem using naming conventions everyone agrees upon and follows. + +One variant of a script should naturally work on all script executors which have their environment properly fitted to the UNS. + +## How can an executor support UNS? +The UNS provides standards for naming conventions as well as API functionality. The standard is written in markdown on this GitHub. Edits or additions are done through pull requests. Edits and additions are manually approved by the UNS council (aka [Master Oogway](https://discord.com/users/830031741225009203)) and discussed by everyone (everyone meaning [Master Oogway](https://discord.com/users/830031741225009203)). + +## Supporting UNC +As a product owner, your support of UNC by following the API will result in a far smoother experience for scripters, as they are able to work on scripts that they can confidently say will work on **most** products. Once you have implemented UNC's API, you can display so by adding the badge to your website, thread or application. + +You can find the badge here: ~~In Progress, please be patient. Im not that good at designing 🥴.~~ + +This will tell people of your alliance in providing scripters with an easier method of engineering scripts that your consumers can enjoy. + +NOTICE: If you, as a product owner, support >=80% of the functions, you are allowed to display the badge on your website. This may change in the future so keep an eye out for that! + +## Checking your environment + +You can run the UNC environment checking script to see how well your executor environment supports the UNC standard. Find the script [here](EnvCheck.luau) or [here.](EnvCheck.luau) The script determines what is missing, and writes the results to the in-built developer console of roblox. + +## Contributing +Go [here](CONTRIBUTING.md) for a guide on contributing. \ No newline at end of file diff --git a/TEMPLATE.md b/TEMPLATE.md new file mode 100644 index 0000000..f30ca3b --- /dev/null +++ b/TEMPLATE.md @@ -0,0 +1,40 @@ +## FunctionName + +`⏰ Yields` `🌎 Global` `🪲 Inconsistent` `🔎 Needs Investigation` `📌 Custom Tag` + +```lua +function FunctionName(param: string): () +``` + +Describe the function in a neat and concise manner. + +May include multiple lines, paragraphs, or info cards. + +> ### 🔎 Notes, tips, info +> Additional information or URLs. + +> ### ⚠️ Warnings, risks +> Exercise caution. + +> ### ⛔ Danger! +> Avoid making this mistake. + +> ### 🪲 Bugs, issues +> Document known issues. + +### Parameters + + * `param` - The parameter description. + +### Aliases + + * `functionAlias` + * `badAlias` - Optional justification. + +### Example + +A description of the example that follows. + +```lua +print(FunctionName()) --> (void) +``` \ No newline at end of file diff --git a/images/logo.webp b/images/logo.webp new file mode 100644 index 0000000..778b849 Binary files /dev/null and b/images/logo.webp differ diff --git a/images/logo_april_fools.webp b/images/logo_april_fools.webp new file mode 100644 index 0000000..0194f2d Binary files /dev/null and b/images/logo_april_fools.webp differ