Загрузка данных


--[[
    CLIENT EXPOSURE AUDITOR v2

    Цель:
    показать всё существенное, что уже доступно клиенту.

    НЕ использует:
    getgc / getsenv / debug registry / hooks / decompiler /
    executor-specific API.

    Подходит для построения threat model своего place.
]]

local HttpService = game:GetService("HttpService")
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")

local localPlayer = Players.LocalPlayer

local MAX_INSTANCES = 100000
local MAX_OUTPUT_CHARS = 1500000

local lines = {}
local charCount = 0

local stats = {
	instances = 0,
	attributes = 0,
	tags = 0,
	values = 0,

	remotes = 0,
	interactables = 0,

	localScripts = 0,
	moduleScripts = 0,
	clientScripts = 0,

	unanchored = 0,
	assets = 0,

	riskMarkers = 0,
}

local suspiciousWords = {
	"secret",
	"token",
	"password",
	"private",
	"apikey",
	"api_key",

	"admin",
	"owner",
	"moderator",

	"money",
	"cash",
	"coin",
	"currency",

	"price",
	"reward",
	"damage",
	"cooldown",

	"inventory",
	"item",
	"weapon",

	"level",
	"xp",
	"experience",

	"config",
	"settings",

	"ban",
	"anticheat",
	"anti_cheat",

	"remote",
	"purchase",
	"give",
	"grant",
}

local function safeRead(object, property)
	local ok, value = pcall(function()
		return object[property]
	end)

	if ok then
		return value
	end

	return nil
end

local function pathOf(object)
	local ok, value = pcall(function()
		return object:GetFullName()
	end)

	if ok then
		return value
	end

	return object.Name
end

local function stringify(value)
	local t = typeof(value)

	if t == "Instance" then
		return pathOf(value)
	end

	if t == "string" then
		if #value > 500 then
			value = value:sub(1, 500) .. "..."
		end

		return value
	end

	if t == "table" then
		local ok, result = pcall(function()
			return HttpService:JSONEncode(value)
		end)

		if ok then
			return result
		end

		return "<table>"
	end

	return tostring(value)
end

local function suspicious(text)
	text = string.lower(text)

	for _, word in ipairs(suspiciousWords) do
		if string.find(text, word, 1, true) then
			return true
		end
	end

	return false
end

local function addLine(category, path, key, value)
	if charCount >= MAX_OUTPUT_CHARS then
		return
	end

	local text =
		"[" .. category .. "] "
		.. path

	if key then
		text ..= " | " .. tostring(key)
	end

	if value ~= nil then
		text ..= " = " .. stringify(value)
	end

	if suspicious(text) then
		text = "[!] " .. text
		stats.riskMarkers += 1
	end

	charCount += #text + 1
	table.insert(lines, text)
end

--------------------------------------------------
-- PROPERTY AUDITING
--------------------------------------------------

local propertiesByClass = {

	RemoteEvent = {
		"Name",
	},

	RemoteFunction = {
		"Name",
	},

	UnreliableRemoteEvent = {
		"Name",
	},

	ProximityPrompt = {
		"ActionText",
		"ObjectText",
		"Enabled",
		"HoldDuration",
		"MaxActivationDistance",
		"RequiresLineOfSight",
		"ClickablePrompt",
	},

	ClickDetector = {
		"MaxActivationDistance",
		"CursorIcon",
	},

	Tool = {
		"RequiresHandle",
		"CanBeDropped",
		"ToolTip",
		"Enabled",
	},

	Humanoid = {
		"Health",
		"MaxHealth",
		"WalkSpeed",
		"JumpPower",
		"JumpHeight",
		"HipHeight",
		"AutoRotate",
		"UseJumpPower",
	},

	Sound = {
		"SoundId",
		"Volume",
		"PlaybackSpeed",
		"Looped",
	},

	Animation = {
		"AnimationId",
	},

	Decal = {
		"Texture",
		"Transparency",
	},

	Texture = {
		"Texture",
		"Transparency",
	},

	MeshPart = {
		"MeshId",
		"TextureID",
		"Anchored",
		"CanCollide",
		"CanQuery",
		"CanTouch",
		"Massless",
	},

	SpecialMesh = {
		"MeshId",
		"TextureId",
	},

	ImageLabel = {
		"Image",
	},

	ImageButton = {
		"Image",
	},

	VideoFrame = {
		"Video",
	},

	Shirt = {
		"ShirtTemplate",
	},

	Pants = {
		"PantsTemplate",
	},

	SurfaceAppearance = {
		"ColorMap",
		"NormalMap",
		"MetalnessMap",
		"RoughnessMap",
	},
}

--------------------------------------------------
-- INSTANCE INSPECTION
--------------------------------------------------

local function inspect(object)

	stats.instances += 1

	local path = pathOf(object)

	addLine(
		"INSTANCE",
		path,
		"class",
		object.ClassName
	)

	--------------------------------------------------
	-- ATTRIBUTES
	--------------------------------------------------

	local okAttributes, attributes = pcall(function()
		return object:GetAttributes()
	end)

	if okAttributes then
		for name, value in pairs(attributes) do
			stats.attributes += 1

			addLine(
				"ATTRIBUTE",
				path,
				name,
				value
			)
		end
	end

	--------------------------------------------------
	-- COLLECTIONSERVICE TAGS
	--------------------------------------------------

	local okTags, tags = pcall(function()
		return CollectionService:GetTags(object)
	end)

	if okTags then
		for _, tag in ipairs(tags) do
			stats.tags += 1

			addLine(
				"TAG",
				path,
				nil,
				tag
			)
		end
	end

	--------------------------------------------------
	-- VALUE OBJECTS
	--------------------------------------------------

	if object:IsA("ValueBase") then

		local value = safeRead(object, "Value")

		stats.values += 1

		addLine(
			"VALUE",
			path,
			nil,
			value
		)
	end

	--------------------------------------------------
	-- NETWORK SURFACE
	--------------------------------------------------

	if object:IsA("RemoteEvent")
		or object:IsA("RemoteFunction")
		or object:IsA("UnreliableRemoteEvent")
	then

		stats.remotes += 1

		addLine(
			"REMOTE",
			path,
			"type",
			object.ClassName
		)
	end

	--------------------------------------------------
	-- INTERACTION SURFACE
	--------------------------------------------------

	if object:IsA("ProximityPrompt")
		or object:IsA("ClickDetector")
		or object:IsA("DragDetector")
	then

		stats.interactables += 1

		addLine(
			"INTERACTION",
			path,
			"type",
			object.ClassName
		)
	end

	--------------------------------------------------
	-- REPLICATED CODE
	--------------------------------------------------

	if object:IsA("LocalScript") then

		stats.localScripts += 1

		addLine(
			"CLIENT_CODE",
			path,
			"type",
			"LocalScript"
		)

	elseif object:IsA("ModuleScript") then

		stats.moduleScripts += 1

		addLine(
			"CLIENT_CODE",
			path,
			"type",
			"ModuleScript"
		)

	elseif object:IsA("Script") then

		local runContext = safeRead(object, "RunContext")

		if runContext and tostring(runContext):find("Client") then
			stats.clientScripts += 1

			addLine(
				"CLIENT_CODE",
				path,
				"RunContext",
				runContext
			)
		end
	end

	--------------------------------------------------
	-- PHYSICS ATTACK SURFACE
	--------------------------------------------------

	if object:IsA("BasePart") then

		local anchored = safeRead(object, "Anchored")

		if anchored == false then

			stats.unanchored += 1

			addLine(
				"PHYSICS",
				path,
				"Anchored",
				false
			)

			local massless = safeRead(object, "Massless")

			addLine(
				"PHYSICS",
				path,
				"Massless",
				massless
			)
		end
	end

	--------------------------------------------------
	-- CLASS-SPECIFIC PROPERTIES
	--------------------------------------------------

	for className, propertyList in pairs(propertiesByClass) do

		if object:IsA(className) then

			for _, property in ipairs(propertyList) do

				local value = safeRead(object, property)

				if value ~= nil and tostring(value) ~= "" then

					if
						property == "SoundId"
						or property == "AnimationId"
						or property == "Texture"
						or property == "TextureID"
						or property == "TextureId"
						or property == "MeshId"
						or property == "Image"
						or property == "Video"
						or property == "ShirtTemplate"
						or property == "PantsTemplate"
						or property == "ColorMap"
						or property == "NormalMap"
						or property == "MetalnessMap"
						or property == "RoughnessMap"
					then
						stats.assets += 1
					end

					addLine(
						"PROPERTY",
						path,
						property,
						value
					)
				end
			end
		end
	end
end

--------------------------------------------------
-- ENVIRONMENT
--------------------------------------------------

local streamingEnabled = false

pcall(function()
	streamingEnabled = workspace.StreamingEnabled
end)

table.insert(lines, "=======================================")
table.insert(lines, "CLIENT EXPOSURE AUDITOR")
table.insert(lines, "=======================================")
table.insert(lines, "")
table.insert(lines, "PlaceId: " .. tostring(game.PlaceId))
table.insert(lines, "GameId: " .. tostring(game.GameId))
table.insert(lines, "JobId: " .. tostring(game.JobId))
table.insert(lines, "StreamingEnabled: " .. tostring(streamingEnabled))
table.insert(lines, "")
table.insert(lines, "Scanning client-visible DataModel...")
table.insert(lines, "")

--------------------------------------------------
-- FULL CLIENT DATAMODEL SCAN
--------------------------------------------------

local descendants = game:GetDescendants()

for i, object in ipairs(descendants) do

	if stats.instances >= MAX_INSTANCES then
		break
	end

	inspect(object)

	if i % 500 == 0 then
		task.wait()
	end
end

--------------------------------------------------
-- SUMMARY
--------------------------------------------------

local summary = {

	"",
	"=======================================",
	"SUMMARY",
	"=======================================",

	"Instances:       " .. stats.instances,

	"Attributes:      " .. stats.attributes,
	"Tags:            " .. stats.tags,
	"Value objects:   " .. stats.values,

	"Remotes:         " .. stats.remotes,
	"Interactables:   " .. stats.interactables,

	"LocalScripts:    " .. stats.localScripts,
	"ModuleScripts:   " .. stats.moduleScripts,
	"Client Scripts:  " .. stats.clientScripts,

	"Unanchored Parts:" .. stats.unanchored,
	"Asset IDs:       " .. stats.assets,

	"Risk markers:    " .. stats.riskMarkers,

	"",
}

for _, line in ipairs(summary) do
	table.insert(lines, line)
end

local report = table.concat(lines, "\n")

--------------------------------------------------
-- OUTPUT
--------------------------------------------------

print(report)

--------------------------------------------------
-- COPYABLE GUI
--------------------------------------------------

if localPlayer then

	local playerGui = localPlayer:WaitForChild("PlayerGui")

	local old = playerGui:FindFirstChild("client_exposure_auditor")

	if old then
		old:Destroy()
	end

	local gui = Instance.new("ScreenGui")
	gui.Name = "client_exposure_auditor"
	gui.ResetOnSpawn = false
	gui.IgnoreGuiInset = true
	gui.Parent = playerGui

	local frame = Instance.new("Frame")
	frame.Size = UDim2.new(0.94, 0, 0.9, 0)
	frame.Position = UDim2.new(0.03, 0, 0.05, 0)
	frame.BackgroundColor3 = Color3.fromRGB(15, 15, 18)
	frame.Parent = gui

	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 12)
	corner.Parent = frame

	local title = Instance.new("TextLabel")
	title.Size = UDim2.new(1, -70, 0, 45)
	title.Position = UDim2.fromOffset(15, 5)
	title.BackgroundTransparency = 1
	title.Text = "CLIENT EXPOSURE AUDITOR"
	title.Font = Enum.Font.Code
	title.TextSize = 20
	title.TextColor3 = Color3.new(1, 1, 1)
	title.TextXAlignment = Enum.TextXAlignment.Left
	title.Parent = frame

	local close = Instance.new("TextButton")
	close.Size = UDim2.fromOffset(45, 40)
	close.Position = UDim2.new(1, -50, 0, 5)
	close.BackgroundTransparency = 1
	close.Text = "×"
	close.TextSize = 26
	close.TextColor3 = Color3.new(1, 1, 1)
	close.Parent = frame

	local box = Instance.new("TextBox")
	box.Size = UDim2.new(1, -20, 1, -60)
	box.Position = UDim2.fromOffset(10, 50)

	box.BackgroundColor3 = Color3.fromRGB(8, 8, 10)

	box.Font = Enum.Font.Code
	box.TextSize = 13

	box.TextColor3 = Color3.fromRGB(220, 220, 225)

	box.TextXAlignment = Enum.TextXAlignment.Left
	box.TextYAlignment = Enum.TextYAlignment.Top

	box.MultiLine = true
	box.ClearTextOnFocus = false
	box.TextEditable = false
	box.TextWrapped = false

	box.Text = report

	box.Parent = frame

	local boxCorner = Instance.new("UICorner")
	boxCorner.CornerRadius = UDim.new(0, 8)
	boxCorner.Parent = box

	close.MouseButton1Click:Connect(function()
		gui:Destroy()
	end)
end

warn("CLIENT EXPOSURE AUDIT COMPLETE")