add gs
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
local UGCValidationService = game:GetService("UGCValidationService")
|
||||
|
||||
local root = script.Parent.Parent
|
||||
local valueToString = require(root.util.valueToString)
|
||||
|
||||
local MARGIN_OF_ERROR = 0.1
|
||||
|
||||
local function validateHandleSize(isAsync, instance)
|
||||
-- these are guaranteed to exist thanks to validateInstanceTree being called beforehand
|
||||
local handle = instance.Handle
|
||||
local mesh = handle:FindFirstChildOfClass("SpecialMesh")
|
||||
|
||||
local success, verts = pcall(function()
|
||||
if isAsync then
|
||||
return UGCValidationService:GetMeshVerts(mesh.MeshId)
|
||||
else
|
||||
return UGCValidationService:GetMeshVertsSync(mesh.MeshId)
|
||||
end
|
||||
end)
|
||||
|
||||
if not success then
|
||||
return false, { "Failed to read mesh" }
|
||||
end
|
||||
|
||||
local minX, maxX = math.huge, 0
|
||||
local minY, maxY = math.huge, 0
|
||||
local minZ, maxZ = math.huge, 0
|
||||
|
||||
for i = 1, #verts do
|
||||
local vert = verts[i] * mesh.Scale
|
||||
minX = math.min(minX, vert.X)
|
||||
minY = math.min(minY, vert.Y)
|
||||
minZ = math.min(minZ, vert.Z)
|
||||
maxX = math.max(maxX, vert.X)
|
||||
maxY = math.max(maxY, vert.Y)
|
||||
maxZ = math.max(maxZ, vert.Z)
|
||||
end
|
||||
|
||||
local meshSize = Vector3.new(
|
||||
maxX - minX,
|
||||
maxY - minY,
|
||||
maxZ - minZ
|
||||
)
|
||||
|
||||
-- allow handle.Size to be within MARGIN_OF_ERROR of meshSize or larger
|
||||
-- this is necessary since we're comparing floats
|
||||
-- the size only needs to be a rough equivalent for thumbnailing
|
||||
if handle.Size.X + MARGIN_OF_ERROR < meshSize.X
|
||||
or handle.Size.Y + MARGIN_OF_ERROR < meshSize.Y
|
||||
or handle.Size.Z + MARGIN_OF_ERROR < meshSize.Z then
|
||||
return false, {
|
||||
string.format("Accessory Handle size should be at least the size of the mesh ( %s )", valueToString(meshSize))
|
||||
}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateHandleSize
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
local createAccessorySchema = require(root.util.createAccessorySchema)
|
||||
local validateWithSchema = require(root.util.validateWithSchema)
|
||||
|
||||
-- validates a given instance based on a schema
|
||||
local function validateInstanceTree(instance, assetTypeEnum)
|
||||
local assetInfo = Constants.ASSET_TYPE_INFO[assetTypeEnum]
|
||||
if not assetInfo then
|
||||
return false, { "Could not validate" }
|
||||
end
|
||||
|
||||
local schema = createAccessorySchema(assetInfo.attachmentNames)
|
||||
|
||||
-- validate using hat schema
|
||||
local validationResult = validateWithSchema(schema, instance)
|
||||
if validationResult.success == false then
|
||||
return false, { validationResult.message }
|
||||
end
|
||||
|
||||
-- fallback case for if validateWithSchema breaks
|
||||
local invalidDescendantsReasons = {}
|
||||
if Constants.BANNED_NAMES[instance.Name] then
|
||||
local reason = string.format("%s has an invalid name", instance:GetFullName())
|
||||
invalidDescendantsReasons[#invalidDescendantsReasons + 1] = reason
|
||||
end
|
||||
|
||||
for _, descendant in pairs(instance:GetDescendants()) do
|
||||
for _, className in pairs(Constants.BANNED_CLASS_NAMES) do
|
||||
if descendant:IsA(className) then
|
||||
local reason = string.format(
|
||||
"%s is of type %s which is not allowed",
|
||||
descendant:GetFullName(),
|
||||
className
|
||||
)
|
||||
invalidDescendantsReasons[#invalidDescendantsReasons + 1] = reason
|
||||
end
|
||||
end
|
||||
if Constants.BANNED_NAMES[descendant.Name] then
|
||||
local reason = string.format("%s has an invalid name", descendant:GetFullName())
|
||||
invalidDescendantsReasons[#invalidDescendantsReasons + 1] = reason
|
||||
end
|
||||
end
|
||||
|
||||
if #invalidDescendantsReasons > 0 then
|
||||
return false, invalidDescendantsReasons
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateInstanceTree
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
|
||||
-- ensures no descendant of instance has a material that does not exist in Constants.MATERIAL_WHITELIST
|
||||
local function validateMaterials(instance)
|
||||
local materialFailures = {}
|
||||
for _, descendant in pairs(instance:GetDescendants()) do
|
||||
if descendant:IsA("BasePart") and not Constants.MATERIAL_WHITELIST[descendant.Material] then
|
||||
materialFailures[#materialFailures + 1] = descendant:GetFullName()
|
||||
end
|
||||
end
|
||||
if #materialFailures > 0 then
|
||||
local reasons = {}
|
||||
local acceptedMaterialNames = {}
|
||||
for material in pairs(Constants.MATERIAL_WHITELIST) do
|
||||
acceptedMaterialNames[#acceptedMaterialNames + 1] = material.Name
|
||||
end
|
||||
reasons[#reasons + 1] = "Invalid materials for"
|
||||
for _, name in pairs(materialFailures) do
|
||||
reasons[#reasons + 1] = name
|
||||
end
|
||||
reasons[#reasons + 1] = "Accepted materials are " .. table.concat(acceptedMaterialNames, ", ")
|
||||
return false, reasons
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateMaterials
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
local UGCValidationService = game:GetService("UGCValidationService")
|
||||
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
|
||||
local DEFAULT_OFFSET = Vector3.new(0, 0, 0)
|
||||
|
||||
local function pointInBounds(worldPos, boundsCF, boundsSize)
|
||||
local objectPos = boundsCF:pointToObjectSpace(worldPos)
|
||||
return objectPos.X >= -boundsSize.X/2
|
||||
and objectPos.X <= boundsSize.X/2
|
||||
and objectPos.Y >= -boundsSize.Y/2
|
||||
and objectPos.Y <= boundsSize.Y/2
|
||||
and objectPos.Z >= -boundsSize.Z/2
|
||||
and objectPos.Z <= boundsSize.Z/2
|
||||
end
|
||||
|
||||
local function getAttachment(parent, names)
|
||||
for _, name in pairs(names) do
|
||||
local result = parent:FindFirstChild(name)
|
||||
if result then
|
||||
return result
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function validateMeshBounds(isAsync, instance, assetTypeEnum)
|
||||
local assetInfo = Constants.ASSET_TYPE_INFO[assetTypeEnum]
|
||||
|
||||
-- these are guaranteed to exist thanks to validateInstanceTree being called beforehand
|
||||
local handle = instance.Handle
|
||||
local mesh = handle:FindFirstChildOfClass("SpecialMesh")
|
||||
local attachment = getAttachment(handle, assetInfo.attachmentNames)
|
||||
|
||||
if mesh.MeshId == "" then
|
||||
return false, { "Mesh must contain valid MeshId" }
|
||||
end
|
||||
|
||||
local success, verts = pcall(function()
|
||||
if isAsync then
|
||||
return UGCValidationService:GetMeshVerts(mesh.MeshId)
|
||||
else
|
||||
return UGCValidationService:GetMeshVertsSync(mesh.MeshId)
|
||||
end
|
||||
end)
|
||||
|
||||
if not success then
|
||||
return false, { "Failed to read mesh" }
|
||||
end
|
||||
|
||||
local boundsInfo = assert(assetInfo.bounds[attachment.Name], "Could not find bounds for " .. attachment.Name)
|
||||
local boundsSize = boundsInfo.size
|
||||
local boundsOffset = boundsInfo.offset or DEFAULT_OFFSET
|
||||
local boundsCF = handle.CFrame * attachment.CFrame * CFrame.new(boundsOffset)
|
||||
|
||||
for _, vertPos in pairs(verts) do
|
||||
local worldPos = handle.CFrame:pointToWorldSpace(vertPos * mesh.Scale)
|
||||
if not pointInBounds(worldPos, boundsCF, boundsSize) then
|
||||
return false, {
|
||||
"Mesh is too large!",
|
||||
string.format("Max size for type %s is ( %s )", assetTypeEnum.Name, tostring(boundsSize)),
|
||||
"Use SpecialMesh.Scale if needed"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateMeshBounds
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
local UGCValidationService = game:GetService("UGCValidationService")
|
||||
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
|
||||
-- ensures accessory mesh does not have more triangles than Constants.MAX_HAT_TRIANGLES
|
||||
local function validateMeshTriangles(isAsync, instance)
|
||||
-- check mesh triangles
|
||||
-- this is guaranteed to exist thanks to validateInstanceTree being called beforehand
|
||||
local mesh = instance.Handle:FindFirstChildOfClass("SpecialMesh")
|
||||
|
||||
if mesh.MeshId == "" then
|
||||
return false, { "Mesh must contain valid MeshId" }
|
||||
end
|
||||
|
||||
local success, triangles = pcall(function()
|
||||
if isAsync then
|
||||
return UGCValidationService:GetMeshTriCount(mesh.MeshId)
|
||||
else
|
||||
return UGCValidationService:GetMeshTriCountSync(mesh.MeshId)
|
||||
end
|
||||
end)
|
||||
|
||||
if not success then
|
||||
return false, { "Failed to load mesh data" }
|
||||
elseif triangles > Constants.MAX_HAT_TRIANGLES then
|
||||
return false, {
|
||||
string.format(
|
||||
"Mesh has %d triangles, but the limit is %d",
|
||||
triangles,
|
||||
Constants.MAX_HAT_TRIANGLES
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateMeshTriangles
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
local getAssetCreationDetails = require(root.util.getAssetCreationDetails)
|
||||
|
||||
local function parseContentId(contentIds, contentIdMap, object, fieldName)
|
||||
local contentId = object[fieldName]
|
||||
|
||||
-- map to ending digits
|
||||
-- rbxassetid://1234 -> 1234
|
||||
-- http://www.roblox.com/asset/?id=1234 -> 1234
|
||||
local id = tonumber(string.match(contentId, "%d+$"))
|
||||
if id == nil then
|
||||
return false, {
|
||||
"Could not parse ContentId",
|
||||
contentId,
|
||||
}
|
||||
end
|
||||
contentIdMap[id] = {
|
||||
fieldName = fieldName,
|
||||
instance = object,
|
||||
}
|
||||
table.insert(contentIds, id)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function parseDescendantContentIds(contentIds, contentIdMap, object)
|
||||
for _, descendant in pairs(object:GetDescendants()) do
|
||||
if descendant:IsA("SpecialMesh") then
|
||||
local success, reasons
|
||||
success, reasons = parseContentId(contentIds, contentIdMap, descendant, "MeshId")
|
||||
if not success then
|
||||
return false, reasons
|
||||
end
|
||||
success, reasons = parseContentId(contentIds, contentIdMap, descendant, "TextureId")
|
||||
if not success then
|
||||
return false, reasons
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- ensures accessory content ids have all passed moderation review
|
||||
local function validateModeration(isAsync, instance)
|
||||
local contentIdMap = {}
|
||||
local contentIds = {}
|
||||
|
||||
local parseSuccess, parseReasons = parseDescendantContentIds(contentIds, contentIdMap, instance)
|
||||
if not parseSuccess then
|
||||
return false, parseReasons
|
||||
end
|
||||
|
||||
local moderatedIds = {}
|
||||
|
||||
local success, response = getAssetCreationDetails(isAsync, contentIds)
|
||||
|
||||
if not success or #response ~= #contentIds then
|
||||
return false, { "Could not fetch details for assets" }
|
||||
end
|
||||
|
||||
for _, details in pairs(response) do
|
||||
if details.status == Constants.ASSET_STATUS.UNKNOWN
|
||||
or details.status == Constants.ASSET_STATUS.REVIEW_PENDING
|
||||
or details.status == Constants.ASSET_STATUS.MODERATED
|
||||
then
|
||||
table.insert(moderatedIds, details.assetId)
|
||||
end
|
||||
end
|
||||
|
||||
if #moderatedIds > 0 then
|
||||
local moderationMessages = {}
|
||||
for idx, id in pairs(moderatedIds) do
|
||||
local mapped = contentIdMap[id]
|
||||
if mapped then
|
||||
moderationMessages[idx] = string.format(
|
||||
"%s.%s ( %s )",
|
||||
mapped.instance:GetFullName(),
|
||||
mapped.fieldName,
|
||||
id
|
||||
)
|
||||
else
|
||||
moderationMessages[idx] = id
|
||||
end
|
||||
end
|
||||
return false, {
|
||||
"The following asset IDs have not passed moderation:",
|
||||
unpack(moderationMessages),
|
||||
}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateModeration
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Cryo = require(CorePackages.Cryo)
|
||||
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
local valueToString = require(root.util.valueToString)
|
||||
|
||||
local EPSILON = 1e-5
|
||||
|
||||
local function floatEq(a, b)
|
||||
return math.abs(a - b) <= EPSILON
|
||||
end
|
||||
|
||||
local function v3FloatEq(a, b)
|
||||
return floatEq(a.X, b.X) and floatEq(a.Y, b.Y) and floatEq(a.Z, b.Z)
|
||||
end
|
||||
|
||||
local function c3FloatEq(a, b)
|
||||
return floatEq(a.r, b.r) and floatEq(a.g, b.g) and floatEq(a.b, b.b)
|
||||
end
|
||||
|
||||
local function propEq(propValue, expectedValue)
|
||||
local valueType = typeof(expectedValue)
|
||||
if expectedValue == Cryo.None then
|
||||
return propValue == nil
|
||||
elseif valueType == "number" then
|
||||
return floatEq(propValue, expectedValue)
|
||||
elseif valueType == "Vector3" then
|
||||
return v3FloatEq(propValue, expectedValue)
|
||||
elseif valueType == "Color3" then
|
||||
return c3FloatEq(propValue, expectedValue)
|
||||
else
|
||||
return propValue == expectedValue
|
||||
end
|
||||
end
|
||||
|
||||
local function validateProperties(instance)
|
||||
|
||||
-- full tree of instance + descendants
|
||||
local objects = instance:GetDescendants()
|
||||
table.insert(objects, instance)
|
||||
|
||||
for _, object in pairs(objects) do
|
||||
for className, properties in pairs(Constants.PROPERTIES) do
|
||||
if object:IsA(className) then
|
||||
for propName, expectedValue in pairs(properties) do
|
||||
-- ensure property exists first
|
||||
local propExists, propValue = pcall(function() return object[propName] end)
|
||||
|
||||
if not propExists then
|
||||
return false, {
|
||||
string.format("Property %s does not exist on type %s", propName, object.ClassName)
|
||||
}
|
||||
end
|
||||
|
||||
if not propEq(propValue, expectedValue) then
|
||||
return false, {
|
||||
string.format("Expected %s.%s to be %s", object:GetFullName(), propName, valueToString(expectedValue))
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateProperties
|
||||
@@ -0,0 +1,25 @@
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
|
||||
local function validateTags(instance)
|
||||
local objects = instance:GetDescendants()
|
||||
table.insert(objects, instance)
|
||||
|
||||
local hasTags = {}
|
||||
for _, obj in pairs(objects) do
|
||||
if #CollectionService:GetTags(obj) > 0 then
|
||||
table.insert(hasTags, obj)
|
||||
end
|
||||
end
|
||||
|
||||
if #hasTags > 0 then
|
||||
local reasons = { "The following objects contain CollectionService tags:" }
|
||||
for _, obj in pairs(hasTags) do
|
||||
table.insert(reasons, obj:GetFullName())
|
||||
end
|
||||
return false, reasons
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateTags
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
local UGCValidationService = game:GetService("UGCValidationService")
|
||||
|
||||
local root = script.Parent.Parent
|
||||
|
||||
local Constants = require(root.Constants)
|
||||
|
||||
local function validateTextureSize(isAsync, instance)
|
||||
-- this is guaranteed to exist thanks to validateInstanceTree being called beforehand
|
||||
local mesh = instance.Handle:FindFirstChildOfClass("SpecialMesh")
|
||||
|
||||
if mesh.TextureId == "" then
|
||||
return false, { "Mesh must contain valid TextureId" }
|
||||
end
|
||||
|
||||
local success, imageSize = pcall(function()
|
||||
if isAsync then
|
||||
return UGCValidationService:GetTextureSize(mesh.TextureId)
|
||||
else
|
||||
return UGCValidationService:GetTextureSizeSync(mesh.TextureId)
|
||||
end
|
||||
end)
|
||||
|
||||
if not success then
|
||||
return false, { "Failed to load texture data", imageSize }
|
||||
elseif imageSize.X > Constants.MAX_TEXTURE_SIZE or imageSize.Y > Constants.MAX_TEXTURE_SIZE then
|
||||
return false, {
|
||||
string.format(
|
||||
"Texture size is %dx%d px, but the limit is %dx%d px",
|
||||
imageSize.X,
|
||||
imageSize.Y,
|
||||
Constants.MAX_TEXTURE_SIZE,
|
||||
Constants.MAX_TEXTURE_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return validateTextureSize
|
||||
Reference in New Issue
Block a user