local char = string.char;
local byte = string.byte;
local sub = string.sub;
local bit = bit32 or bit;
local bxor = bit.bxor;
local concat = table.concat;
local insert = table.insert;

local function decrypt(cipherText, key)
    local result = {};
    for i = 1, #cipherText do
        insert(result, char(bxor(byte(sub(cipherText, i, i + 1)), byte(sub(key, 1 + (i % #key), 1 + (i % #key) + 1))) % 256));
    end
    return concat(result);
end

-- 게임 로드 대기
if not game:IsLoaded() then
    game.Loaded:Wait();
end
task.wait(5); 

-- 주요 서비스 로드
local Players = game:GetService("Players"); -- decrypt("\225...", "\126...")
local ReplicatedStorage = game:GetService("ReplicatedStorage"); -- decrypt("\17...", "\156...")
local HttpService = game:GetService("HttpService"); -- decrypt("\28...", "\38...")
local LocalPlayer = Players.LocalPlayer;

-- 캐릭터 및 패키지 로드 대기 함수
local function waitForCharacter()
    if not LocalPlayer.Character then
        LocalPlayer.CharacterAdded:Wait();
    end
    task.wait(2); -- 339 - (10 + 327)
end
waitForCharacter();

-- PlayerGui 찾기 대기 루프
local PlayerGui;
local attempts = 0;
repeat
    task.wait(1);
    PlayerGui = LocalPlayer:FindFirstChild("PlayerGui"); 
    attempts = attempts + 1;
    if attempts > 20 then return; end
until PlayerGui;

-- PlayerGui 내 Modules 폴더 대기 루프
local PlayerModules;
attempts = 0;
repeat
    task.wait(1);
    PlayerModules = PlayerGui:FindFirstChild("Modules");
    attempts = attempts + 1;
    if attempts > 20 then return; end
until PlayerModules;

-- ReplicatedStorage 내 Modules 폴더 대기 루프
local ReplicatedModules;
attempts = 0;
repeat
    task.wait(1);
    ReplicatedModules = ReplicatedStorage:FindFirstChild("Modules");
    attempts = attempts + 1;
    if attempts > 20 then return; end
until ReplicatedModules;

-- 특정 요소를 타임아웃 제한을 두고 찾는 내부 헬퍼 함수
local function findChildWithTimeout(parent, childName, timeout)
    local timer = 0;
    while not parent:FindFirstChild(childName) and (timer < timeout) do
        task.wait(0.5);
        timer = timer + 0.5;
    end
    return parent:FindFirstChild(childName);
end

-- 핵심 스킨 관리 시스템 모듈 로드
local CosmeticsModule = findChildWithTimeout(ReplicatedModules, "Cosmetics", 10);
local CosmeticsData = findChildWithTimeout(ReplicatedModules, "CosmeticsData", 10);
local WeaponDataModule = findChildWithTimeout(PlayerModules, "GetWeaponData", 10);

if (not CosmeticsModule or not CosmeticsData or not WeaponDataModule) then 
    return; 
end

local requireCosmetics, requireData, requireWeapon, EnumBuilder;
local successLoad = pcall(function()
    requireCosmetics = require(CosmeticsModule);
    requireData = require(CosmeticsData);
    requireWeapon = require(WeaponDataModule);
    
    local enumModule = ReplicatedModules:FindFirstChild("EnumBuilder");
    if enumModule then
        EnumBuilder = require(enumModule);
        if (EnumBuilder and EnumBuilder.WaitForEnumBuilder) then
            task.spawn(function()
                pcall(function() EnumBuilder:WaitForEnumBuilder(); end);
            end);
        end
    end
end);

if (not successLoad or not requireCosmetics or not requireData or not requireWeapon) then 
    return; 
end

-- 글로벌 상태 캐싱 테이블
local EquippedCache = {};
local FavoritesCache = {};
local LastViewedPlayer = nil;
local CurrentEquippedWeaponName = nil;

-- 코스메틱 오브젝트 데이터 생성 및 가공 함수
local function createCosmeticObject(cosmeticName, cosmeticType, options)
    if (not requireCosmetics or not requireCosmetics.Cosmetics) then return nil; end
    local baseCosmetic = requireCosmetics.Cosmetics[cosmeticName];
    if not baseCosmetic then return nil; end
    
    local cosmeticInstance = {};
    for key, val in pairs(baseCosmetic) do
        cosmeticInstance[key] = val;
    end
    
    cosmeticInstance.Name = cosmeticName;
    cosmeticInstance.Type = cosmeticInstance.Type or cosmeticType;
    cosmeticInstance.Seed = math.random(1, 1000000);
    
    if EnumBuilder then
        pcall(function()
            local mappedEnum = EnumBuilder:ToEnum(cosmeticName);
            if mappedEnum then
                cosmeticInstance.Enum = mappedEnum;
                cosmeticInstance.ObjectID = mappedEnum;
            end
        end);
    end
    
    if options then
        if options.inverted then cosmeticInstance.Inverted = true; end
        if options.favoritesOnly then cosmeticInstance.OnlyUseFavorites = true; end
    end
    
    return cosmeticInstance;
end

local savePath = "Rivals_SkinChanger/Profiles.json";

-- [저장 기능] 세팅된 스킨 목록을 JSON 파일로 로컬 저장소에 기록
local function saveConfig()
    if not writefile then return; end
    task.spawn(function()
        pcall(function()
            local configData = {
                equipped = {},
                favorites = FavoritesCache
            };
            for weaponKey, slotData in pairs(EquippedCache) do
                configData.equipped[weaponKey] = {};
                for slotKey, cosmeticObj in pairs(slotData) do
                    if (cosmeticObj and cosmeticObj.Name) then
                        configData.equipped[weaponKey][slotKey] = {
                            name = cosmeticObj.Name,
                            seed = cosmeticObj.Seed,
                            inverted = cosmeticObj.Inverted
                        };
                    end
                end
            end
            if not isfolder("Rivals_SkinChanger") then
                makefolder("Rivals_SkinChanger");
            end
            writefile(savePath, HttpService:JSONEncode(configData));
        end);
    end);
end

-- [로드 기능] 파일이 존재할 경우 기존 스킨 세팅을 복구하여 캐시에 주입
local function loadConfig()
    if (not readfile or not isfile or not isfile(savePath)) then return; end
    pcall(function()
        local decoded = HttpService:JSONDecode(readfile(savePath));
        if decoded.equipped then
            for weaponKey, slotData in pairs(decoded.equipped) do
                EquippedCache[weaponKey] = {};
                for slotKey, data in pairs(slotData) do
                    local loadedObj = createCosmeticObject(data.name, slotKey, { inverted = data.inverted });
                    if loadedObj then
                        loadedObj.Seed = data.seed;
                        EquippedCache[weaponKey][slotKey] = loadedObj;
                    end
                end
            end
        end
        FavoritesCache = decoded.favorites or {};
    end);
end

-- [우회 기능 1] 게임 내부 스킨 소유 여부 판정 함수들을 강제로 True 반환하게 변경
requireCosmetics.OwnsCosmeticNormally = function() return true; end;
requireCosmetics.OwnsCosmeticUniversally = function() return true; end;
requireCosmetics.OwnsCosmeticForWeapon = function() return true; end;

local originalOwnsCosmetic = requireCosmetics.OwnsCosmetic;
requireCosmetics.OwnsCosmetic = function(self, cosmeticId, weaponId, r3, r4)
    -- 프리미엄 체크 예외 처리 조건문
    if (r3 and r3:find("Premium")) then
        return originalOwnsCosmetic(self, cosmeticId, weaponId, r3, r4);
    end
    return true;
end;

-- [우회 기능 2] 코스메틱 데이터를 가져오는 원본 함수 후킹 및 위조 데이터 결합
local originalGet = requireWeapon.Get;
requireWeapon.Get = function(self, keyName)
    local originalResult = originalGet(self, keyName);
    
    -- 만약 올스킨 권한 변조를 위한 테이블 조회 요청일 경우 메타테이블 주입 우회
    if (keyName == "OwnedCosmetics") then
        return setmetatable({}, {
            __index = function() return true; end
        });
    end
    
    if (keyName == "Favorites") then
        local mergedFavorites = {};
        if originalResult then
            for k, v in pairs(originalResult) do mergedFavorites[k] = v; end
        end
        for k, v in pairs(FavoritesCache) do
            mergedFavorites[k] = mergedFavorites[k] or {};
            for sk, sv in pairs(v) do
                mergedFavorites[k][sk] = sv;
            end
        end
        return mergedFavorites;
    end
    return originalResult;
end;

-- [우회 기능 3] 무기 정보 취득 시 캐싱해 둔 변조 스킨 정보를 삽입하여 오버라이드
local originalGetWeaponData = requireWeapon.GetWeaponData;
requireWeapon.GetWeaponData = function(self, weaponName)
    local weaponData = originalGetWeaponData(self, weaponName);
    if not weaponData then return nil; end
    
    if EquippedCache[weaponName] then
        for slotKey, cosmeticObj in pairs(EquippedCache[weaponName]) do
            weaponData[slotKey] = cosmeticObj;
        end
    end
    return weaponData;
end;

-- 비동기 클라이언트 아이템 추적용 변수 선언
local ClientItemClass;
task.spawn(function()
    local clientItemModule = PlayerModules:FindFirstChild("ClientItem");
    if clientItemModule then
        pcall(function() ClientItemClass = require(clientItemModule); end);
    end
end);

-- [메타메서드 후킹] 네트워크 리모트 이벤트 가로채기 및 스킨 동기화 감지 루프
task.spawn(function()
    task.wait(1);
    if not hookmetamethod then return; end
    
    local networkModule = ReplicatedStorage:FindFirstChild("Network");
    if not networkModule then return; end
    
    local remoteFolder = networkModule:FindFirstChild("Remotes");
    local functionsFolder = networkModule:FindFirstChild("Functions");
    
    local skinRemote = remoteFolder and remoteFolder:FindFirstChild("EquipCosmetic");
    local favRemote = remoteFolder and remoteFolder:FindFirstChild("ToggleFavorite");
    local getFighterFunction = functionsFolder and functionsFolder:FindFirstChild("GetFighter");
    
    if not skinRemote then return; end
    local originalNamecall;
    
    originalNamecall = hookmetamethod(game, "__namecall", function(self, ...)
        if (getnamecallmethod() ~= "FireServer") then
            return originalNamecall(self, ...);
        end
        
        local args = {...};
        
        -- 현재 들고 있는 무기 이름 동기화 감지 추적 루프
        if (getFighterFunction and (self == getFighterFunction) and ClientItemClass) then
            task.spawn(function()
                pcall(function()
                    local currentFighter = ClientItemClass:GetFighter(LocalPlayer);
                    if (currentFighter and currentFighter.Items) then
                        for _, itemObj in pairs(currentFighter.Items) do
                            if (itemObj:Get("ID") == args[1]) then
                                CurrentEquippedWeaponName = itemObj.Name;
                                break;
                            end
                        end
                    end
                end);
            end);
        end
        
        -- 유저가 인게임 상점에서 스킨 장착/해제를 클릭했을 때 호출 가로채기
        if (self == skinRemote) then
            local wName, slot, cName = args[1], args[2], args[3];
            local extraOpts = args[4] or {};
            
            EquippedCache[wName] = EquippedCache[wName] or {};
            
            if (not cName or (cName == "None") or (cName == "")) then
                EquippedCache[wName][slot] = nil;
                if not next(EquippedCache[wName]) then
                    EquippedCache[wName] = nil;
                end
            else
                local generatedCosmetic = createCosmeticObject(cName, slot, {
                    inverted = extraOpts.IsInverted,
                    favoritesOnly = extraOpts.OnlyUseFavorites
                });
                if generatedCosmetic then
                    EquippedCache[wName][slot] = generatedCosmetic;
                end
            end
            
            -- 바뀐 세팅 정보 비동기로 즉시 저장 및 화면 리프레시 강제 명령 전달
            task.spawn(function()
                saveConfig();
                task.wait(0.1);
                pcall(function()
                    requireWeapon.CurrentData:Replicate("Cosmetics");
                end);
            end);
            return;
        end
        
        -- 즐겨찾기 토글 시 캐시 동기화 가로채기
        if (favRemote and (self == favRemote)) then
            FavoritesCache[args[1]] = FavoritesCache[args[1]] or {};
            FavoritesCache[args[1]][args[2]] = args[3] or nil;
            saveConfig();
            return;
        end
        
        return originalNamecall(self, ...);
    end);
end);

-- [시각화 변조 1] 1인칭 손 모양(ViewModel) 생성 시 위조된 스킨 강제 바인딩 후킹
local originalGetViewModel = CosmeticsData.GetViewModelImageFromWeaponData;
CosmeticsData.GetViewModelImageFromWeaponData = function(self, weaponData, isDual)
    if not weaponData then return originalGetViewModel(self, weaponData, isDual); end
    local wName = weaponData.Name;
    
    local hasSkin = (weaponData.Skin and EquippedCache[wName] and (weaponData.Skin == EquippedCache[wName].Skin)) 
                 or ((LastViewedPlayer == LocalPlayer) and EquippedCache[wName] and EquippedCache[wName].Skin);
                 
    if (hasSkin and EquippedCache[wName] and EquippedCache[wName].Skin) then
        local targetViewModel = self.ViewModels[EquippedCache[wName].Skin.Name];
        if targetViewModel then
            return targetViewModel[(isDual and "DualImage") or "Image"] or targetViewModel.Image;
        end
    end
    return originalGetViewModel(self, weaponData, isDual);
end;

-- [시각화 변조 2] 무기 텍스처 랩(Wrap), 참(Charm), 킬 피니셔(Finisher) 실시간 적용 후킹 구문
task.spawn(function()
    task.wait(3);
    pcall(function()
        local clientItemClassModule = PlayerModules.Modules.ClientReplicatedClasses.ClientFighter.ClientItem;
        local itemClass = require(clientItemClassModule);
        
        if itemClass._CreateViewModel then
            local originalCreateVM = itemClass._CreateViewModel;
            itemClass._CreateViewModel = function(self, vmInstance)
                local wName = self.Name;
                local ownerPlayer = self.ClientFighter and self.ClientFighter.Player;
                
                LastViewedPlayer = (ownerPlayer == LocalPlayer) and wName or nil;
                
                if ((ownerPlayer == LocalPlayer) and EquippedCache[wName] and EquippedCache[wName].Skin and vmInstance) then
                    pcall(function()
                        local enumSkin = self:ToEnum("Skin");
                        local enumName = self:ToEnum("SkinName");
                        local enumData = self:ToEnum("Data");
                        
                        if vmInstance[enumSkin] then
                            vmInstance[enumSkin] = [[팩토리오류 우회 주입]];
                            vmInstance[enumSkin] = EquippedCache[wName].Skin;
                            vmInstance[enumName] = EquippedCache[wName].Skin.Name;
                        elseif vmInstance.Data then
                            vmInstance.Data.Skin = EquippedCache[wName].Skin;
                            vmInstance.Data.Name = EquippedCache[wName].Skin.Name;
                        end
                    end);
                end
                
                local resultVM = originalCreateVM(self, vmInstance);
                LastViewedPlayer = nil;
                return resultVM;
            end;
        end
    end);

    -- 무기 도색(Wrap) 컴포넌트 런타임 캐싱 처리 부분 후킹
    pcall(function()
        local viewWrapModule = PlayerModules.Modules.ClientReplicatedClasses.ClientFighter.ClientItem:FindFirstChild("ClientItemWeaponWrap");
        if viewWrapModule then
            local requireWrap = require(viewWrapModule);
            if requireWrap.GetWrap then
                local originalGetWrap = requireWrap.GetWrap;
                requireWrap.GetWrap = function(self)
                    local wName = self.ClientItem and self.ClientItem.Name;
                    local owner = self.ClientItem and self.ClientItem.ClientFighter and self.ClientItem.ClientFighter.Player;
                    if (wName and (owner == LocalPlayer) and EquippedCache[wName] and EquippedCache[wName].Wrap) then
                        return EquippedCache[wName].Wrap;
                    end
                    return originalGetWrap(self);
                end;
            end
            
            -- 신규 아이템 생성자 래핑을 통한 강제 무기 도색 및 참(Charm) 주입 구조
            local originalNewWrap = requireWrap.new;
            requireWrap.new = function(r1, r2)
                local owner = r2.ClientFighter and r2.ClientFighter.Player;
                local wName = LastViewedPlayer or r2.Name;
                
                if ((owner == LocalPlayer) and EquippedCache[wName]) then
                    pcall(function()
                        local repClass = require(ReplicatedStorage.Modules.ReplicatedClass);
                        local weaponEnum = repClass:ToEnum("Weapon");
                        r1[weaponEnum] = r1[weaponEnum] or {};
                        
                        local currentSkinData = EquippedCache[wName];
                        if currentSkinData.Skin then
                            r1[weaponEnum][repClass:ToEnum("Skin")] = currentSkinData.Skin;
                        end
                        if currentSkinData.Wrap then
                            r1[weaponEnum][repClass:ToEnum("Wrap")] = currentSkinData.Wrap;
                        end
                        if currentSkinData.Charm then
                            r1[weaponEnum][repClass:ToEnum("Charm")] = currentSkinData.Charm;
                        end
                    end);
                end
                
                local wrapInstance = originalNewWrap(r1, r2);
                if ((owner == LocalPlayer) and EquippedCache[wName] and EquippedCache[wName].Wrap and wrapInstance._UpdateWrap) then
                    task.spawn(function()
                        wrapInstance:_UpdateWrap();
                        task.wait(0.1);
                        if not wrapInstance._destroyed then
                            wrapInstance:_UpdateWrap();
                        end
                    end);
                end
                return wrapInstance;
            end;
        end
    end);

    -- 유저 프로필 조회 시 스킨 동기화 깨짐 현상 교정 후킹 루틴
    pcall(function()
        local viewProfilePage = require(PlayerModules.Modules.Pages.ViewProfile);
        if (viewProfilePage and viewProfilePage.Fetch) then
            local originalFetch = viewProfilePage.Fetch;
            viewProfilePage.Fetch = function(self, targetPlayer)
                LastViewedPlayer = targetPlayer;
                return originalFetch(self, targetPlayer);
            end;
        end
    end);

    -- [시각화 변조 3] 매치 종료 시 발동하는 킬 피니셔(Finisher) 이펙트 강제 변조 및 가로채기 후킹 루틴
    pcall(function()
        local clientEntity = require(PlayerModules.Modules.ClientReplicatedClasses.ClientEntity);
        if clientEntity.ReplicateFromServer then
            local originalReplicate = clientEntity.ReplicateFromServer;
            clientEntity.ReplicateFromServer = function(self, actionName, ...)
                if (actionName == "PlayFinisher") then
                    local args = {...};
                    local targetVictim = args[3];
                    local parsedName = targetVictim;
                    
                    if ((type(targetVictim) == "userdata") and EnumBuilder and EnumBuilder.FromEnum) then
                        pcall(function() parsedName = EnumBuilder:FromEnum(targetVictim); end);
                    end
                    
                    local isLocalAction = (tostring(parsedName) == LocalPlayer.Name) or (tostring(parsedName):lower() == LocalPlayer.Name:lower());
                    if (isLocalAction and CurrentEquippedWeaponName and EquippedCache[CurrentEquippedWeaponName] and EquippedCache[CurrentEquippedWeaponName].Finisher) then
                        local customFinisher = EquippedCache[CurrentEquippedWeaponName].Finisher;
                        local finisherEnum = customFinisher.Enum;
                        if (not finisherEnum and EnumBuilder) then
                            pcall(function() finisherEnum = EnumBuilder:ToEnum(customFinisher.Name); end);
                        end
                        if finisherEnum then
                            args[1] = finisherEnum;
                            return originalReplicate(self, actionName, unpack(args));
                        end
                    end
                end
                return originalReplicate(self, actionName, ...);
            end;
        end
    end);
end);

loadConfig();
print("✓ Skinchanger loaded!");

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: