Universal model inserter v3
Key RequiredUniversal

Universal model inserter v3

Universal ScriptSmg44463Smg44463 1.2K views 329 executesUpdated Dec 23, 2025
Unrated
00
Lua Script•1738 lines (73706 chars)
-- ═══════════════════════════════════════════════════════════════════
--  SERVICES & PLAYERS
-- ═══════════════════════════════════════════════════════════════════
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Lighting = game:GetService("Lighting")
local TextService = game:GetService("TextService")
local CoreGui = game:GetService("CoreGui")
local Mouse = Players.LocalPlayer:GetMouse()

local LocalPlayer = Players.LocalPlayer
local playerGui = LocalPlayer:WaitForChild("PlayerGui")
local backpack = LocalPlayer:WaitForChild("Backpack")
local character = LocalPlayer.Character or LocalPlayer.CharacterAdded:Wait()

-- ═══════════════════════════════════════════════════════════════════
--  SSA INFRASTRUCTURE — Mocks, Caches, Sandbox Environment
-- ═══════════════════════════════════════════════════════════════════

-- Hidden UI resolution
local function getui()
    local s, r = pcall(function() return get_hidden_ui() or get_hidden_gui() end)
    return (s and r) or CoreGui
end

local NativeRequire = require
local RealInstance = Instance
local RealGame = game
local RealRunService = RunService
local RealLoadstring = loadstring

-- Basement & MockSSS — hidden container for server-side assets
local Basement = LocalPlayer:FindFirstChild("PlayerGui"):FindFirstChild("...") or RealInstance.new("Folder", LocalPlayer:FindFirstChild("PlayerGui"))
Basement.Name = "..."

local MockSSS = Basement:FindFirstChild("ServerScriptService") or RealInstance.new("Folder", Basement)
MockSSS.Name = "ServerScriptService"

-- Module cache: prevents re-execution and circular require loops
_G._ModuleCache = _G._ModuleCache or {}

-- Tool GUI registry: maps tool name -> list of ScreenGui/BillboardGui instances
_G._ToolGUIRegistry  = _G._ToolGUIRegistry  or {}
_G._ActiveToolContext = _G._ActiveToolContext or nil

-- ─── MockInstance ─────────────────────────────────────────────────
local MockInstance = {}
setmetatable(MockInstance, {__index = RealInstance})

function MockInstance.new(class, parent)
    if class == "ScreenGui" or class == "BillboardGui" or class == "SurfaceGui" then
        local gui = RealInstance.new(class, parent or getui())
        -- Register for tool GUI tracking
        local ctx = _G._ActiveToolContext
        if ctx then
            _G._ToolGUIRegistry[ctx] = _G._ToolGUIRegistry[ctx] or {}
            table.insert(_G._ToolGUIRegistry[ctx], gui)
            gui.Enabled = false
            gui:GetPropertyChangedSignal("Enabled"):Connect(function()
                if gui.Enabled then
                    local char = LocalPlayer.Character
                    if not (char and char:FindFirstChild(ctx)) then
                        task.defer(function() gui.Enabled = false end)
                    end
                end
            end)
        end
        return gui
    end
    if class == "RemoteEvent" then
        local fake = {}
        local serverEvent = RealInstance.new("BindableEvent")
        local clientEvent = RealInstance.new("BindableEvent")
        fake.Name = "RemoteEvent"
        fake.ClassName = "RemoteEvent"
        fake.Parent = parent
        fake.OnServerEvent = serverEvent.Event
        fake.OnClientEvent = clientEvent.Event
        function fake:FireServer(...) serverEvent:Fire(LocalPlayer, ...) end
        function fake:FireClient(_, ...) clientEvent:Fire(...) end
        function fake:FireAllClients(...) clientEvent:Fire(...) end
        function fake:IsA(t) return t == "RemoteEvent" or t == "Instance" end
        function fake:WaitForChild(c) return self[c] or RealInstance.new("Folder") end
        return fake
    end
    return RealInstance.new(class, parent)
end

-- ─── MockRunService (server flavour) ──────────────────────────────
local MockRunService = {}
setmetatable(MockRunService, {
    __index = function(_, k)
        if k == "IsServer" then return function() return true end end
        if k == "IsClient" then return function() return false end end
        local v = RealRunService[k]
        if type(v) == "function" then return function(_, ...) return v(RealRunService, ...) end end
        return v
    end
})

-- ─── MockGame ─────────────────────────────────────────────────────
local MockGame = {}
setmetatable(MockGame, {
    __index = function(_, k)
        if k == "ServerScriptService" or k == "ServerStorage" then return MockSSS end
        if k == "RunService" then return MockRunService end
        if k == "GetService" or k == "getService" then
            return function(_, s)
                if s == "ServerScriptService" or s == "ServerStorage" then return MockSSS end
                if s == "RunService" then return MockRunService end
                return RealGame:GetService(s)
            end
        end
        local v = RealGame[k]
        if type(v) == "function" then return function(_, ...) return v(RealGame, ...) end end
        return v
    end
})

-- ─── Build sandboxed environment for infected scripts ─────────────
local Infect  -- forward declaration

local function buildEnv(scriptObj)
    local env = {
        script      = scriptObj,
        game        = MockGame,
        Game        = MockGame,
        workspace   = workspace,
        Workspace   = workspace,
        owner       = LocalPlayer,
        RunService  = MockRunService,
        Instance    = MockInstance,
        require     = function(id) return _G.SmartRequire(id, scriptObj) end,
        _G          = _G,
        shared      = shared,
        print       = print,
        warn        = warn,
        error       = error,
        assert      = assert,
        pcall       = pcall,
        xpcall      = xpcall,
        tostring    = tostring,
        tonumber    = tonumber,
        type        = type,
        typeof      = typeof,
        pairs       = pairs,
        ipairs      = ipairs,
        next        = next,
        select      = select,
        unpack      = unpack or table.unpack,
        rawget      = rawget,
        rawset      = rawset,
        rawequal    = rawequal,
        rawlen      = rawlen,
        setmetatable = setmetatable,
        getmetatable = getmetatable,
        string      = string,
        table       = table,
        math        = math,
        os          = os,
        coroutine   = coroutine,
        bit32       = bit32,
        utf8        = utf8,
        task        = task,
        delay       = delay,
        spawn       = spawn,
        wait        = wait,
        tick        = tick,
        time        = time,
        Vector2     = Vector2,
        Vector3     = Vector3,
        CFrame      = CFrame,
        Color3      = Color3,
        UDim        = UDim,
        UDim2       = UDim2,
        Rect        = Rect,
        Region3     = Region3,
        Ray         = Ray,
        TweenInfo   = TweenInfo,
        NumberRange = NumberRange,
        NumberSequence = NumberSequence,
        NumberSequenceKeypoint = NumberSequenceKeypoint,
        ColorSequence = ColorSequence,
        ColorSequenceKeypoint = ColorSequenceKeypoint,
        BrickColor  = BrickColor,
        Enum        = Enum,
        Axes        = Axes,
        Faces       = Faces,
        PhysicalProperties = PhysicalProperties,
        Random      = Random,
        loadstring  = function(src, chunkname)
            local f, err = RealLoadstring(src, chunkname)
            if f and Infect then
                Infect(f, scriptObj)
            end
            return f, err
        end,
    }
    return env
end

-- ─── Infect: applies sandbox environment to a function ────────────
Infect = function(func, scriptObj)
    if type(func) ~= "function" then return func end
    local env = buildEnv(scriptObj)
    setfenv(func, setmetatable(env, {
        __index = function(t, k)
            local v = rawget(t, k)
            if v ~= nil then return v end
            local ok, gv = pcall(function() return getgenv()[k] end)
            if ok and gv ~= nil then return gv end
            return (getfenv(0))[k]
        end,
        __newindex = function(t, k, v)
            rawset(t, k, v)
        end
    }))
    return func
end

-- ─── SmartRequire: module resolution with caching ─────────────────
_G.SmartRequire = function(id, caller)
    local module = nil
    local cacheKey = (typeof(id) == "Instance") and tostring(id) or tostring(id)

    if _G._ModuleCache[cacheKey] ~= nil then
        return _G._ModuleCache[cacheKey]
    end

    -- 1. String path resolution
    if type(id) == "string" and not tonumber(id) then
        local cleanName = id:match("([^/]+)$")
        if caller and cleanName then
            module = caller:FindFirstChild(cleanName)
                or (caller.Parent and caller.Parent:FindFirstChild(cleanName))
        end
        if not module and cleanName then
            for _, v in pairs(MockSSS:GetDescendants()) do
                if v.Name == cleanName and v:IsA("ModuleScript") then module = v break end
            end
        end
    end

    -- 2. Asset ID resolution
    if not module and (type(id) == "number" or tonumber(id)) then
        local s, r = pcall(function() return RealGame:GetObjects("rbxassetid://" .. tonumber(id)) end)
        if s and type(r) == "table" and #r > 0 then
            local containers = {}
            for _, root in ipairs(r) do
                root.Parent = MockSSS
                table.insert(containers, root)
            end
            local container = containers[1]

            local prevCtx = _G._ActiveToolContext
            _G._ActiveToolContext = container.Name

            local function getSource(inst)
                local src = inst.Source
                if not src or src == "" then
                    local ok2, dec = pcall(function() return decompile(inst) end)
                    if ok2 and type(dec) == "string" and dec ~= "" then src = dec end
                end
                return (src and src ~= "") and src or nil
            end

            -- PASS 1: Collect scripts
            local serverScripts = {}
            local localScripts  = {}
            for _, root in ipairs(containers) do
                local nodes = {root}
                for _, v in ipairs(root:GetDescendants()) do table.insert(nodes, v) end
                for _, v in ipairs(nodes) do
                    if typeof(v) == "Instance" then
                        if v:IsA("Script") and not v:IsA("LocalScript") then
                            pcall(function() v.Disabled = true end)
                            local src = getSource(v)
                            if src then
                                table.insert(serverScripts, {inst = v, src = src})
                            else
                                warn("[SmartRequire] Empty source for Script:", v:GetFullName())
                            end
                        elseif v:IsA("LocalScript") then
                            pcall(function() v.Disabled = true end)
                            local src = getSource(v)
                            if src then
                                table.insert(localScripts, {inst = v, src = src})
                            else
                                warn("[SmartRequire] Empty source for LocalScript:", v:GetFullName())
                            end
                        end
                    end
                end
            end

            -- PASS 2: Build per-type RunService mocks
            local ClientRS = setmetatable({}, {
                __index = function(_, k)
                    if k == "IsServer" then return function() return false end end
                    if k == "IsClient" then return function() return true  end end
                    local v = RealRunService[k]
                    if type(v) == "function" then return function(_, ...) return v(RealRunService, ...) end end
                    return v
                end
            })

            local function makeClientGame()
                return setmetatable({}, {
                    __index = function(_, k)
                        if k == "RunService" then return ClientRS end
                        if k == "GetService" or k == "getService" then
                            return function(_, s2)
                                if s2 == "RunService" then return ClientRS end
                                if s2 == "ServerScriptService" or s2 == "ServerStorage" then return MockSSS end
                                return RealGame:GetService(s2)
                            end
                        end
                        return MockGame[k]
                    end
                })
            end

            local function infectScript(f, inst, isClient)
                local env = buildEnv(inst)
                if isClient then
                    env.RunService = ClientRS
                    env.game       = makeClientGame()
                    env.Game       = env.game
                end
                env.loadstring = function(src, chunkname)
                    local df, derr = RealLoadstring(src, chunkname)
                    if df then infectScript(df, inst, isClient) end
                    return df, derr
                end
                setfenv(f, setmetatable(env, {
                    __index = function(t, k)
                        local v = rawget(t, k)
                        if v ~= nil then return v end
                        local ok2, gv = pcall(function() return getgenv()[k] end)
                        if ok2 and gv ~= nil then return gv end
                        return (getfenv(0))[k]
                    end,
                    __newindex = function(t, k, v) rawset(t, k, v) end
                }))
                return f
            end

            -- PASS 3: Launch all scripts, track completion
            local pending   = 0
            local doneEvent = RealInstance.new("BindableEvent")

            local function spawnScript(entry, isClient)
                pending += 1
                task.spawn(function()
                    local f, ferr = RealLoadstring(entry.src)
                    if not f then
                        warn("[SmartRequire] loadstring failed:", entry.inst:GetFullName(), "-", ferr)
                    else
                        infectScript(f, entry.inst, isClient)
                        local ok, err = pcall(f)
                        if not ok then
                            warn("[SmartRequire] Runtime error:", entry.inst:GetFullName(), "-", err)
                        end
                    end
                    pending -= 1
                    if pending == 0 then doneEvent:Fire() end
                end)
            end

            for _, entry in ipairs(serverScripts) do spawnScript(entry, false) end
            for _, entry in ipairs(localScripts)  do spawnScript(entry, true)  end

            -- PASS 4: Wait for completion (5 second cap)
            if pending > 0 then
                local timeout = false
                local timeoutThread = task.delay(5, function()
                    timeout = true
                    if pending > 0 then
                        warn("[SmartRequire] Timed out waiting for scripts in:", container.Name, "(", pending, "still pending)")
                        doneEvent:Fire()
                    end
                end)
                doneEvent.Event:Wait()
                if not timeout then
                    pcall(function() timeoutThread:Disconnect() end)
                    task.wait()
                end
            end

            _G._ActiveToolContext = prevCtx
            doneEvent:Destroy()

            module = container:IsA("ModuleScript") and container
                or container:FindFirstChildWhichIsA("ModuleScript", true)
        end
    end

    -- 3. Direct Instance
    if not module and typeof(id) == "Instance" then module = id end

    -- 4. Execute ModuleScript and cache
    if module and typeof(module) == "Instance" and module:IsA("ModuleScript") then
        _G._ModuleCache[cacheKey] = false

        local prevCtx = _G._ActiveToolContext
        if not prevCtx then
            _G._ActiveToolContext = module.Parent and module.Parent.Name or module.Name
        end

        local src = module.Source
        if src and src ~= "" then
            local f, ferr = RealLoadstring(src)
            if f then
                Infect(f, module)
                local ok, result = pcall(f)
                _G._ActiveToolContext = prevCtx
                if ok then
                    if type(result) == "function" then result = Infect(result, module) end
                    _G._ModuleCache[cacheKey] = result
                    return result
                else
                    warn("[SmartRequire] Module error in", module.Name, ":", result)
                    _G._ModuleCache[cacheKey] = nil
                end
            else
                _G._ActiveToolContext = prevCtx
                warn("[SmartRequire] loadstring failed for", module.Name, ":", ferr)
                _G._ModuleCache[cacheKey] = nil
            end
        else
            _G._ActiveToolContext = prevCtx
        end
    end

    -- 5. Native fallback
    local ok, fallback = pcall(NativeRequire, id)
    if ok then
        if type(fallback) == "function" then fallback = Infect(fallback, module) end
        _G._ModuleCache[cacheKey] = fallback
        return fallback
    end

    warn("[SmartRequire] Could not resolve module:", tostring(id))
    return nil
end


-- ═══════════════════════════════════════════════════════════════════
--  INSERter STATE
-- ═══════════════════════════════════════════════════════════════════
local enableScripts = false
local enableDecompilation = false
local placeMode = false
local currentPlaceAssetId = nil
local placingIndicator = nil
local currentPreviewModel = nil
local currentRotation = CFrame.Angles(0, 0, 0)

-- ═══════════════════════════════════════════════════════════════════
--  DECOMPILER
-- ═══════════════════════════════════════════════════════════════════
local decompilerLoaded = false
local function loadDecompiler()
    if decompilerLoaded then return true end
    local success, result = pcall(function()
        local decompsrc = game:HttpGet("https://raw.githubusercontent.com/playvoras/Fork-Advanced-Decompiler-V3/main/init.lua", true)
        local function loaddecomp(decomptimeout)
            local CONSTANTS = [[
local ENABLED_REMARKS = {
    NATIVE_REMARK = true,
    INLINE_REMARK = true
}
local DECOMPILER_TIMEOUT = ]] .. decomptimeout .. [[

local READER_FLOAT_PRECISION = 99
local SHOW_INSTRUCTION_LINES = false
local SHOW_REFERENCES = false
local SHOW_OPERATION_NAMES = false
local SHOW_MISC_OPERATIONS = false
local LIST_USED_GLOBALS = false
local RETURN_ELAPSED_TIME = false
]]
            loadstring(string.gsub(decompsrc, ";;CONSTANTS HERE;;", CONSTANTS), "Advanced-Decompiler-V3")()
        end
        getgenv().loaddecomp = loaddecomp
        loaddecomp(190000000000)
    end)
    if success then
        decompilerLoaded = true
        print("[Inserter] Advanced Decompiler loaded")
    else
        warn("[Inserter] Failed to load decompiler:", tostring(result))
    end
    return success
end

-- Character tracking
LocalPlayer.CharacterAdded:Connect(function(c)
    character = c
end)


-- ═══════════════════════════════════════════════════════════════════
--  SKY & LIGHTING CONFIGURATION (from Inserter v5.4)
-- ═══════════════════════════════════════════════════════════════════
local function configureSkybox(skybox)
    if not skybox or not skybox:IsA("Sky") then return false end
    local existingSky = Lighting:FindFirstChildOfClass("Sky")
    if existingSky then existingSky:Destroy() end
    local newSky = skybox:Clone()
    newSky.Parent = Lighting
    print("[Inserter] Skybox applied:", skybox.Name)
    return true
end

local function applySkyTexture(texture)
    if not texture or not texture:IsA("Texture") then return false end
    local textureName = texture.Name:lower()
    local isSkyTexture = textureName:find("sky") or textureName:find("cloud") or
                         textureName:find("sun") or textureName:find("moon") or textureName:find("star")
    if not isSkyTexture then return false end
    local sky = Lighting:FindFirstChildOfClass("Sky")
    if not sky then sky = Instance.new("Sky"); sky.Parent = Lighting end
    if textureName:find("sky") or textureName:find("bk") or textureName:find("background") then
        sky.SkyboxBk = texture; sky.SkyboxDn = texture; sky.SkyboxFt = texture
        sky.SkyboxLf = texture; sky.SkyboxRt = texture; sky.SkyboxUp = texture
    elseif textureName:find("cloud") then sky.SkyboxClouds = texture
    elseif textureName:find("sun") then sky.SunTexture = texture
    elseif textureName:find("moon") then sky.MoonTexture = texture
    end
    print("[Inserter] Sky texture applied:", texture.Name)
    return true
end

local function configureLighting(lightingConfig)
    if not lightingConfig or not lightingConfig:IsA("Configuration") then return false end
    for _, property in ipairs({"Brightness", "Ambient", "OutdoorAmbient", "ColorShift_Bottom", "ColorShift_Top", "FogColor", "FogEnd", "FogStart"}) do
        if lightingConfig:FindFirstChild(property) then
            local value = lightingConfig[property].Value
            if Lighting[property] ~= nil then Lighting[property] = value end
        end
    end
    print("[Inserter] Lighting configuration applied:", lightingConfig.Name)
    return true
end


-- ═══════════════════════════════════════════════════════════════════
--  MODEL PREVIEW & PLACEMENT (from Inserter v5.4)
-- ═══════════════════════════════════════════════════════════════════
local function createPreviewModel(assetId)
    if currentPreviewModel then currentPreviewModel:Destroy(); currentPreviewModel = nil end
    assetId = tonumber(assetId)
    if not assetId then return nil end
    local ok, objects = pcall(function() return game:GetObjects("rbxassetid://" .. assetId) end)
    if not ok or not objects or #objects == 0 then warn("[Preview] Failed:", assetId); return nil end
    local preview = objects[1]:Clone()
    for _, part in ipairs(preview:GetDescendants()) do
        if part:IsA("BasePart") then
            part.Transparency = 0.7; part.CanCollide = false; part.Anchored = true
            part.Material = Enum.Material.ForceField; part.Color = Color3.fromRGB(100, 200, 255)
        end
    end
    for _, v in preview:GetDescendants() do
        if v:IsA("Script") or v:IsA("LocalScript") or v:IsA("ModuleScript") then v:Destroy() end
    end
    preview.Name = "PlacePreviewModel"; preview.Parent = workspace
    return preview
end

local function updatePreviewPosition()
    if currentPreviewModel and Mouse.Hit then
        local pos = Mouse.Hit.p + Vector3.new(0, 2.5, 0)
        local cf = CFrame.new(pos) * currentRotation
        if currentPreviewModel.PrimaryPart then
            currentPreviewModel:SetPrimaryPartCFrame(cf)
        else
            currentPreviewModel:PivotTo(cf)
        end
    end
end

local function createIndicator()
    if placingIndicator then placingIndicator:Destroy() end
    placingIndicator = Instance.new("Model")
    placingIndicator.Name = "PlacePreview"; placingIndicator.Parent = workspace
    local base = Instance.new("Part")
    base.Size = Vector3.new(5, 0.1, 5); base.Material = Enum.Material.Neon
    base.Color = Color3.fromRGB(0, 255, 255); base.Transparency = 0.6
    base.Anchored = true; base.CanCollide = false; base.Shape = Enum.PartType.Cylinder
    base.Parent = placingIndicator
    local arrow = Instance.new("Part")
    arrow.Size = Vector3.new(0.5, 0.5, 4); arrow.Color = Color3.fromRGB(255, 255, 0)
    arrow.Material = Enum.Material.ForceField; arrow.Anchored = true; arrow.CanCollide = false
    arrow.Parent = placingIndicator
end

local function resetRotation() currentRotation = CFrame.Angles(0, 0, 0) end


-- ═══════════════════════════════════════════════════════════════════
--  SMART PARENTING (enhanced from Inserter v5.4 + SSA sky support)
-- ═══════════════════════════════════════════════════════════════════
local function smartParent(model, customCFrame)
    if not model then return end
    -- Special type handling
    if model:IsA("Sky") then configureSkybox(model); return end
    if model:IsA("Texture") then if applySkyTexture(model) then return end end
    if model:IsA("Configuration") and model.Name:find("Lighting") then configureLighting(model); return end
    -- Check for sky objects inside model
    local skybox = model:FindFirstChildOfClass("Sky")
    if skybox then configureSkybox(skybox) end
    for _, texture in ipairs(model:GetDescendants()) do
        if texture:IsA("Texture") then applySkyTexture(texture) end
    end
    local lightingConfig = model:FindFirstChildWhichIsA("Configuration")
    if lightingConfig then configureLighting(lightingConfig) end
    -- Standard parenting
    if model:IsA("Tool") then model.Parent = backpack; return end
    if model:IsA("ScreenGui") then model.ResetOnSpawn = false; model.Parent = playerGui; return end
    if model:IsA("Accessory") or model:FindFirstChildWhichIsA("Accessory") then model.Parent = character; return end
    if character and character:FindFirstChild("HumanoidRootPart") then
        local targetCFrame = customCFrame or (character.HumanoidRootPart.CFrame * CFrame.new(0, 8, 0))
        if model.PrimaryPart then model:SetPrimaryPartCFrame(targetCFrame)
        else model:PivotTo(targetCFrame) end
    end
    model.Parent = workspace
end


-- ═══════════════════════════════════════════════════════════════════
--  ENHANCED SCRIPT HANDLING — now uses SSA Infect sandbox
-- ═══════════════════════════════════════════════════════════════════
local function handleScript(originalScript, clonedScript)
    if not enableScripts then clonedScript:Destroy(); return end

    local src = originalScript.Source
    if not src or src:len() < 5 then clonedScript.Disabled = false; return end

    -- Attempt decompilation for encoded scripts
    if enableDecompilation and decompilerLoaded and getgenv().decompile then
        pcall(function()
            local decompiled = getgenv().decompile(originalScript)
            if decompiled and decompiled:len() > 100 then
                src = "-- Decompiled Script\n" .. decompiled
                print("[Inserter] Decompiled:", originalScript.Name)
            end
        end)
    end

    -- Use Infect sandbox instead of simple loadstring
    local f, err = RealLoadstring(src)
    if f then
        -- Infect the function with a full sandbox environment
        -- The clonedScript becomes the "script" reference inside
        Infect(f, clonedScript)

        task.spawn(function()
            task.wait(0.3)
            _G.__ROB_SCRIPT_REF = clonedScript
            local ok, runErr = pcall(f)
            _G.__ROB_SCRIPT_REF = nil
            if ok then
                clonedScript.Disabled = false
            else
                local errorMsg = tostring(runErr or "")
                if errorMsg:find("Humanoid") or errorMsg:find("HumanoidRootPart") or
                   errorMsg:find("Handle") or errorMsg:find("not found") then
                    task.wait(0.5)
                    _G.__ROB_SCRIPT_REF = clonedScript
                    local retryOk = pcall(f)
                    _G.__ROB_SCRIPT_REF = nil
                    if retryOk then clonedScript.Disabled = false
                    else warn("[Inserter] Script error:", originalScript.Name, runErr); clonedScript:Destroy() end
                else
                    warn("[Inserter] Script error:", originalScript.Name, runErr); clonedScript:Destroy()
                end
            end
        end)
    else
        warn("[Inserter] Compilation failed:", originalScript.Name, err)
        clonedScript:Destroy()
    end
end

local function handleModuleScript(originalModule, clonedModule)
    if not enableScripts then clonedModule:Destroy(); return end
    local src = originalModule.Source
    if not src or src:len() < 5 then return end
    if enableDecompilation and decompilerLoaded and getgenv().decompile then
        pcall(function()
            local decompiled = getgenv().decompile(originalModule)
            if decompiled and decompiled:len() > 100 then
                src = "-- Decompiled Module\nreturn " .. decompiled
                print("[Inserter] Decompiled module:", originalModule.Name)
            end
        end)
    end
    clonedModule.Source = src
end


-- ═══════════════════════════════════════════════════════════════════
--  MAIN INSERT FUNCTION (enhanced with Infect sandbox)
-- ═══════════════════════════════════════════════════════════════════
local function insertAsset(assetId, customCFrame)
    assetId = tonumber(assetId)
    if not assetId then return false end
    local ok, objects = pcall(function() return game:GetObjects("rbxassetid://" .. assetId) end)
    if not ok or not objects or #objects == 0 then warn("[Inserter] Failed:", assetId); return false end

    local original = objects[1]
    local clone = original:Clone()

    -- Sky/lighting asset detection
    local isSkyAsset = original:IsA("Sky") or
        (original:IsA("Texture") and original.Name:lower():find("sky")) or
        (original:IsA("Configuration") and original.Name:find("Lighting"))
    if isSkyAsset then
        print("[Inserter] Sky/lighting asset:", original.Name)
        smartParent(clone, customCFrame); return true
    end

    -- Check for sky objects in descendants
    local hasSkyObjects = false
    for _, obj in ipairs(original:GetDescendants()) do
        if obj:IsA("Sky") or (obj:IsA("Texture") and obj.Name:lower():find("sky")) then
            hasSkyObjects = true; break
        end
    end
    if hasSkyObjects then
        print("[Inserter] Model contains sky objects:", original.Name)
        for _, obj in ipairs(clone:GetDescendants()) do
            if obj:IsA("Sky") then configureSkybox(obj)
            elseif obj:IsA("Texture") and obj.Name:lower():find("sky") then applySkyTexture(obj) end
        end
    end

    smartParent(clone, customCFrame)

    -- Script handling via Infect sandbox
    if enableScripts and not isSkyAsset then
        local cloneMap = {}
        local function buildMap(origObj, clonedObj)
            cloneMap[origObj] = clonedObj
            for _, child in ipairs(origObj:GetChildren()) do
                local clonedChild = clonedObj:FindFirstChild(child.Name)
                if clonedChild then buildMap(child, clonedChild) end
            end
        end
        buildMap(original, clone)

        local modulesToProcess = {}
        local scriptsToProcess = {}
        for _, obj in original:GetDescendants() do
            if obj:IsA("ModuleScript") then table.insert(modulesToProcess, obj)
            elseif obj:IsA("Script") or obj:IsA("LocalScript") then table.insert(scriptsToProcess, obj) end
        end

        -- Clear existing scripts from clone
        for _, v in clone:GetDescendants() do
            if v:IsA("Script") or v:IsA("LocalScript") or v:IsA("ModuleScript") then v:Destroy() end
        end

        -- Process modules first (dependencies)
        for _, module in ipairs(modulesToProcess) do
            local newModule = Instance.new("ModuleScript")
            newModule.Name = module.Name
            newModule.Parent = cloneMap[module.Parent] or clone
            handleModuleScript(module, newModule)
        end
        -- Process scripts
        for _, scriptObj in ipairs(scriptsToProcess) do
            local newScript = Instance.new(scriptObj.ClassName)
            newScript.Name = scriptObj.Name; newScript.Disabled = true
            newScript.Parent = cloneMap[scriptObj.Parent] or clone
            handleScript(scriptObj, newScript)
        end
    elseif not isSkyAsset then
        for _, v in clone:GetDescendants() do
            if v:IsA("Script") or v:IsA("LocalScript") or v:IsA("ModuleScript") then v:Destroy() end
        end
    end
    return true
end

local function batchInsert(text)
    local ids = {}
    for id in text:gmatch("%d+") do table.insert(ids, id) end
    if #ids == 0 then warn("[Inserter] No valid IDs found!"); return end
    for i, id in ipairs(ids) do
        task.spawn(function()
            task.wait((i - 1) * 0.35)
            local success = insertAsset(id)
            if success then print("[Inserter] Inserted model:", id) end
        end)
    end
    print("[Inserter] Batch inserting " .. #ids .. " models...")
end


-- ═══════════════════════════════════════════════════════════════════
--  THEMES (ported from SSA script — all 9 themes)
-- ═══════════════════════════════════════════════════════════════════
local THEMES = {
    -- 1. SSA (original)
    {
        name = "SSA",
        frameBg = Color3.fromRGB(18, 18, 28),  frameBgT = 0,
        headerBg = Color3.fromRGB(18, 18, 28),  headerText = Color3.new(1,1,1),
        outputBg = Color3.fromRGB(18, 18, 28),  outputBgT = 0,
        inputBg = Color3.fromRGB(35, 35, 45),   inputText = Color3.new(1,1,1),
        accent = Color3.fromRGB(70, 200, 255),  accent2 = Color3.fromRGB(120, 80, 255),
        btnBatch = Color3.fromRGB(0, 140, 255),  btnPlace = Color3.fromRGB(120, 80, 255),
        btnScript = Color3.fromRGB(60, 60, 70),  btnDecomp = Color3.fromRGB(80, 60, 70),
        btnSave = Color3.fromRGB(0, 100, 0),     btnLoad = Color3.fromRGB(0, 0, 130),
        btnTheme = Color3.fromRGB(100, 80, 0),   btnClose = Color3.fromRGB(200, 0, 0),
        btnExec = Color3.fromRGB(0, 120, 0),     outputText = Color3.new(1,1,1),
        errText = Color3.fromRGB(255, 80, 80),   infoText = Color3.fromRGB(180, 255, 180),
        borderCol = Color3.fromRGB(70, 200, 255), borderRadius = 22,
        strokeOn = true, strokeCol = Color3.fromRGB(70, 200, 255),
        listBg = Color3.fromRGB(18, 18, 28),    listItemBg = Color3.fromRGB(35, 35, 45),
        listItemText = Color3.fromRGB(80, 160, 255),
    },
    -- 2. c00lgui (black + red)
    {
        name = "c00lgui",
        frameBg = Color3.fromRGB(0,0,0),         frameBgT = 0,
        headerBg = Color3.fromRGB(0,0,0),         headerText = Color3.new(1,1,1),
        outputBg = Color3.fromRGB(0,0,0),         outputBgT = 0,
        inputBg = Color3.fromRGB(20,0,0),          inputText = Color3.new(1,1,1),
        accent = Color3.fromRGB(255,0,0),         accent2 = Color3.fromRGB(200,0,0),
        btnBatch = Color3.fromRGB(150,0,0),        btnPlace = Color3.fromRGB(120,0,0),
        btnScript = Color3.fromRGB(40,0,0),        btnDecomp = Color3.fromRGB(60,0,0),
        btnSave = Color3.fromRGB(0,80,0),          btnLoad = Color3.fromRGB(0,0,80),
        btnTheme = Color3.fromRGB(100,0,0),        btnClose = Color3.fromRGB(255,0,0),
        btnExec = Color3.fromRGB(150,0,0),         outputText = Color3.new(1,1,1),
        errText = Color3.fromRGB(255,80,80),       infoText = Color3.fromRGB(255,150,150),
        borderCol = Color3.fromRGB(255,0,0),       borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(255,0,0),
        listBg = Color3.fromRGB(0,0,0),           listItemBg = Color3.fromRGB(30,0,0),
        listItemText = Color3.fromRGB(255,60,60),
    },
    -- 3. TopKek (dark navy + crimson)
    {
        name = "TopKek",
        frameBg = Color3.fromRGB(14,23,29),       frameBgT = 0,
        headerBg = Color3.fromRGB(7,11,15),        headerText = Color3.fromRGB(184,7,54),
        outputBg = Color3.fromRGB(10,18,23),       outputBgT = 0,
        inputBg = Color3.fromRGB(10,18,23),        inputText = Color3.new(1,1,1),
        accent = Color3.fromRGB(184,7,54),         accent2 = Color3.fromRGB(62,62,62),
        btnBatch = Color3.fromRGB(150,5,44),       btnPlace = Color3.fromRGB(62,62,62),
        btnScript = Color3.fromRGB(20,30,40),      btnDecomp = Color3.fromRGB(30,20,30),
        btnSave = Color3.fromRGB(7,11,15),         btnLoad = Color3.fromRGB(7,11,15),
        btnTheme = Color3.fromRGB(7,11,15),        btnClose = Color3.fromRGB(200,10,60),
        btnExec = Color3.fromRGB(150,5,44),        outputText = Color3.fromRGB(230,230,230),
        errText = Color3.fromRGB(255,80,80),       infoText = Color3.fromRGB(184,100,120),
        borderCol = Color3.fromRGB(62,62,62),      borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(62,62,62),
        listBg = Color3.fromRGB(14,23,29),        listItemBg = Color3.fromRGB(10,18,23),
        listItemText = Color3.fromRGB(184,7,54),
    },
    -- 4. TopKek V4 (deep burgundy)
    {
        name = "TopKekV4",
        frameBg = Color3.fromRGB(130,47,47),      frameBgT = 0,
        headerBg = Color3.fromRGB(159,57,57),      headerText = Color3.fromRGB(226,226,226),
        outputBg = Color3.fromRGB(120,42,42),      outputBgT = 0,
        inputBg = Color3.fromRGB(163,57,57),       inputText = Color3.fromRGB(226,226,226),
        accent = Color3.fromRGB(226,226,226),      accent2 = Color3.fromRGB(141,27,27),
        btnBatch = Color3.fromRGB(141,27,27),      btnPlace = Color3.fromRGB(141,27,27),
        btnScript = Color3.fromRGB(100,30,30),     btnDecomp = Color3.fromRGB(100,30,30),
        btnSave = Color3.fromRGB(141,27,27),       btnLoad = Color3.fromRGB(141,27,27),
        btnTheme = Color3.fromRGB(141,27,27),      btnClose = Color3.fromRGB(255,255,255),
        btnExec = Color3.fromRGB(141,27,27),       outputText = Color3.fromRGB(209,209,209),
        errText = Color3.fromRGB(255,130,130),     infoText = Color3.fromRGB(255,200,200),
        borderCol = Color3.fromRGB(130,47,47),     borderRadius = 0,
        strokeOn = false, strokeCol = Color3.new(0,0,0),
        listBg = Color3.fromRGB(120,42,42),        listItemBg = Color3.fromRGB(163,57,57),
        listItemText = Color3.fromRGB(226,226,226),
    },
    -- 5. T0PK3K V0.8 (grayscale)
    {
        name = "T0PK3K",
        frameBg = Color3.fromRGB(110,110,110),    frameBgT = 0,
        headerBg = Color3.fromRGB(110,110,110),    headerText = Color3.fromRGB(209,209,209),
        outputBg = Color3.fromRGB(56,56,56),       outputBgT = 0,
        inputBg = Color3.fromRGB(100,100,100),     inputText = Color3.new(1,1,1),
        accent = Color3.fromRGB(255,255,255),      accent2 = Color3.fromRGB(168,168,168),
        btnBatch = Color3.fromRGB(56,56,56),       btnPlace = Color3.fromRGB(56,56,56),
        btnScript = Color3.fromRGB(80,80,80),      btnDecomp = Color3.fromRGB(80,80,80),
        btnSave = Color3.fromRGB(56,56,56),        btnLoad = Color3.fromRGB(56,56,56),
        btnTheme = Color3.fromRGB(56,56,56),       btnClose = Color3.fromRGB(255,60,60),
        btnExec = Color3.fromRGB(56,56,56),        outputText = Color3.fromRGB(209,209,209),
        errText = Color3.fromRGB(255,60,60),       infoText = Color3.fromRGB(209,209,209),
        borderCol = Color3.fromRGB(67,67,67),      borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(67,67,67),
        listBg = Color3.fromRGB(56,56,56),         listItemBg = Color3.fromRGB(80,80,80),
        listItemText = Color3.fromRGB(209,209,209),
    },
    -- 6. RoXploit 6.0 (blue)
    {
        name = "RoXploit",
        frameBg = Color3.fromRGB(0,75,150),        frameBgT = 0,
        headerBg = Color3.fromRGB(0,75,150),        headerText = Color3.new(1,1,1),
        outputBg = Color3.fromRGB(0,60,120),       outputBgT = 0,
        inputBg = Color3.fromRGB(0,60,120),        inputText = Color3.new(1,1,1),
        accent = Color3.fromRGB(180,230,255),      accent2 = Color3.fromRGB(0,100,200),
        btnBatch = Color3.fromRGB(0,100,200),      btnPlace = Color3.fromRGB(0,100,200),
        btnScript = Color3.fromRGB(0,60,100),      btnDecomp = Color3.fromRGB(0,60,100),
        btnSave = Color3.fromRGB(0,80,0),          btnLoad = Color3.fromRGB(0,0,100),
        btnTheme = Color3.fromRGB(0,80,0),         btnClose = Color3.fromRGB(255,80,80),
        btnExec = Color3.fromRGB(0,100,200),       outputText = Color3.new(1,1,1),
        errText = Color3.fromRGB(255,120,120),     infoText = Color3.fromRGB(180,230,255),
        borderCol = Color3.fromRGB(0,100,200),     borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(0,100,200),
        listBg = Color3.fromRGB(0,60,120),         listItemBg = Color3.fromRGB(0,80,160),
        listItemText = Color3.fromRGB(180,230,255),
    },
    -- 7. RC7 (light/white)
    {
        name = "RC7",
        frameBg = Color3.fromRGB(233,233,233),    frameBgT = 0,
        headerBg = Color3.fromRGB(233,233,233),    headerText = Color3.fromRGB(0,0,0),
        outputBg = Color3.fromRGB(255,255,255),    outputBgT = 0,
        inputBg = Color3.fromRGB(255,255,255),     inputText = Color3.fromRGB(0,0,0),
        accent = Color3.fromRGB(0,140,0),          accent2 = Color3.fromRGB(149,149,149),
        btnBatch = Color3.fromRGB(200,200,200),    btnPlace = Color3.fromRGB(200,200,200),
        btnScript = Color3.fromRGB(220,220,220),   btnDecomp = Color3.fromRGB(220,220,220),
        btnSave = Color3.fromRGB(0,120,0),         btnLoad = Color3.fromRGB(0,0,180),
        btnTheme = Color3.fromRGB(80,80,80),       btnClose = Color3.fromRGB(200,0,0),
        btnExec = Color3.fromRGB(0,120,0),         outputText = Color3.fromRGB(0,0,0),
        errText = Color3.fromRGB(200,0,0),         infoText = Color3.fromRGB(0,100,0),
        borderCol = Color3.fromRGB(149,149,149),   borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(149,149,149),
        listBg = Color3.fromRGB(255,255,255),      listItemBg = Color3.fromRGB(240,240,240),
        listItemText = Color3.fromRGB(0,0,180),
    },
    -- 8. Brack Hub V2 (white + amber + teal)
    {
        name = "BrackHub",
        frameBg = Color3.fromRGB(127,127,127),    frameBgT = 0,
        headerBg = Color3.fromRGB(255,255,255),    headerText = Color3.fromRGB(255,170,0),
        outputBg = Color3.fromRGB(200,200,200),    outputBgT = 0,
        inputBg = Color3.fromRGB(255,255,255),     inputText = Color3.fromRGB(27,42,53),
        accent = Color3.fromRGB(255,170,0),        accent2 = Color3.fromRGB(27,42,53),
        btnBatch = Color3.fromRGB(255,170,0),      btnPlace = Color3.fromRGB(27,42,53),
        btnScript = Color3.fromRGB(180,180,180),   btnDecomp = Color3.fromRGB(180,180,180),
        btnSave = Color3.fromRGB(0,100,0),         btnLoad = Color3.fromRGB(27,42,53),
        btnTheme = Color3.fromRGB(255,170,0),      btnClose = Color3.fromRGB(170,0,0),
        btnExec = Color3.fromRGB(255,170,0),       outputText = Color3.fromRGB(27,42,53),
        errText = Color3.fromRGB(170,0,0),         infoText = Color3.fromRGB(27,42,53),
        borderCol = Color3.fromRGB(27,42,53),      borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(27,42,53),
        listBg = Color3.fromRGB(200,200,200),      listItemBg = Color3.fromRGB(255,255,255),
        listItemText = Color3.fromRGB(255,170,0),
    },
    -- 9. Rocky Admin (dark red/black)
    {
        name = "Rocky",
        frameBg = Color3.fromRGB(21,0,0),          frameBgT = 0,
        headerBg = Color3.fromRGB(18,18,18),       headerText = Color3.new(1,1,1),
        outputBg = Color3.fromRGB(17,17,17),       outputBgT = 0,
        inputBg = Color3.fromRGB(17,17,17),        inputText = Color3.new(1,1,1),
        accent = Color3.fromRGB(255,60,60),        accent2 = Color3.fromRGB(27,42,53),
        btnBatch = Color3.fromRGB(38,0,0),         btnPlace = Color3.fromRGB(27,42,53),
        btnScript = Color3.fromRGB(30,0,0),        btnDecomp = Color3.fromRGB(30,0,0),
        btnSave = Color3.fromRGB(0,60,0),          btnLoad = Color3.fromRGB(0,0,60),
        btnTheme = Color3.fromRGB(60,0,0),         btnClose = Color3.fromRGB(255,0,0),
        btnExec = Color3.fromRGB(38,0,0),          outputText = Color3.new(1,1,1),
        errText = Color3.fromRGB(255,60,60),       infoText = Color3.fromRGB(255,120,120),
        borderCol = Color3.fromRGB(27,42,53),      borderRadius = 0,
        strokeOn = true, strokeCol = Color3.fromRGB(27,42,53),
        listBg = Color3.fromRGB(17,17,17),         listItemBg = Color3.fromRGB(30,0,0),
        listItemText = Color3.fromRGB(255,0,0),
    },
}

local currentThemeIdx = 1


-- ═══════════════════════════════════════════════════════════════════
--  BUILD UI — Combined Inserter + SSA features
-- ═══════════════════════════════════════════════════════════════════
local gui = RealInstance.new("ScreenGui", getui())
gui.Name = "RobInserterV6_SSA"
gui.ResetOnSpawn = false

-- Main frame
local frame = RealInstance.new("Frame")
frame.Size = UDim2.new(0, 500, 0, 520)
frame.Position = UDim2.new(0.5, -250, 0.5, -260)
frame.BackgroundColor3 = Color3.fromRGB(18, 18, 28)
frame.Active = true
frame.Draggable = true
frame.Parent = gui

local corner = RealInstance.new("UICorner", frame)
corner.CornerRadius = UDim.new(0, 22)
local stroke = RealInstance.new("UIStroke", frame)
stroke.Thickness = 3
stroke.Color = Color3.fromRGB(70, 200, 255)

-- ─── Title bar ────────────────────────────────────────────────────
local title = RealInstance.new("TextLabel")
title.Text = "Rob's Inserter v6.0 ULTIMATE + SSA"
title.Size = UDim2.new(1, -35, 0, 40)
title.BackgroundTransparency = 1
title.TextColor3 = Color3.new(1, 1, 1)
title.Font = Enum.Font.GothamBlack
title.TextSize = 20
title.TextXAlignment = Enum.TextXAlignment.Left
title.Parent = frame
do local p = RealInstance.new("UIPadding", title); p.PaddingLeft = UDim.new(0, 12); p.PaddingTop = UDim.new(0, 8) end

-- Close button
local closeBtn = RealInstance.new("TextButton")
closeBtn.Size = UDim2.new(0, 35, 0, 35)
closeBtn.Position = UDim2.new(1, -35, 0, 2)
closeBtn.BackgroundTransparency = 1
closeBtn.Font = Enum.Font.GothamBold
closeBtn.TextSize = 22
closeBtn.Text = "X"
closeBtn.TextColor3 = Color3.fromRGB(200, 0, 0)
closeBtn.Parent = frame
closeBtn.MouseButton1Click:Connect(function() gui:Destroy() end)

-- ─── Output console (scrollable log) ─────────────────────────────
local output = RealInstance.new("ScrollingFrame")
output.Size = UDim2.new(1, -40, 0, 80)
output.Position = UDim2.new(0, 20, 0, 45)
output.BackgroundColor3 = Color3.fromRGB(18, 18, 28)
output.BorderSizePixel = 0
output.ScrollBarThickness = 5
output.ClipsDescendants = true
output.ScrollingDirection = Enum.ScrollingDirection.Y
output.AutomaticCanvasSize = Enum.AutomaticSize.Y
output.CanvasSize = UDim2.new(0, 0, 0, 0)
output.Parent = frame
local outputLayout = RealInstance.new("UIListLayout", output)
outputLayout.SortOrder = Enum.SortOrder.LayoutOrder
outputLayout.Padding = UDim.new(0, 1)

local function newLine(txt, col)
    local t = THEMES[currentThemeIdx]
    local label = RealInstance.new("TextLabel", output)
    label.BackgroundTransparency = 1
    label.Font = Enum.Font.SourceSans
    label.TextColor3 = col or t.outputText
    label.TextSize = 13
    label.TextXAlignment = Enum.TextXAlignment.Left
    label.TextYAlignment = Enum.TextYAlignment.Top
    label.Text = txt
    label.TextWrapped = true
    label.Size = UDim2.new(1, -4, 0,
        TextService:GetTextSize(txt, label.TextSize, label.Font,
            Vector2.new(output.AbsoluteSize.X - 10, math.huge)).Y)
    task.defer(function() output.CanvasPosition = Vector2.new(0, math.huge) end)
end

-- ─── Input Box ────────────────────────────────────────────────────
local inputBox = RealInstance.new("TextBox")
inputBox.PlaceholderText = "Single ID or Batch (comma/space/newline)\nSky textures auto-apply to Lighting"
inputBox.Size = UDim2.new(1, -40, 0, 60)
inputBox.Position = UDim2.new(0, 20, 0, 130)
inputBox.BackgroundColor3 = Color3.fromRGB(35, 35, 45)
inputBox.TextColor3 = Color3.new(1, 1, 1)
inputBox.TextSize = 15
inputBox.PlaceholderColor3 = Color3.fromRGB(100, 100, 100)
inputBox.MultiLine = true
inputBox.TextWrapped = true
inputBox.ClearTextOnFocus = false
inputBox.Parent = frame
RealInstance.new("UICorner", inputBox).CornerRadius = UDim.new(0, 12)

-- ─── Row 1: BATCH INSERT + PLACE MODE ────────────────────────────
local batchBtn = RealInstance.new("TextButton")
batchBtn.Text = "BATCH INSERT"
batchBtn.Size = UDim2.new(0.48, -10, 0, 45)
batchBtn.Position = UDim2.new(0.02, 0, 0, 200)
batchBtn.BackgroundColor3 = Color3.fromRGB(0, 140, 255)
batchBtn.TextColor3 = Color3.new(1, 1, 1)
batchBtn.Font = Enum.Font.GothamBold
batchBtn.TextSize = 16
batchBtn.BorderSizePixel = 0
batchBtn.Parent = frame
RealInstance.new("UICorner", batchBtn).CornerRadius = UDim.new(0, 10)

local placeBtn = RealInstance.new("TextButton")
placeBtn.Text = "PLACE MODE: OFF"
placeBtn.Size = UDim2.new(0.48, -10, 0, 45)
placeBtn.Position = UDim2.new(0.52, 10, 0, 200)
placeBtn.BackgroundColor3 = Color3.fromRGB(120, 80, 255)
placeBtn.TextColor3 = Color3.new(1, 1, 1)
placeBtn.Font = Enum.Font.GothamBold
placeBtn.TextSize = 14
placeBtn.BorderSizePixel = 0
placeBtn.Parent = frame
RealInstance.new("UICorner", placeBtn).CornerRadius = UDim.new(0, 10)

-- ─── Row 2: Scripts + Decompile ──────────────────────────────────
local scriptToggle = RealInstance.new("TextButton")
scriptToggle.Text = "Scripts: OFF"
scriptToggle.Size = UDim2.new(0.48, -10, 0, 40)
scriptToggle.Position = UDim2.new(0.02, 0, 0, 250)
scriptToggle.BackgroundColor3 = Color3.fromRGB(60, 60, 70)
scriptToggle.TextColor3 = Color3.new(1, 1, 1)
scriptToggle.Font = Enum.Font.GothamBold
scriptToggle.TextSize = 14
scriptToggle.BorderSizePixel = 0
scriptToggle.Parent = frame
RealInstance.new("UICorner", scriptToggle).CornerRadius = UDim.new(0, 10)

local decompileToggle = RealInstance.new("TextButton")
decompileToggle.Text = "Decompile: OFF"
decompileToggle.Size = UDim2.new(0.48, -10, 0, 40)
decompileToggle.Position = UDim2.new(0.52, 10, 0, 250)
decompileToggle.BackgroundColor3 = Color3.fromRGB(80, 60, 70)
decompileToggle.TextColor3 = Color3.new(1, 1, 1)
decompileToggle.Font = Enum.Font.GothamBold
decompileToggle.TextSize = 13
decompileToggle.BorderSizePixel = 0
decompileToggle.Parent = frame
RealInstance.new("UICorner", decompileToggle).CornerRadius = UDim.new(0, 10)

-- ─── Row 3: SAV + LOD + THM (ported from SSA) ───────────────────
local saveBtn = RealInstance.new("TextButton")
saveBtn.Text = "SAV"
saveBtn.Size = UDim2.new(0.30, -10, 0, 35)
saveBtn.Position = UDim2.new(0.02, 0, 0, 295)
saveBtn.BackgroundColor3 = Color3.fromRGB(0, 100, 0)
saveBtn.TextColor3 = Color3.fromRGB(80, 220, 120)
saveBtn.Font = Enum.Font.GothamBold
saveBtn.TextSize = 14
saveBtn.BorderSizePixel = 0
saveBtn.Parent = frame
RealInstance.new("UICorner", saveBtn).CornerRadius = UDim.new(0, 8)

local loadBtn = RealInstance.new("TextButton")
loadBtn.Text = "LOD"
loadBtn.Size = UDim2.new(0.30, -10, 0, 35)
loadBtn.Position = UDim2.new(0.35, 0, 0, 295)
loadBtn.BackgroundColor3 = Color3.fromRGB(0, 0, 130)
loadBtn.TextColor3 = Color3.fromRGB(80, 160, 255)
loadBtn.Font = Enum.Font.GothamBold
loadBtn.TextSize = 14
loadBtn.BorderSizePixel = 0
loadBtn.Parent = frame
RealInstance.new("UICorner", loadBtn).CornerRadius = UDim.new(0, 8)

local themeBtn = RealInstance.new("TextButton")
themeBtn.Text = "THM"
themeBtn.Size = UDim2.new(0.30, -10, 0, 35)
themeBtn.Position = UDim2.new(0.68, 0, 0, 295)
themeBtn.BackgroundColor3 = Color3.fromRGB(100, 80, 0)
themeBtn.TextColor3 = Color3.fromRGB(220, 200, 60)
themeBtn.Font = Enum.Font.GothamBold
themeBtn.TextSize = 14
themeBtn.BorderSizePixel = 0
themeBtn.Parent = frame
RealInstance.new("UICorner", themeBtn).CornerRadius = UDim.new(0, 8)

-- ─── Info labels ──────────────────────────────────────────────────
local rotationLabel = RealInstance.new("TextLabel")
rotationLabel.Text = "Q/E = Rotate | Z/X = Tilt | R = Reset | Click to Place"
rotationLabel.Size = UDim2.new(1, -40, 0, 20)
rotationLabel.Position = UDim2.new(0, 20, 0, 340)
rotationLabel.BackgroundTransparency = 1
rotationLabel.TextColor3 = Color3.fromRGB(180, 255, 180)
rotationLabel.Font = Enum.Font.Gotham
rotationLabel.TextSize = 13
rotationLabel.Parent = frame

local previewInfo = RealInstance.new("TextLabel")
previewInfo.Text = "Live Model Preview in Place Mode"
previewInfo.Size = UDim2.new(1, -40, 0, 18)
previewInfo.Position = UDim2.new(0, 20, 0, 362)
previewInfo.BackgroundTransparency = 1
previewInfo.TextColor3 = Color3.fromRGB(100, 255, 200)
previewInfo.Font = Enum.Font.Gotham
previewInfo.TextSize = 12
previewInfo.Parent = frame

local skyInfo = RealInstance.new("TextLabel")
skyInfo.Text = "Sky textures auto-apply to Lighting service"
skyInfo.Size = UDim2.new(1, -40, 0, 18)
skyInfo.Position = UDim2.new(0, 20, 0, 382)
skyInfo.BackgroundTransparency = 1
skyInfo.TextColor3 = Color3.fromRGB(100, 200, 255)
skyInfo.Font = Enum.Font.Gotham
skyInfo.TextSize = 12
skyInfo.Parent = frame

local decompileInfo = RealInstance.new("TextLabel")
decompileInfo.Text = "Decompiler: LOADING..."
decompileInfo.Size = UDim2.new(1, -40, 0, 16)
decompileInfo.Position = UDim2.new(0, 20, 0, 402)
decompileInfo.BackgroundTransparency = 1
decompileInfo.TextColor3 = Color3.fromRGB(200, 200, 200)
decompileInfo.Font = Enum.Font.Gotham
decompileInfo.TextSize = 11
decompileInfo.Parent = frame

local sandboxInfo = RealInstance.new("TextLabel")
sandboxInfo.Text = "SSA Sandbox Engine: ACTIVE | SmartRequire: READY"
sandboxInfo.Size = UDim2.new(1, -40, 0, 16)
sandboxInfo.Position = UDim2.new(0, 20, 0, 420)
sandboxInfo.BackgroundTransparency = 1
sandboxInfo.TextColor3 = Color3.fromRGB(80, 220, 120)
sandboxInfo.Font = Enum.Font.Gotham
sandboxInfo.TextSize = 11
sandboxInfo.Parent = frame

local themeInfo = RealInstance.new("TextLabel")
themeInfo.Text = "Theme: SSA (1/" .. #THEMES .. ")"
themeInfo.Size = UDim2.new(1, -40, 0, 16)
themeInfo.Position = UDim2.new(0, 20, 0, 438)
themeInfo.BackgroundTransparency = 1
themeInfo.TextColor3 = Color3.fromRGB(220, 200, 60)
themeInfo.Font = Enum.Font.Gotham
themeInfo.TextSize = 11
themeInfo.Parent = frame


-- ═══════════════════════════════════════════════════════════════════
--  SAVE DIALOG (ported from SSA)
-- ═══════════════════════════════════════════════════════════════════
local saveDialog = RealInstance.new("Frame", frame)
saveDialog.Size = UDim2.new(1, 0, 0, 110)
saveDialog.Position = UDim2.new(0, 0, 1, -120)
saveDialog.BackgroundColor3 = Color3.new(0, 0, 0)
saveDialog.BackgroundTransparency = 0.15
saveDialog.BorderSizePixel = 0
saveDialog.ZIndex = 10
saveDialog.Visible = false
RealInstance.new("UICorner", saveDialog).CornerRadius = UDim.new(0, 12)

local dialogTitle = RealInstance.new("TextLabel", saveDialog)
dialogTitle.Size = UDim2.new(1, -16, 0, 20)
dialogTitle.Position = UDim2.new(0, 8, 0, 8)
dialogTitle.BackgroundTransparency = 1
dialogTitle.Font = Enum.Font.SourceSansBold
dialogTitle.TextColor3 = Color3.fromRGB(80, 220, 120)
dialogTitle.TextSize = 13
dialogTitle.TextXAlignment = Enum.TextXAlignment.Left
dialogTitle.Text = "Save script as..."
dialogTitle.ZIndex = 11

local nameBox = RealInstance.new("TextBox", saveDialog)
nameBox.AnchorPoint = Vector2.new(0, 0.5)
nameBox.Position = UDim2.new(0, 8, 0, 55)
nameBox.Size = UDim2.new(1, -16, 0, 26)
nameBox.BackgroundColor3 = Color3.new(0, 0, 0)
nameBox.BackgroundTransparency = 0.4
nameBox.BorderSizePixel = 0
nameBox.Font = Enum.Font.SourceSans
nameBox.TextColor3 = Color3.new(1, 1, 1)
nameBox.TextSize = 13
nameBox.PlaceholderText = "filename (e.g. myscript)"
nameBox.PlaceholderColor3 = Color3.fromRGB(100, 100, 100)
nameBox.Text = ""
nameBox.ClearTextOnFocus = false
nameBox.TextXAlignment = Enum.TextXAlignment.Left
nameBox.ZIndex = 11
do local p = RealInstance.new("UIPadding", nameBox); p.PaddingLeft = UDim.new(0, 6) end
RealInstance.new("UICorner", nameBox).CornerRadius = UDim.new(0, 6)

local confirmBtn = RealInstance.new("TextButton", saveDialog)
confirmBtn.Position = UDim2.new(0, 8, 0, 82)
confirmBtn.Size = UDim2.new(0.5, -12, 0, 24)
confirmBtn.BackgroundColor3 = Color3.new(0, 0.18, 0)
confirmBtn.BackgroundTransparency = 0.3
confirmBtn.BorderSizePixel = 0
confirmBtn.Font = Enum.Font.SourceSansBold
confirmBtn.TextColor3 = Color3.fromRGB(80, 220, 120)
confirmBtn.TextSize = 13
confirmBtn.Text = "Save"
confirmBtn.ZIndex = 11
RealInstance.new("UICorner", confirmBtn).CornerRadius = UDim.new(0, 6)

local cancelSaveBtn = RealInstance.new("TextButton", saveDialog)
cancelSaveBtn.Position = UDim2.new(0.5, 4, 0, 82)
cancelSaveBtn.Size = UDim2.new(0.5, -12, 0, 24)
cancelSaveBtn.BackgroundColor3 = Color3.new(0.18, 0, 0)
cancelSaveBtn.BackgroundTransparency = 0.3
cancelSaveBtn.BorderSizePixel = 0
cancelSaveBtn.Font = Enum.Font.SourceSansBold
cancelSaveBtn.TextColor3 = Color3.fromRGB(220, 80, 80)
cancelSaveBtn.TextSize = 13
cancelSaveBtn.Text = "Cancel"
cancelSaveBtn.ZIndex = 11
RealInstance.new("UICorner", cancelSaveBtn).CornerRadius = UDim.new(0, 6)


-- ═══════════════════════════════════════════════════════════════════
--  LOAD DIALOG (ported from SSA)
-- ═══════════════════════════════════════════════════════════════════
local loadDialog = RealInstance.new("Frame", frame)
loadDialog.Size = UDim2.new(1, 0, 0, 180)
loadDialog.Position = UDim2.new(0, 0, 1, -190)
loadDialog.BackgroundColor3 = Color3.new(0, 0, 0)
loadDialog.BackgroundTransparency = 0.15
loadDialog.BorderSizePixel = 0
loadDialog.ZIndex = 10
loadDialog.Visible = false
RealInstance.new("UICorner", loadDialog).CornerRadius = UDim.new(0, 12)

local loadDialogTitle = RealInstance.new("TextLabel", loadDialog)
loadDialogTitle.Size = UDim2.new(1, -56, 0, 20)
loadDialogTitle.Position = UDim2.new(0, 8, 0, 6)
loadDialogTitle.BackgroundTransparency = 1
loadDialogTitle.Font = Enum.Font.SourceSansBold
loadDialogTitle.TextColor3 = Color3.fromRGB(80, 160, 255)
loadDialogTitle.TextSize = 13
loadDialogTitle.TextXAlignment = Enum.TextXAlignment.Left
loadDialogTitle.Text = "Load script - click to paste into input"
loadDialogTitle.ZIndex = 11

local cancelLoadBtn = RealInstance.new("TextButton", loadDialog)
cancelLoadBtn.Position = UDim2.new(1, -50, 0, 6)
cancelLoadBtn.Size = UDim2.new(0, 42, 0, 20)
cancelLoadBtn.BackgroundColor3 = Color3.new(0.18, 0, 0)
cancelLoadBtn.BackgroundTransparency = 0.3
cancelLoadBtn.BorderSizePixel = 0
cancelLoadBtn.Font = Enum.Font.SourceSansBold
cancelLoadBtn.TextColor3 = Color3.fromRGB(220, 80, 80)
cancelLoadBtn.TextSize = 12
cancelLoadBtn.Text = "Close"
cancelLoadBtn.ZIndex = 11
RealInstance.new("UICorner", cancelLoadBtn).CornerRadius = UDim.new(0, 6)

local fileList = RealInstance.new("ScrollingFrame", loadDialog)
fileList.Position = UDim2.new(0, 4, 0, 30)
fileList.Size = UDim2.new(1, -8, 1, -38)
fileList.BackgroundColor3 = Color3.new(0, 0, 0)
fileList.BackgroundTransparency = 0.6
fileList.BorderSizePixel = 0
fileList.ScrollBarThickness = 5
fileList.ClipsDescendants = true
fileList.ScrollingDirection = Enum.ScrollingDirection.Y
fileList.AutomaticCanvasSize = Enum.AutomaticSize.Y
fileList.CanvasSize = UDim2.new(0, 0, 0, 0)
fileList.ZIndex = 11
local fileListLayout = RealInstance.new("UIListLayout", fileList)
fileListLayout.SortOrder = Enum.SortOrder.LayoutOrder
fileListLayout.Padding = UDim.new(0, 2)


-- ═══════════════════════════════════════════════════════════════════
--  FILE HELPERS (ported from SSA)
-- ═══════════════════════════════════════════════════════════════════
local SAVE_DIR = "inserter_scripts"

local function ensureDir()
    if makefolder then pcall(makefolder, SAVE_DIR) end
end


-- ═══════════════════════════════════════════════════════════════════
--  THEME APPLICATION
-- ═══════════════════════════════════════════════════════════════════
local function applyTheme(idx)
    currentThemeIdx = idx
    local t = THEMES[idx]

    -- Frame
    frame.BackgroundColor3 = t.frameBg
    corner.CornerRadius = UDim.new(0, t.borderRadius)

    -- Stroke
    stroke.Thickness = t.strokeOn and 3 or 0
    stroke.Color = t.strokeCol

    -- Title
    title.BackgroundColor3 = t.headerBg
    title.TextColor3 = t.headerText

    -- Output
    output.BackgroundColor3 = t.outputBg

    -- Input
    inputBox.BackgroundColor3 = t.inputBg
    inputBox.TextColor3 = t.inputText

    -- Buttons - Row 1
    batchBtn.BackgroundColor3 = t.btnBatch
    placeBtn.BackgroundColor3 = t.btnPlace

    -- Buttons - Row 2
    scriptToggle.BackgroundColor3 = t.btnScript
    decompileToggle.BackgroundColor3 = t.btnDecomp

    -- Buttons - Row 3 (SSA)
    saveBtn.BackgroundColor3 = t.btnSave
    saveBtn.TextColor3 = t.accent
    loadBtn.BackgroundColor3 = t.btnLoad
    loadBtn.TextColor3 = t.listItemText
    themeBtn.BackgroundColor3 = t.btnTheme
    themeBtn.TextColor3 = t.accent

    -- Close
    closeBtn.TextColor3 = t.btnClose

    -- Info labels
    rotationLabel.TextColor3 = t.infoText
    previewInfo.TextColor3 = t.infoText
    skyInfo.TextColor3 = t.infoText
    themeInfo.TextColor3 = t.accent
    sandboxInfo.TextColor3 = t.accent

    -- Dialogs
    saveDialog.BackgroundColor3 = t.listBg
    loadDialog.BackgroundColor3 = t.listBg

    -- Re-colour existing output lines
    for _, lbl in ipairs(output:GetChildren()) do
        if lbl:IsA("TextLabel") then lbl.TextColor3 = t.outputText end
    end

    -- Update theme info label
    themeInfo.Text = "Theme: " .. t.name .. " (" .. idx .. "/" .. #THEMES .. ")"
end


-- ═══════════════════════════════════════════════════════════════════
--  SAVE LOGIC
-- ═══════════════════════════════════════════════════════════════════
local function doSave()
    local t = THEMES[currentThemeIdx]
    local name = nameBox.Text:match("^%s*(.-)%s*$")
    if name == "" then
        newLine("[Save] Filename cannot be empty.", t.errText)
        saveDialog.Visible = false; return
    end
    name = name:gsub("%.lua$", "")
    local path = SAVE_DIR .. "/" .. name .. ".lua"
    local src = inputBox.Text
    if src == "" then
        newLine("[Save] Nothing to save.", Color3.fromRGB(255, 200, 60))
        saveDialog.Visible = false; nameBox.Text = ""; return
    end
    local ok, err = pcall(function()
        ensureDir()
        writefile(path, src)
    end)
    saveDialog.Visible = false; nameBox.Text = ""
    if ok then
        newLine("[Save] Saved -> " .. path, t.accent)
    else
        newLine("[Save] Failed: " .. tostring(err), t.errText)
    end
end

saveBtn.MouseButton1Click:Connect(function()
    if inputBox.Text == "" then
        newLine("[Save] Nothing to save.", Color3.fromRGB(255, 200, 60)); return
    end
    saveDialog.Visible = true
    nameBox:CaptureFocus()
end)
confirmBtn.MouseButton1Click:Connect(doSave)
cancelSaveBtn.MouseButton1Click:Connect(function()
    saveDialog.Visible = false; nameBox.Text = ""
end)
nameBox.FocusLost:Connect(function(enter) if enter then doSave() end end)


-- ═══════════════════════════════════════════════════════════════════
--  LOAD LOGIC
-- ═══════════════════════════════════════════════════════════════════
local function refreshFileList()
    for _, child in ipairs(fileList:GetChildren()) do
        if not child:IsA("UIListLayout") then child:Destroy() end
    end
    local t = THEMES[currentThemeIdx]
    local files = {}
    local ok, result = pcall(listfiles, SAVE_DIR)
    if ok and type(result) == "table" then
        for _, path in ipairs(result) do
            if path:match("%.lua$") then table.insert(files, path) end
        end
    end
    if #files == 0 then
        local empty = RealInstance.new("TextLabel", fileList)
        empty.Size = UDim2.new(1, 0, 0, 22)
        empty.BackgroundTransparency = 1
        empty.Font = Enum.Font.SourceSans
        empty.TextColor3 = Color3.fromRGB(120, 120, 120)
        empty.TextSize = 13
        empty.Text = "(no saved scripts)"
        empty.TextXAlignment = Enum.TextXAlignment.Left
        empty.ZIndex = 12
        do local p = RealInstance.new("UIPadding", empty); p.PaddingLeft = UDim.new(0, 6) end
        return
    end
    table.sort(files)
    for _, fullPath in ipairs(files) do
        local displayName = (fullPath:match("([^/\\]+)$") or fullPath):gsub("%.lua$", "")
        local row = RealInstance.new("TextButton", fileList)
        row.Size = UDim2.new(1, 0, 0, 24)
        row.BackgroundColor3 = t.listItemBg
        row.BackgroundTransparency = 0.3
        row.BorderSizePixel = 0
        row.Font = Enum.Font.SourceSans
        row.TextColor3 = t.listItemText
        row.TextSize = 13
        row.TextXAlignment = Enum.TextXAlignment.Left
        row.Text = "  > " .. displayName
        row.ZIndex = 12
        RealInstance.new("UICorner", row).CornerRadius = UDim.new(0, 6)
        do local p = RealInstance.new("UIPadding", row); p.PaddingLeft = UDim.new(0, 4) end
        row.MouseButton1Click:Connect(function()
            local readOk, content = pcall(readfile, fullPath)
            if readOk and type(content) == "string" then
                inputBox.Text = content
                loadDialog.Visible = false
                newLine("[Load] <- " .. displayName, t.listItemText)
            else
                newLine("[Load] Failed to read: " .. tostring(content), t.errText)
                loadDialog.Visible = false
            end
        end)
    end
end

loadBtn.MouseButton1Click:Connect(function()
    ensureDir()
    refreshFileList()
    loadDialog.Visible = true
end)
cancelLoadBtn.MouseButton1Click:Connect(function() loadDialog.Visible = false end)


-- ═══════════════════════════════════════════════════════════════════
--  THEME CYCLING
-- ═══════════════════════════════════════════════════════════════════
themeBtn.MouseButton1Click:Connect(function()
    local nextIdx = (currentThemeIdx % #THEMES) + 1
    applyTheme(nextIdx)
    newLine("[Theme] -> " .. THEMES[nextIdx].name, THEMES[nextIdx].accent)
end)


-- ═══════════════════════════════════════════════════════════════════
--  BUTTON FUNCTIONALITY (from Inserter v5.4)
-- ═══════════════════════════════════════════════════════════════════

-- Batch Insert
batchBtn.MouseButton1Click:Connect(function()
    local text = inputBox.Text:gsub("%s", "")
    if text == "" then newLine("[Insert] Enter at least one model ID.", THEMES[currentThemeIdx].errText); return end
    batchInsert(inputBox.Text)
    inputBox.Text = ""
end)

-- Script toggle
scriptToggle.MouseButton1Click:Connect(function()
    enableScripts = not enableScripts
    scriptToggle.Text = enableScripts and "Scripts: ON" or "Scripts: OFF"
    scriptToggle.BackgroundColor3 = enableScripts and Color3.fromRGB(0, 200, 0) or Color3.fromRGB(60, 60, 70)
    newLine("[Config] Scripts " .. (enableScripts and "ENABLED" or "DISABLED"),
        enableScripts and Color3.fromRGB(80, 220, 120) or Color3.fromRGB(200, 100, 100))
end)

-- Decompile toggle
decompileToggle.MouseButton1Click:Connect(function()
    if not decompilerLoaded then
        loadDecompiler()
        decompileInfo.Text = "Decompiler: " .. (decompilerLoaded and "LOADED" or "FAILED")
        decompileInfo.TextColor3 = decompilerLoaded and Color3.fromRGB(180, 255, 180) or Color3.fromRGB(255, 100, 100)
    end
    if decompilerLoaded then
        enableDecompilation = not enableDecompilation
        decompileToggle.Text = enableDecompilation and "Decompile: ON" or "Decompile: OFF"
        decompileToggle.BackgroundColor3 = enableDecompilation and Color3.fromRGB(200, 0, 200) or Color3.fromRGB(80, 60, 70)
        newLine("[Config] Decompilation " .. (enableDecompilation and "ENABLED" or "DISABLED"),
            enableDecompilation and Color3.fromRGB(200, 100, 255) or Color3.fromRGB(200, 100, 100))
    else
        decompileToggle.Text = "Decompile: FAILED"
        decompileToggle.BackgroundColor3 = Color3.fromRGB(255, 50, 50)
        task.wait(2)
        decompileToggle.Text = "Decompile: OFF"
        decompileToggle.BackgroundColor3 = Color3.fromRGB(80, 60, 70)
    end
end)

-- Place Mode toggle
placeBtn.MouseButton1Click:Connect(function()
    placeMode = not placeMode
    placeBtn.Text = placeMode and "PLACE MODE: ON" or "PLACE MODE: OFF"
    placeBtn.BackgroundColor3 = placeMode and Color3.fromRGB(0, 220, 255) or Color3.fromRGB(120, 80, 255)
    if placeMode then
        local id = inputBox.Text:match("%d+")
        if not id then
            placeMode = false
            placeBtn.Text = "PLACE MODE: OFF (No ID!)"
            placeBtn.BackgroundColor3 = Color3.fromRGB(255, 80, 80)
            newLine("[Place] Enter a model ID first.", THEMES[currentThemeIdx].errText)
            task.wait(2)
            placeBtn.Text = "PLACE MODE: OFF"
            placeBtn.BackgroundColor3 = Color3.fromRGB(120, 80, 255)
            return
        end
        currentPlaceAssetId = tonumber(id)
        createIndicator()
        resetRotation()
        currentPreviewModel = createPreviewModel(id)
        if currentPreviewModel then
            newLine("[Place] Preview for model " .. id .. " created.", Color3.fromRGB(100, 255, 200))
        else
            newLine("[Place] Could not create preview for " .. id .. ".", THEMES[currentThemeIdx].errText)
        end
    else
        if placingIndicator then placingIndicator:Destroy(); placingIndicator = nil end
        if currentPreviewModel then currentPreviewModel:Destroy(); currentPreviewModel = nil end
        currentPlaceAssetId = nil
        newLine("[Place] Mode deactivated.", Color3.fromRGB(180, 180, 180))
    end
end)


-- ═══════════════════════════════════════════════════════════════════
--  INPUT HANDLING
-- ═══════════════════════════════════════════════════════════════════

-- Enter key fires batch insert
inputBox.FocusLost:Connect(function(enter)
    if enter then batchBtn.MouseButton1Click:Fire() end
end)

-- Rotation controls
UserInputService.InputBegan:Connect(function(input, gp)
    if gp or not placeMode then return end
    if input.KeyCode == Enum.KeyCode.Q then
        currentRotation = currentRotation * CFrame.Angles(0, math.rad(-15), 0)
    elseif input.KeyCode == Enum.KeyCode.E then
        currentRotation = currentRotation * CFrame.Angles(0, math.rad(15), 0)
    elseif input.KeyCode == Enum.KeyCode.Z then
        currentRotation = currentRotation * CFrame.Angles(math.rad(-15), 0, 0)
    elseif input.KeyCode == Enum.KeyCode.X then
        currentRotation = currentRotation * CFrame.Angles(math.rad(15), 0, 0)
    elseif input.KeyCode == Enum.KeyCode.R then
        resetRotation()
    end
end)

-- Click to place
UserInputService.InputBegan:Connect(function(input)
    if placeMode and input.UserInputType == Enum.UserInputType.MouseButton1 and currentPlaceAssetId then
        local pos = Mouse.Hit.p + Vector3.new(0, 2.5, 0)
        local finalCFrame = CFrame.new(pos) * currentRotation
        local success = insertAsset(currentPlaceAssetId, finalCFrame)
        if success then
            newLine("[Place] Placed model at " .. tostring(pos), Color3.fromRGB(100, 255, 200))
        end
    end
end)

-- Real-time preview update
RunService.RenderStepped:Connect(function()
    if placeMode then
        if placingIndicator and Mouse.Hit then
            local pos = Mouse.Hit.p + Vector3.new(0, 2.5, 0)
            local cf = CFrame.new(pos) * currentRotation
            placingIndicator:PivotTo(cf)
        end
        updatePreviewPosition()
    end
end)


-- ═══════════════════════════════════════════════════════════════════
--  TOOL EQUIP/UNEQUIP HOOKS (ported from SSA)
-- ═══════════════════════════════════════════════════════════════════
local function hookCharacter(char)
    char.ChildAdded:Connect(function(child)
        if child:IsA("Tool") then
            local guis = _G._ToolGUIRegistry[child.Name]
            if guis then
                for _, g in ipairs(guis) do pcall(function() g.Enabled = true end) end
            end
        end
    end)
    char.ChildRemoved:Connect(function(child)
        if child:IsA("Tool") then
            local guis = _G._ToolGUIRegistry[child.Name]
            if guis then
                for _, g in ipairs(guis) do pcall(function() g.Enabled = false end) end
            end
        end
    end)
end

if LocalPlayer.Character then hookCharacter(LocalPlayer.Character) end
LocalPlayer.CharacterAdded:Connect(hookCharacter)


-- ═══════════════════════════════════════════════════════════════════
--  INITIALIZE
-- ═══════════════════════════════════════════════════════════════════
applyTheme(1)

-- Auto-load decompiler in background
task.spawn(function()
    loadDecompiler()
    decompileInfo.Text = "Decompiler: " .. (decompilerLoaded and "LOADED" or "FAILED")
    decompileInfo.TextColor3 = decompilerLoaded and Color3.fromRGB(180, 255, 180) or Color3.fromRGB(255, 100, 100)
end)

-- Print startup info to console
local ageDays = LocalPlayer.AccountAge
local years = math.floor(ageDays / 365)
local months = math.floor((ageDays % 365) / 30)

newLine("Rob's Inserter v6.0 ULTIMATE + SSA Sandbox Engine")
newLine("Player: " .. LocalPlayer.Name .. " (" .. LocalPlayer.UserId .. ")")
newLine("Account Age: " .. years .. "y " .. months .. "m " .. ageDays .. "d")
newLine("Theme: SSA (1/" .. #THEMES .. ")")
newLine("SSA Sandbox: ACTIVE | SmartRequire: READY")
newLine("Save Dir: " .. SAVE_DIR .. "/")
newLine("Q/E=Rotate | Z/X=Tilt | R=Reset | Click=Place")
newLine("--------------------------------------------------")

print("================================================")
print("  ROB'S MODEL INSERTER v6.0 ULTIMATE + SSA ENGINE  ")
print("  * Batch Insert * Click-to-Place                    ")
print("  * Full Rotation: Q/E (Y) | Z/X (Tilt) | R         ")
print("  * SSA SmartRequire Sandbox Execution              ")
print("  * Module Caching & Circular Dep Prevention        ")
print("  * 9 Built-in Themes (THM to cycle)                ")
print("  * Save/Load Scripts (SAV/LOD)                     ")
print("  * Tool GUI Registry (auto show/hide)              ")
print("  * Advanced Decompiler Integration                 ")
print("  * Automatic Sky Texture Application               ")
print("  * Live Model Preview in Place Mode                ")
print("================================================")

Features & Information

Credits to rob and little bit of help from ai to make this a masterpiece and this is not FE btw Update: New theme toggle + save and load feature + new script decompiler
#Universal#Model inserter

Community Comments (13)

Smg44463Smg44463Dec 23, 2025

@ScriptRater yea np

Replying to @Smg44463
FEscript_showcaserFEscript_showcaserJan 11, 2026

@Smg44463 I think he's being sarcastic.

Replying to @FEscript_showcaser
Smg44463Smg44463Jan 11, 2026

@FEscript_showcaser no?

Yaroslaw_228Yaroslaw_228Dec 25, 2025

:283: Expected identifier when parsing expression, got ':'

feat573feat573Feb 7, 2026

Nice, doesnt work.

Replying to @feat573
Smg44463Smg44463Feb 8, 2026

@feat573 well im not updating it I should put discontinued on it

PropperitooPropperitooApr 19, 2026

creator, can you please fix a lil bit? i just give myself guns, but they dont shoot.

Replying to @Propperitoo
Smg44463Smg44463Apr 19, 2026

@Propperitoo im planning on A remake

Replying to @Smg44463
PropperitooPropperitooApr 20, 2026

@Smg44463 Will guns damage people?

Replying to @Propperitoo
Smg44463Smg44463Apr 20, 2026

@Propperitoo well maybe but im planning on a remake soon just wait until I release it but release date is unknown for remake but yea I'll try to make scripts work

Replying to @Propperitoo
Smg44463Smg44463Apr 20, 2026

@Propperitoo alr ima release the script so go ahead and try it

Replying to @Smg44463
PropperitooPropperitooApr 20, 2026

@Smg44463 when will you realse it?

Replying to @Propperitoo
Smg44463Smg44463Apr 20, 2026

@Propperitoo its already released

More Scripts for Universal Scripts