repeat task.wait() until game:IsLoaded() setfpscap(10) -- ★★ HẠ-RENDER (đòn bẩy CPU client LỚN NHẤT — bot headless ko cần vẽ; game SAB render đầy brainrot chuyển động + VFX). -- MẶC ĐỊNH TẮT (false) vì chưa verify live (MCP rớt lúc thêm). StreamingEnabled stream theo VỊ TRÍ character (KHÔNG theo -- render) ⇒ về lý thuyết tắt render KO ảnh hưởng replicate plot/pet. ĐỔI = true để bật, rồi THEO DÕI: scan còn đủ pet/plot ko? -- Nếu miss → tắt lại (hoặc chỉ giữ phần QualityLevel/shadows = an toàn tuyệt đối, bỏ Set3dRenderingEnabled = phần mạnh nhất nhưng rủi ro nhất). local LOW_RENDER = true -- ★ BẬT render-off (như đối thủ): tắt 3D = CPU vẽ ~0 → máy "thoát xác" chạy nhẹ. ⚠️ TEST crash vài phút; nếu crash → comment dòng Set3dRenderingEnabled bên dưới (giữ phần quality-low an toàn) hoặc đổi lại false. if LOW_RENDER then -- (a) AN TOÀN TUYỆT ĐỐI (chỉ giảm chi tiết hình, ko đụng DataModel): quality thấp nhất + tắt bóng/sương. pcall(function() settings().Rendering.QualityLevel = Enum.QualityLevel.Level01 end) pcall(function() game:GetService("UserSettings"):GetService("UserGameSettings").SavedQualityLevel = Enum.SavedQualitySetting.QualityLevel1 end) pcall(function() local L = game:GetService("Lighting") L.GlobalShadows = false L.FogEnd = 9e9 L.Brightness = 0 L.ExposureCompensation = -math.huge end) -- (b) MẠNH NHẤT (tắt hẳn 3D render = gần như 0 CPU vẽ) — RỦI RO NHẤT, tách riêng để dễ bỏ nếu nghi ảnh hưởng: -- pcall(function() game:GetService("RunService"):Set3dRenderingEnabled(false) end) end -- ★ cloneref TẤT CẢ service (né detect so-sánh-reference). LocalPlayer = Players.LocalPlayer (KHÔNG -- cloneref chính LocalPlayer → giữ instance thật; mọi so sánh self trong script dùng .UserId nên an toàn). local cloneref = (typeof(cloneref) == "function" and cloneref) or (typeof(clonereference) == "function" and clonereference) or function(o) return o end local Players = cloneref(game:GetService("Players")) local Workspace = cloneref(game:GetService("Workspace")) local ReplicatedStorage = cloneref(game:GetService("ReplicatedStorage")) local HttpService = cloneref(game:GetService("HttpService")) local TeleportService = cloneref(game:GetService("TeleportService")) local LocalPlayer = Players.LocalPlayer -- ── ANTI-TAMPER (lõi): chụp ref builtin NGAY lúc load. Nếu sau đó global bị THAY (hook runtime -- để moi key) → coreTampered()=true. FP thấp: chỉ trip khi global đổi SAU load (không trip với -- executor pre-hook lúc load, vì lúc đó ta đã chụp đúng bản pre-hook). Wiring + mode ở dưới CONFIG. local _rawCore = { c = string.char, b = string.byte, t = table.concat, f = math.floor, s = string.sub } local function coreTampered() return not ( rawequal(string.char, _rawCore.c) and rawequal(string.byte, _rawCore.b) and rawequal(table.concat, _rawCore.t) and rawequal(math.floor, _rawCore.f) and rawequal(string.sub, _rawCore.s) ) end local AntiTamperCheck -- forward decl: trả true → crypto POISON key (gán sau CONFIG, đọc CONFIG.AntiTamper) -- ============================================================================ -- WNLITE SIÊU-LITE — chỉ ENCRYPT jobId ("wnotifier.com-"): PAID (cdev) + FREE (free-log Discord). XOR THUẦN: c = bxor(byte, K[pos]). -- Bỏ pos_term + bỏ decrypt/validUUID (scanner ko giải). SECRET giấu (deob SENC). SENC_PAID GIỐNG HỆT ClientSab. -- ★ ĐỔI KEY = đổi 2 mảng SENC (GIỐNG HỆT ClientSab). ⚠️ Đổi cipher (bỏ pos_term) → deploy CÙNG ClientSab. -- ============================================================================ local WNLite = (function() local sbyte, bxor = string.byte, bit32.bxor local SENC_PAID = { 0x7a, 0xc4, 0x1f, 0x93, 0x58, 0xe2, 0x0b, 0xbd, 0x46, 0xf1, 0x2d, 0x89, 0xd6, 0x30, 0xa7, 0x6e, 0x14, 0xcb, 0x82, 0x3f, 0xe9, 0x55, 0x9c, 0x08, 0xb3, 0x67, 0xda, 0x41, 0xfe, 0x22, 0x8d, 0x70, 0xa1, 0x5b, 0xc6, 0x19, 0x94, 0x2f, 0xe0, 0x4c, 0xb8, 0x03, 0x76, 0xdf, 0x38, 0x9a, 0x61, 0xed, } local SENC_FREE = { 0x2e, 0x91, 0x5c, 0xf8, 0x03, 0xa6, 0x4b, 0xdd, 0x70, 0x17, 0xba, 0x66, 0xc1, 0x38, 0x8f, 0x24, 0xe5, 0x59, 0xb2, 0x0d, 0x97, 0x42, 0xfb, 0x2a, 0x86, 0xd3, 0x60, 0x0e, 0xa9, 0x74, 0xcf, 0x33, 0x9e, 0x48, 0xe1, 0x7b, 0x15, 0xbc, 0x57, 0xf0, 0x29, 0x84, 0xdb, 0x36, 0x6d, 0xc8, 0x11, 0xa2, } local function deob(senc) local s = {} for i = 1, #senc do s[i] = bxor(senc[i], ((i * 37 + 0xA3) % 256)) end return s, #senc end local KP, NP = deob(SENC_PAID) local KF, NF = deob(SENC_FREE) local function mkM(K) local m = {} for i = 1, #K do m[i] = (K[i] * 29 + 0x5B + i * 13) % 256 end return m end local MP, MF = mkM(KP), mkM(KF) local function encrypt(jobId, free) jobId = tostring(jobId) local K, n, M = (free and KF or KP), (free and NF or NP), (free and MF or MP) local poison = (AntiTamperCheck and AntiTamperCheck()) and true or false local b = {} for i = 1, #jobId do local kk = K[((i - 1) % n) + 1] if poison then kk = bxor((kk + 0x6d + i * 11) % 256, 0x5a) end b[i] = bxor((sbyte(jobId, i) + kk) % 256, M[((i - 1) % n) + 1]) end for i = 2, #b do b[i] = bxor(b[i], b[i - 1]) end local hex = {} for i = 1, #b do hex[i] = ("%02x"):format(b[i]) end return "wnotifier.com-" .. table.concat(hex) end return { encrypt = encrypt } end)() local _encJobCache -- ★★ RAW_JOBID: true = gửi jobId THÔ (bỏ encrypt WNLite) → nhanh + consumer ĐỌC RAW (ko decode). false = encrypt như cũ. -- ⚠️ Đặt ở ĐẦY (ko trong CONFIG) vì encJob định nghĩa TRƯỚC CONFIG. ⚠️ CONSUMER (ClientSab/local relay) PHẢI khớp: đọc raw jobId. local RAW_JOBID = true -- ★ false = MÃ HOÁ jobId WNLite ("wnotifier.com-") trước khi gửi cdev → ClientSab giải mã. true = gửi thô (consumer đọc raw). -- mã hoá jobId BẤT KỲ (free=true → key FREE). Lỗi → "wnotifier.com-?" (KHÔNG lộ raw). local function encJobOf(j, free) local ok, v = pcall(function() return WNLite.encrypt(tostring(j), free) end) return (ok and v) or "wnotifier.com-?" end -- mã hoá jobId server hiện tại (cache). encJob() = key PAID (realtime cdev). (encJobFree ĐÃ XOÁ cùng free-log.) -- ★ RAW_JOBID=true → trả game.JobId THÔ (ko encrypt, ko cache — tostring rẻ; jobId cố định/server). local function encJob() if RAW_JOBID then return tostring(game.JobId) end if not _encJobCache then _encJobCache = encJobOf(game.JobId, false) end return _encJobCache end -- ============================================================================ -- CONFIG ── chỉnh ở đây ── -- ============================================================================ local CONFIG = { -- ★★ CÔNG TẮC TỔNG WEBHOOK DISCORD — false = TẮT SẠCH mọi webhook (tier + monitor + free-log). true = bật lại. -- (KHÔNG ảnh hưởng WS POST cdev realtime — đó là core, ko phải webhook.Brainrot Notify GameData/Hub cũng riêng.) WebhooksEnabled = true, -- ★★ DYNAMIC FPS BOOST (tiết kiệm CPU fleet + phản ứng nhanh lúc có pet — như đối thủ "boost lúc thấy pet rồi về"). -- Baseline thấp = nhẹ khi server vắng; có HOẠT ĐỘNG pet (plot mới claim / AnimalList đổi) → bùng fps cao N giây rồi tự về. -- ⚠️ FpsBase PHẢI khớp setfpscap(...) ở ĐẦU FILE (dòng 2) — đổi baseline thì đổi CẢ 2. FpsBoost ≤ FpsBase = TẮT boost. FpsBase = 10, -- baseline (server vắng) — khớp setfpscap dòng 2 FpsBoost = 60, -- bùng khi có pet → event/POST/hop xử lý nhanh (60 = ~16ms/frame thay vì 100ms ở fps 10) FpsBoostSec = 2, -- giữ boost N giây sau hoạt động gần nhất (gia hạn nếu pet tiếp tục đổ về) BoostOnPlayerJoin = true, -- ★ boost fps NGAY lúc player MỚI join; AnimalList OnChanged cũng tự boost khi channel event khả dụng. BoostOnJoinSec = 2, -- ★ giữ boost N giây sau mỗi player join (đủ load → claim plot → đặt pet). "đến khi scan xong plot" xấp xỉ bằng cửa sổ này; pet đổ về sẽ tự gia hạn qua OnChanged -- ★★★ RAW FAST MODE — strip xử lý cho POST THÔ + NHANH NHẤT (cạnh tranh tốc độ). Bật lại = đổi từng cờ. -- ⚠️ Đánh đổi: nhiều POST hơn (no dedup/no tier) + CONSUMER phải đọc jobId RAW (RawJobId=true). cdev WS vẫn là core. -- (Dedup ĐÃ XOÁ — globalSeen bỏ; mọi find đều POST) TierFilter = true, -- false = TẮT lọc tier → POST MỌI con (ko cần nằm TIER_NAMES, ko min $/s). Con ko-tier → POST nhưng KHÔNG hop/kick (chỉ OG/PEAK/HIGH mới hop). -- (RawJobId = gửi jobId THÔ: cờ `RAW_JOBID` ở ĐẦU FILE — vì encJob định nghĩa TRƯỚC CONFIG nên ko đọc được CONFIG.) -- ── Webhook theo TIER ($/s). OG route theo RARITY (con rarity OG). ── WebhookLow = "https://discord.com/api/webhooks/1501055459984015462/Jn7P668MmPsXwpC8tBq9cLWsavcVXzp_ccP6LnHlcwgaHrzPuvRELIHKw03hPpUbVkU9", -- LOW WebhookMid = "https://discord.com/api/webhooks/1499101196328374432/bOoBzfWUf1Zzj_9EMx2BnCINZIWbvMNMYlIskiaUwFVvRUU1M7jQqDSweKrY6WU1YWvz", -- MID WebhookHigh = "https://discord.com/api/webhooks/1501379473415733410/gqLcwZ1Ab6U6sk_OoG5i-L1lL5jnckYodYDhLXjfvQt8U6OR7boC-xh5OQrTEKnVpA7T", -- ★★ HIGH: DÁN WEBHOOK URL VÀO ĐÂY (đang trống → con HIGH sẽ KHÔNG gửi) WebhookPeak = "https://discord.com/api/webhooks/1432276698715525130/x_-TcSRZcz8xmby0fqYq4cYV6F_tVSsRoTTm41kYn8r9JcBeMmG9yoyKUoQteocjvqDg", -- PEAK WebhookOG = "https://discord.com/api/webhooks/1380320735322439824/ZS1B67uZaqKuFVVTBlPvVgQllTA_O3hCeH9JTuyF2B-83b5dXw575LwFI3Jw0bJenGW3", -- OG PatchFailureWebhook = "https://discord.com/api/webhooks/1501249978302205952/QO7AbeFJJCy0g0HtvkFraDXN2ClyI7gmsiZB10edpXw9Bja4mM_6NVdOjWzmM9me-zsp", -- tùy chọn: alert một lần khi patch quá 3 phút vẫn không thành công -- (MonitorWebhook ĐÃ XOÁ — sendMonitorWebhook dead, call site đã comment) -- (GameData ĐÃ XOÁ — postGameData + tierToCategory bỏ; token d491ec 401, kênh /logs ko dùng) -- ★ MIN GỬI THEO TIER ($/s): low=200M, mid=50M, high/peak/og = 0 (gửi luôn). Con phải nằm trong TIER_NAMES. TierMinSend = { LOW = 200000000, MID = 50000000, HIGH = 0, PEAK = 0, OG = 0 }, OGMoney = 1000000000, -- ★ OG KHÔNG tính $/s (fast-path snipe) → gửi ĐẠI giá trị này (bỏ genOf/genFromData). PHẢI ≥ money-filter OG bên consumer để ko bị lọc. -- ★ NÂNG TIER theo $/s: con ≥ HighFloorGen mà đang free(ngoài TIER_NAMES)/LOW/MID → ÉP lên HIGH (hàng to luôn báo high + hop). 0 = tắt. HighFloorGen = 1000000000, -- 1B/s -- (CONFIG.FreeLog ĐÃ XOÁ — bỏ post free theo yêu cầu) -- ★ MACHINE DETECT — DATA-DRIVEN (method scanner remake): con đang ở MÁY active = KHÔNG cướp được, đọc qua -- item.Machine.Active từ Synchronizer (bỏ heuristic overhead/Transparency cũ — nhẹ CPU). machineActive() = boolean. FuseDetect = { Skip = true, -- compatibility-only: patched scanner luôn bỏ con đang ở máy active. }, -- ★ Chống gửi trùng: MỖI OWNER chỉ gửi 1 LẦN / mỗi lần exec (cache local, reset khi inject lại). Xem sentOwners. WebhookSpacing = 0, -- POST-speed: ko giãn cách (429 đã có backoff riêng trong sendWebhook) WebhookMaxWaitSec = 20, -- ★ retry webhook theo DEADLINE: kiên nhẫn xuyên cửa sổ 429 tối đa N giây (mọi kênh đối xử như nhau → ko còn "monitor có, tier ko") -- (SkipMachineWebhook ĐÃ XOÁ — con đang máy bị bỏ ngay ở makePet/petFromSyncItem nên ko tới webhook) SendQueueMax = 80, -- ★ cap hàng đợi gửi (429 chặn lâu → queue phình); đầy → bỏ con CŨ nhất WebhookCacheTTL = 1200, -- ★ DEDUP BỀN qua hop: cùng 1 con (owner|tên|mut|$/s) đã gửi thì N giây sau mới gửi lại (chống spam khi hop re-inject) DetectConveyor = "always", -- "always" | "event" (patched Events/Bee) | "off". ConveyorDelay = 0.1, -- compatibility-only; logical intake dispatches after validated trait lookup. BaseScanDelay = 0.05, -- legacy workspace scanner field; patched base detector does not use it. UseSynchronizer = true, -- true = use the fail-closed patched Synchronizer:Get base detector. SyncPollSec = 2, -- legacy compatibility field; patched detector has no permanent poll. SyncModuleWaitSec = 8, -- bounded readiness and patched-Get batch budget. JoinSettle = 0.1, -- settle after a live Plots root before patched base intake starts. FastPath = false, -- (DEAD/legacy) — thay bằng InstantOG + SettleQuiet bên dưới. Giữ field để ko vỡ config cũ. -- ★★ TỐI ƯU POST (mọi tier, KHÔNG miss best) — xem hookPlot: InstantOG = true, -- ★ OG = tier ĐỈNH tuyệt đối → gửi NGAY khi thấy (KHÔNG chờ debounce). AN TOÀN 100%: ko gì vượt OG; 2 OG khác $ = CÙNG jobId/server → auto-join y hệt. (Chỉ OG; PEAK/HIGH instant sẽ unsafe vì OG có thể stream cùng burst.) SettleQuiet = 0.05, -- ★ SIẾT (user): 0.15→0.05 = quét ~1 frame sau add cuối thay vì 2. (Chỉ path workspace; Synchronizer ko dùng.) Rủi ro: burst nhiều-frame có thể bị cắt → instant-OG vẫn lo OG. SettleMax = 0.8, -- ★ trần chống chờ vô tận khi add dồn dập liên tục (burst quá dài → vẫn quét tại đây). DiscordInvite = "https://discord.gg/wblox", -- ★ link discord gắn dưới mỗi webhook (để trống "" nếu không muốn) OGRoleTag = "", -- ★ tag role này khi nổ OG (để trống "" nếu không muốn) PlaceId = 109983668079237, -- SAB main NewPlayersPlaceId = 96342491571673, -- ★ bản "[New Players]" (nhà giá trị thấp) → vào là hop sang Main ngay ScriptUrl = "", -- ★ (tùy chọn) URL raw của script này → tự chạy lại sau mỗi hop (queue_on_teleport). -- Để TRỐNG nếu anh đã có autoexec re-inject lúc join. -- ★ PRESENCE (hobeojob /api/presence) — POST heartbeat {account(username),placeId,jobId,players,ping,hops,uptime}. Presence = { Enabled = true, Interval = 60, -- post 1 lần NGAY khi vào sv, rồi mỗi N giây (180 = 3') }, -- ★ FRIEND REGISTRY (hobeojob /api/usernames) — CHỈ GET (KHÔNG post username). Lấy list username fleet 1 lần lúc join -- → nếu có clone fleet chung server thì hop (hasFriendClone). (Username do hệ khác/presence-backend populate.) FriendRegistry = { Enabled = true, -- ★ RETRY GET /api/usernames (list fleet ĐÔNG → host chậm/timeout → 1 GET fail = registry RỖNG = ko detect clone = kẹt 2-3 acc/sv). GetTries = 4, -- số lần thử GET mỗi lượt refresh (1 = ko retry) GetRetryGap = 3, -- giây nghỉ giữa các lần GET fail (host đang quá tải → đợi rồi thử lại) -- ★ CACHE ĐĨA (writefile/readfile) — CHIA SẺ giữa MỌI tab trên máy: GET 1 lần ghi file, còn-hạn thì ĐỌC FILE (KHÔNG GET). -- → cả fleet chỉ GET ~1 lần/CacheTTL (giảm tải host), tab vào sau dùng luôn cache tab trước. GET fail hết retry → fallback cache CŨ. CacheTTL = 300, -- 30' cache còn "tươi" → trong khoảng này chỉ đọc file, KHÔNG gọi mạng (0 = tắt cache, GET như cũ) -- ★ REFRESH ĐỊNH KỲ: GET lại list + check clone mỗi N giây → recover GET-fail lúc join + bắt clone vào SAU. RefreshInterval = 800, -- 5' refresh registry 1 lần (0 = chỉ GET lúc join, ko refresh định kỳ) -- ★ DEADLOCK-BREAKER: clone fleet ở CHUNG sv LIÊN TỤC đủ grace (lẽ ra nó tự hop mà ko hop → GET-fail/kẹt) → acc này hop phá kẹt. -- Tie-break theo RANK UserId (acc UserId NHỎ hop TRƯỚC): grace_thực = GraceSeconds + rank*StaggerSeconds. -- → đúng 1 acc rời mỗi đợt (acc kia thấy clone biến mất sẽ RESET, ở lại) → KHÔNG bao giờ cả 2 cùng hop / re-collide vô hạn. GraceSeconds = 180, -- 3' clone vẫn còn chung sv → hop phá kẹt StaggerSeconds = 120, -- mỗi bậc rank cộng N giây grace. PHẢI > thời gian hop TỐI ĐA khi CÓ server (~MaxRetries×(WaitPerTry+RetryBackoff) ≈ 90-100s) -- → acc rank-trước hop xong (rời sv) TRƯỚC khi acc rank-sau tới hạn ⇒ đúng 1 acc hop/đợt, ko double-hop. -- (Lúc NO-SERVER cả 2 acc đều kẹt ko hop được → ko double-hop HẠI; nên KHÔNG cần > KickAfterFailSeconds(700).) }, IncludeOwnBase = false, -- false = BỎ QUA nhà của chính bot (chỉ scan nhà NGƯỜI KHÁC để steal) ShowImage = false, -- ★ ẢNH TẮT (tối ưu tốc độ POST) — không fetch ảnh dưới mọi hình thức ImageSize = 500, -- size ảnh wiki (pithumbsize, px) ImageMeshFallback = false, -- wiki ko có page → false = ko ảnh; true = render mesh 3D (xám) thay thế Hop = { Enabled = true, -- false = chỉ ở lại scan, không hop -- ── ĐIỀU KIỆN HOP (solo kiểm tra theo timer; các điều kiện khác dùng monitor hiện có) ── HopOnHit = true, -- compatibility-only: OG/PEAK chỉ gọi _killLeave sau khi CDEV WebSocket gửi thành công. HopDelayAfterWS = 1, -- compatibility-only. KickAfterSendSec = 0.2, -- compatibility-only. ScanBeforeKick = true, -- compatibility-only. ScanSettleSec = 3, -- compatibility-only. ScanMaxStaySec = 8, -- compatibility-only. DeliverMaxWait = 0, -- compatibility-only. -- (FalseSuccessSec / NoJoinHopMinutes / KickAfterFailSeconds ĐÃ XOÁ theo yêu cầu — bỏ lưới chống-kẹt: hop/teleport fail thì cứ thử lại, KHÔNG tự kick) FullPlayers = 7, -- ★ server ≥ N người (định nghĩa "đầy") -- ★★ ĐIỀU KIỆN HOP còn lại: [1] chỉ còn bot + [2] friend-clone + [3] 8 người-5'. (HopSeven / HopRefresh2h / HopAllOthersPoor ĐÃ XOÁ theo yêu cầu.) HopWhenSolo = false, -- [1] true = hop khi server chỉ còn bot SoloCheckInterval = 300, -- [1] kiểm tra server size mỗi 5 phút HopFull8 = false, -- [3] server ĐẦY 8 người liên tục FullSeconds → hop FullSeconds = 400, -- [3] 8 người (kín slot) liên tục N giây (5') → hop: ko nạn nhân mới vào được nữa -- ★★ (true/false — mặc định FALSE, bật khi cần) HOP NGAY khi server > PoorMinPlayers người mà MỌI người khác đọc được tiền đều < PoorCashThreshold (nghèo, ko đáng steal). -- ⚠️ Cần ĐỌC ĐƯỢC tiền player (playerCash thử leaderstats → attribute → Synchronizer channel). Đọc ko ra player nào = coi KHÔNG nghèo (KHÔNG hop nhầm). -- Chỉ hop khi đọc được ≥ nửa số người khác VÀ tất cả đều nghèo. BẬT rồi TEST (Debug=true xem log) trước khi tin. HopPoorServer = false, PoorMinPlayers = 7, -- > N người mới xét (trên 6 = từ 7 trở lên) PoorCashThreshold = 10000, -- mọi người khác < N tiền = server nghèo → hop ngay -- ★★ HOP "SERVER CHẾT": server ≥ NoJoinMinPlayers người mà NoJoinMinutes phút KHÔNG ai JOIN mới (matchmaking đứng → ko nạn nhân mới) → hop. HopNoJoin = false, -- (true/false) bật điều kiện này NoJoinMinPlayers = 7, -- ★ server phải ≥ N người mới xét (trên 6 = từ 7, gồm cả bot) NoJoinMinutes = 30, -- ★ N phút liên tục KHÔNG có player mới join (PlayerAdded) → hop. Mỗi lần có người join thì reset đồng hồ. MonitorInterval = 5, -- chu kỳ kiểm tra điều kiện hop (giây) MaxRetries = 12, -- teleport 1 job lỗi → thử job khác (tối đa N lần) chứ KHÔNG bỏ qua WaitPerTry = 6, -- chờ mỗi lần teleport (đợi TeleportInitFailed nếu full) RetryBackoff = 1.5, -- nghỉ giữa các retry (GameFull trả về nhanh nên giảm) MatchmakingFallback = true, -- ★ retry hết vẫn full → Teleport(placeId) matchmaking (Roblox tự tìm sv còn chỗ) -- ── KHI KHÔNG CÓ SERVER ĐỂ HOP (hobeojob+API đều rỗng) ── -- ★ Hành vi: API ko có server → CHỜ NoServerWaitSec rồi GET LẠI, lặp tối đa NoServerMaxRetries lần (60s×10 = ~10 phút). -- Đủ NoServerMaxRetries lần vẫn rỗng → HOP RANDOM của Roblox (matchmaking). Random hop CŨNG fail → _killLeave() (đóng game). NoServerWaitSec = 2, -- ★ ko có server → chờ N giây (1 phút) rồi thử GET lại NoServerMaxRetries = 800, -- ★ thử lại N lần (mỗi NoServerWaitSec) → đủ N lần vẫn rỗng mới RANDOM HOP Roblox (10×60s ≈ 10 phút) -- ── Nguồn server ── UseRobloxApiFallback = true, -- ★ hobeojob trống → fallback API gốc Roblox (games.roblox.com) MaxPages = 6, -- fallback paginate tối đa N trang (100 sv/trang) tìm sv còn slot HopCurrentPlace = true, -- ★ true = hop TRONG place hiện tại (tránh bị SAB đá về "New Players"). false = ép sang CONFIG.PlaceId HopOnFriendClone = true, -- ★ JOIN thấy acc fleet (GET /api/usernames) chung server → hop (mình tới sau) PostBlacklist = true, -- ★ POST jobId server ĐÍCH vào blacklist NGAY TRƯỚC khi teleport vào (reserve, chống clone khác nhảy trùng). KHÔNG post khi đang ở sv. ReportDead = true, -- ★ teleport vào server mà result=GameEnded ("Could not find requested game instance") = CHẾT → POST /api/jobs/{placeId}/dead → gỡ NGAY khỏi pool (clone khác né). false = tắt. }, Debug = false, -- in log chi tiết VerboseScan = false, -- ★ LOG ĐẦY ĐỦ mỗi pet thấy được (không lọc) + quyết định gửi/skip ShowUI = false, -- ★ TẮT mặc định (giảm RAM/CPU mỗi tab fleet: bỏ ~7 GUI Instance + vòng build-chuỗi 2s). Đổi true nếu muốn xem panel. KeypressAntiAFK = true, -- ★ anti-afk keypress 0x87 hold 2s mỗi 5' (⚠️ inject input — có thể bị detect; false=tắt) -- ★ STUCK GUARD: N phút STATS.seen (pet quét được) KHÔNG đổi → script kẹt/server chết → rejoin (hoặc kick) StuckGuard = { Enabled = true, Minutes = 300, Action = "rejoin" }, -- Action = "rejoin" | "kick" -- ★ RAM REFRESH: VM treo nhiều giờ → RAM phình (tích luỹ cache + GC pressure) → full CPU → gửi chậm. Cứ N phút REJOIN reset VM (RAM về 0). 0 = tắt. RamKickMinutes = 120, -- ★ ANTI-TAMPER / CRASH — chống kẻ HOOK builtin lúc runtime để moi key crypto. -- (Chỉ có ý nghĩa nếu file scanner BỊ LEAK & chạy bởi người lạ — scanner chạy trên máy anh thì gần như ko trip.) AntiTamper = { Enabled = true, Mode = "poison", -- "off" | "warn" | "poison" | "crash" -- off = tắt hẳn. -- warn = chỉ warn console, KHÔNG làm gì (an toàn nhất để test). -- poison = phát hiện hook → KEY bị bẻ → token mã hoá thành RÁC (kẻ tấn công ko ra jobId), -- scanner VẪN chạy, KHÔNG crash, KHÔNG trip anti-cheat. ★ KHUYÊN DÙNG. -- crash = kick mình + treo VM. ⚠️ false-positive = TỰ BRICK acc — chỉ bật khi chắc chắn. CheckInterval = 10, -- (mode warn/crash) quét hook định kỳ mỗi N giây }, } function safeTaskWait(time) time = time or 0 return task.wait(time) end -- ============================================================================ -- ANTI-TAMPER wiring (đọc CONFIG.AntiTamper) — gán AntiTamperCheck đã forward-decl ở trên. -- ============================================================================ -- ★ TEST-LEAVE helper: hiện messagebox (đánh dấu) → RỒI Kick + game:Shutdown() cho CHẮC rời server. -- messagebox BLOCKING → bấm OK mới tới kick/shutdown; kick rời trước, shutdown là fallback. Mỗi lệnh pcall riêng. local function _killLeave() pcall(function() messagebox(game.Players.LocalPlayer.Name, "Kill Me", 0x00000010) task.wait(1) end) pcall(function() LocalPlayer:Kick("\n[wblox] leave") end) pcall(function() task.wait(1) game:Shutdown() end) end local _atTripped = false local function _atCrash() pcall(function() _killLeave() end) safeTaskWait(3) -- ★ chờ 3s cho kick "ăn" rồi shutdown _killLeave() while true do end -- treo VM (chỉ chạy ở mode "crash") end function AntiTamperCheck() local at = CONFIG.AntiTamper if not (at and at.Enabled) or at.Mode == "off" then return false end if not coreTampered() then return false end if at.Mode == "warn" then if not _atTripped then _atTripped = true warn("[wblox] core builtin bị hook (tamper) — chỉ cảnh báo") end return false end if at.Mode == "crash" then _atCrash() return true end return true -- "poison": crypto sẽ bẻ key end -- (mode warn/crash) quét hook định kỳ kể cả khi không gọi crypto. poison thì check ngay trong crypto. -- task.spawn(function() -- local at0 = CONFIG.AntiTamper -- if not (at0 and at0.Enabled and (at0.Mode == "warn" or at0.Mode == "crash")) then return end -- ★ poison/tắt → KHÔNG cần loop định kỳ (poison check INLINE trong crypto) → thoát coroutine, ko tick mãi 10s vô ích -- while true do -- local at = CONFIG.AntiTamper -- if at and at.Enabled and (at.Mode == "warn" or at.Mode == "crash") then pcall(AntiTamperCheck) end -- safeTaskWait((CONFIG.AntiTamper and CONFIG.AntiTamper.CheckInterval) or 10) -- end -- end) -- ============================================================================ -- ★ NETWORK I/O TIMING — TẤT CẢ request ra ngoài + chu kỳ (ghi cụ thể): -- ── GET (đọc) ── -- • Server list (hobeojob /api/jobs + Roblox API): CHỈ KHI HOP, không định kỳ (fetchServers) -- • Ảnh brainrot (wiki Fandom) : mỗi lần gửi webhook có ảnh (imageFor, per-find) -- ── POST (gửi) ── -- • Presence heartbeat (hobeojob /api/presence): 1 LẦN khi vào sv, rồi mỗi CONFIG.Presence.Interval (post acc/jobId/username/players/...) -- • Webhook Discord (find/hit) : THEO SỰ KIỆN (mỗi con đạt tier), giãn WebhookSpacing = 0.25s, mỗi owner 1 lần/exec -- ⇒ Định kỳ ra hobeojob: CHỈ presence POST. ĐÃ BỎ username-registry + blacklist (presence post cả jobId lẫn username → backend tự điều phối clone/né sv). -- ============================================================================ -- ── STATS (uptime + hops giữ qua mỗi lần hop nhờ getgenv; seen/sent reset theo server) ── getgenv().__SAB_Start = getgenv().__SAB_Start or os.time() getgenv().__SAB_Hops = getgenv().__SAB_Hops or 0 local STATS = { seen = 0, sent = 0, whOk = 0, wh429 = 0, whFail = 0 } -- whOk/wh429/whFail: theo dõi webhook rate-limit getgenv().__SAB_PatchStatus = "..." -- ★ uptime MONOTONIC (giây) — fix "uptime âm": os.time() mỗi server Roblox lệch nhau vài giây (clock skew), -- __SAB_Start lưu xuyên hop nên hop sang server clock SỚM hơn → os.time() < __SAB_Start → âm. -- Khi phát hiện clock tụt (now < lastTime), DỜI __SAB_Start xuống đúng delta → uptime liền mạch, KHÔNG âm/tụt. local function sabUptime() local g = getgenv() local now = os.time() g.__SAB_Start = g.__SAB_Start or now if g.__SAB_LastTime and now < g.__SAB_LastTime then g.__SAB_Start = g.__SAB_Start - (g.__SAB_LastTime - now) -- bù clock skew → giữ uptime liền mạch end g.__SAB_LastTime = now local up = now - g.__SAB_Start if up < 0 then up = 0 end return up end -- ★ UP TIME GIỮA MÀN HÌNH (ScreenGui riêng, update mỗi 1s) task.spawn(function() local host = (gethui and gethui()) or cloneref(game:GetService("CoreGui")) pcall(function() local o = host:FindFirstChild("SAB_Uptime") if o then o:Destroy() end end) local gui = Instance.new("ScreenGui") gui.Name = "SAB_Uptime" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true gui.DisplayOrder = 999 local lbl = Instance.new("TextLabel") lbl.AnchorPoint = Vector2.new(0.5, 0.5) lbl.Position = UDim2.fromScale(0.5, 0.5) lbl.Size = UDim2.fromOffset(320, 66) lbl.BackgroundColor3 = Color3.fromRGB(0, 0, 0) lbl.BackgroundTransparency = 0.35 lbl.TextColor3 = Color3.fromRGB(255, 255, 255) lbl.Font = Enum.Font.GothamBold lbl.TextSize = 20 lbl.RichText = true lbl.Text = "Up time 00h 00m 00s\nPatch ..." Instance.new("UICorner", lbl).CornerRadius = UDim.new(0, 10) lbl.Parent = gui pcall(function() gui.Parent = host end) while true do local up = sabUptime() local patchStatus = getgenv().__SAB_PatchStatus or "..." local patchColor = patchStatus:sub(1, 2) == "OK" and "00e68c" or "ffb02a" lbl.Text = ('Up time %02dh %02dm %02ds\nPatch %s'):format( math.floor(up / 3600), math.floor((up % 3600) / 60), up % 60, patchColor, patchStatus ) task.wait(1) end end) -- ============================================================================ -- TIER THEO TÊN BRAINROT (khách mua theo CON CỤ THỂ, KHÔNG theo $/s) -- • Con nằm trong set nào → gửi tới webhook tier đó. -- • Con KHÔNG nằm set nào → BỎ QUA (không phải hàng khách cần). -- • $/s phải ≥ min theo tier (TierMinSend): low 200M, mid 50M, high/peak/og 0. -- ★ Thêm/bớt con: sửa thẳng trong list dưới đây. -- ============================================================================ local TIER_NAMES = { OG = { -- no min (list thủ công — auto-sync theo rarity OG bên dưới sẽ bổ sung con game thêm sau) "Headless Horseman", "John Pork", "Meowl", "Skibidi Toilet", "Strawberry Elephant", "Spyder Elephant", "Arcadragon", "Griffin", "Signore Carapace", }, PEAK = { -- no min, hop ngay sau khi gá»­i "Antonio", "Bunny and Eggy", "Digi Narwhal", "Dragon Aquanini", "Dragon Cannelloni", "Dragon Gingerini", "Elefanto Frigo", "Fishino Clownino", "Ginger Gerat", "Hydra Bunny", "Hydra Dragon Cannelloni", "Jelly Moby", "Kalika Bros", "Ketupat Bros", "Kraken", "La Casa Boo", "La Supreme Combinasion", "Love Love Bear", "Pancake and Syrup", "Tirilikalika Tirilikalako", "Moby Bros", "Grabatron", "Bumbatron", }, HIGH = { -- no min "Nachorilla", "Sammyni Truckini", "Gorillo Subwoofero", "Polaroidini", "La Fuse Machine", "S'more Serat", "La Breakfast Combinasion", "Los Admins", "Venuspino", "Yetimatic", "Honey Honey Bear", "Queen Bee", "Noo my Resume", "Rico Dinero", "Rubrikiko", "Tenini Ballini", "Los Secret Combinasionas", "Pizza and Ranch", "Bearito Cabinito", "Foxini Lanternini", "Noodle Noodle Poodle", "Cangurato Gelato", "Rubiko and Kubiko", "Boppin Bunny", "Capitano Moby", "Cash or Card", "Celestial Pegasus", "Celularcini Viciosini", "Cerberus", "Cloverat Clapat", "Coco and Mango", "Cooki and Milki", "Duggy Bros", "Dug dug dug", "Festive 67", "Fortunu and Cashuru", "Fragola La La La", "Fragrama and Chocrama", "Guest 666", "Gym Bros", "Hopilikalika Hopilikalako", "Jolly Jolly Sahur", "Los Amigos", "Los Chillis", "Los Hackers", "Los Sekolahs", "Money Money Bros", "Popcuru and Fizzuru", "Reinito Sleighito", "Rosey and Teddy", "Sammyni Cakini", "Sand Sand Sand", "Spooky and Pumpky", "Steakini Fattini", "Tralaledon", "La Food Combinasion", "Noo my examine", "Globa Steppa", "Lazy Ducky", "Quackini Snackini", "Los Tangcitos", "Los Tictacs", }, MID = { -- min 50M "Scorpino Coasterino", "Examen Bros", "La Summer Grande", "Abyssaloco", "Avocadorilla", "Brutto Gialutto", "Burguro And Fryuro", "Caylusaurus", "Chillin Chili", "Chipso and Queso", "Eviledon", "Ganganzelli Trulala", "Garama and Madundung", "Gobblino Uniciclino", "Gold Gold Gold", "Ketupat Kepat", "Ketchuru and Musturu", "La Anniversary Grande", "La Easter Grande", "La Extinct Grande", "La Ginger Sekolah", "La Jolly Grande", "La Romantic Grande", "La Secret Combinasion", "La Spooky Grande", "Los Tacoritas", "La Taco Combinasion", "Las Sis", "Los Bros", "Los Cupids", "Los Fruits", "Los Hotspotsitos", "Los Jolly Combinasionas", "Los Planitos", "Los Puggies", "Los Primos", "Los Spaghettis", "Los Spooky Combinasionas", "Lovin Rose", "Money Money Puggy", "Money Money Reindeer", "Nacho Spyder", "Nuclearo Dinossauro", "Orcaledon", "Rhino Helicopterino", "Rosetti Tualetti", "Sammyni Fattini", "Tacorita Bicicleta", "Tang Tang Keletang", "Tictac Sahur", "Tob Tobi Tobi", "Tuff Toucan", "Ventoliero Pavonero", "W or L", "Pineaplino", "La Lucky Grande", "Pretzo Robo", "Unclito Samito", "Capitano Americano", }, LOW = { -- min 200M "Bacuru and Egguru", "Bananito", "Baskito", "Camera Ramena", "Capitano Gullini", "Chicleteira Cupideira", "Chicleteira Noelteira", "Chimnino", "Churrito Bunnito", "Cigno Fulgoro", "Craburger", "DJ Panda", "John Doe", "La Grande Combinasion", "Los 25", "Los 67", "Los Candies", "Los Combinasionas", "Los Mobilis", "Los Sweethearts", "Mariachi Corazoni", "Mieteteira Bicicleteira", "Noo my Gold", "Noo my Heart", "Octoball", "Snailo Clovero", "Spaghetti Tualetti", "Spinny Hammy", "Sushi Inu", "Swag Soda", "Swaggy Bros", "Tacorillo Crocodillo", "Chicleteira Surfeiteira", "Girafini Raftini", }, } -- name -> tier (build 1 lần) local NAME_TIER = {} for tier, names in pairs(TIER_NAMES) do for _, n in ipairs(names) do -- key = trim + lower → match KHÔNG phân biệt hoa/thường + space thừa (vd "And" vs "and") local key = n:gsub("^%s+", ""):gsub("%s+$", ""):lower() NAME_TIER[key] = tier end end local function tierOf(name) return name and NAME_TIER[name:lower()] or nil end local function _fb_doApplyLighting() local lighting = cloneref(game:GetService("Lighting")) lighting.GlobalShadows = false lighting.FogEnd = 9e9 lighting.ExposureCompensation = -math.huge for _, v in ipairs(lighting:GetChildren()) do if v:IsA("PostEffect") or v:IsA("BlurEffect") or v:IsA("SunRaysEffect") or v:IsA("BloomEffect") or v:IsA("ColorCorrectionEffect") or v:IsA("DepthOfFieldEffect") or v:IsA("Atmosphere") or v:IsA("Sky") or v:IsA("Clouds") then pcall(v.Destroy, v) end end end _fb_doApplyLighting() -- do -- local function killIdle() -- if typeof(getconnections) ~= "function" then return false end -- local ok = pcall(function() -- for _, c in ipairs(getconnections(LocalPlayer.Idled)) do -- pcall(function() c:Disable() end) -- Disable (giữ để re-enable nếu cần) -- end -- end) -- return ok -- end -- if killIdle() then -- task.spawn(function() while true do safeTaskWait(30); killIdle() end end) -- re-disable phòng game nối lại -- end -- end -- ⚠️ Anti-AFK KEYPRESS (theo yêu cầu): nhấn giữ phím 0x87 ~2 giây, mỗi 5 phút. -- CẢNH BÁO: INJECT INPUT GIẢ — có thể bị anti-cheat SAB detect/kick. Tắt = CONFIG.KeypressAntiAFK=false. -- if CONFIG and CONFIG.KeypressAntiAFK ~= false then -- task.spawn(function() -- local rng = Random.new() -- local function tapKey(vk, minHold, maxHold) -- if keypress and keyrelease then -- keypress(vk) -- safeTaskWait(rng:NextNumber(minHold, maxHold)) -- keyrelease(vk) -- end -- end -- while true do -- -- 20 giây -> 3 phút -- safeTaskWait(rng:NextInteger(20, 180)) -- pcall(function() -- local roll = rng:NextInteger(1, 100) -- local hum = LocalPlayer.Character -- and LocalPlayer.Character:FindFirstChildOfClass("Humanoid") -- if roll <= 30 then -- -- W -- tapKey(0x57, 0.3, 2) -- elseif roll <= 50 then -- -- A -- tapKey(0x41, 0.2, 1) -- elseif roll <= 70 then -- -- D -- tapKey(0x44, 0.2, 1) -- elseif roll <= 85 then -- -- Jump -- if hum then -- hum.Jump = true -- end -- elseif roll <= 95 then -- -- W + Jump -- tapKey(0x57, 0.5, 1.5) -- if hum then -- hum.Jump = true -- end -- else -- -- Click chuột -- if mouse1click then -- mouse1click() -- end -- end -- end) -- end -- end) -- end -- ============================================================================ -- ★ LOG/WARN giờ IN RA console executor (pcall chống print bị hook/nil). LOG gated bởi CONFIG.Debug (master switch), -- WARN luôn in. Tắt log thường: CONFIG.Debug=false. Bớt spam mỗi-pet: CONFIG.VerboseScan=false. (log WS connect riêng = CDEV.LogWs.) local function LOG(...) if CONFIG.Debug then pcall(print, "[SAB]", ...) end end local function WARN(...) pcall(warn, "[SAB]", ...) end -- ★ (tùy chọn) tự chạy lại script sau mỗi lần hop/teleport — chỉ khi điền CONFIG.ScriptUrl -- (nếu đã có autoexec re-inject lúc join thì để trống, block này bỏ qua). do local q = (syn and syn.queue_on_teleport) or (fluxus and fluxus.queue_on_teleport) or (getgenv and getgenv().queue_on_teleport) or queue_on_teleport if q and CONFIG.ScriptUrl and CONFIG.ScriptUrl ~= "" then pcall(q, ([[loadstring(game:HttpGet("%s"))()]]):format(CONFIG.ScriptUrl)) end end -- HTTP request (executor) — thử nhiều tên hàm local httpRequest = (syn and syn.request) or (http and http.request) or http_request or (fluxus and fluxus.request) or (getgenv and getgenv().request) or request local function httpGet(url) -- ★ httpRequest TRƯỚC (có User-Agent → Cloudflare/hobeojob ít chặn/stall hơn game:HttpGet, thường NHANH hơn nhiều). -- game:HttpGet (ko UA) dễ bị server làm chậm (~30s) → đây là 1 phần "55s mới send". Để game:HttpGet làm FALLBACK. -- Nhanh registry → clone-hop bắn sớm → thu hẹp cửa sổ fleet-dup; nhanh fetchServers/ảnh luôn. if httpRequest then local ok, r = pcall(httpRequest, { Url = url, Method = "GET" }) if ok and r and r.Body then local code = tonumber(r.StatusCode or r.Status) if not code or (code >= 200 and code < 300) then return r.Body end end end local ok2, res = pcall(function() return game:HttpGet(url) end) if ok2 and type(res) == "string" then return res end return nil end -- ★ gửi webhook CÓ XỬ LÝ 429 (rate-limit). 40 máy bắn chung 1 webhook → Discord 429. -- Retry theo DEADLINE (WebhookMaxWaitSec) chứ ko phải đếm lần: KIÊN NHẪN xuyên qua cửa sổ 429 tới khi gửi được -- hoặc hết budget. (Fix bug "monitor có, tier ko": tier gửi trước hết retry sớm → bỏ; monitor gửi sau lọt.) local function sendWebhook(url, payload) if CONFIG.WebhooksEnabled == false then return false end -- ★ CÔNG TẮC TỔNG: tắt sạch webhook Discord (tier/monitor/free-log) if not url or url == "" or not httpRequest then return false end local body = HttpService:JSONEncode(payload) local deadline = os.clock() + (CONFIG.WebhookMaxWaitSec or 20) while true do local ok, res = pcall(httpRequest, { Url = url, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = body, }) local code = ok and res and tonumber(res.StatusCode or res.Status) if ok and (not code or (code >= 200 and code < 300)) then STATS.whOk = (STATS.whOk or 0) + 1 return true end -- ★ FIX "send 2 phát": CHỈ retry khi 429 (Discord TỪ CHỐI rõ ràng, CHƯA đăng → gửi lại an toàn, ko dup). -- 5xx / timeout / đứt mạng = AMBIGUOUS — Discord CÓ THỂ đã đăng rồi mà client ko nhận được 2xx → retry sẽ -- tạo message LẦN 2. Nên các lỗi này KHÔNG retry (coi như xong; find vẫn còn ở cdev feed + các kênh khác). if code ~= 429 then STATS.whFail = (STATS.whFail or 0) + 1 return false end STATS.wh429 = (STATS.wh429 or 0) + 1 local wait = 0.8 if res then -- đọc retry_after (giây) từ body / header if res.Body then local okj, j = pcall(function() return HttpService:JSONDecode(res.Body) end) if okj and j and tonumber(j.retry_after) then wait = tonumber(j.retry_after) end end local h = res.Headers if h and tonumber(h["retry-after"] or h["Retry-After"]) then wait = tonumber(h["retry-after"] or h["Retry-After"]) end end wait = math.clamp(wait, 0.3, 6) + math.random() * 0.6 -- + jitter: nhiều máy khỏi retry đồng loạt if os.clock() + wait >= deadline then break end -- hết budget → bỏ safeTaskWait(wait) end STATS.whFail = (STATS.whFail or 0) + 1 return false end -- safeRequire: WaitForChild theo path RỒI require. KHÔNG crash nếu thiếu/chưa replicate -- (FIX bug cũ: pcall(require, RS.Utils.NumberUtils) bị eager-eval indexing → lỗi "Utils is not a valid member" trước khi vào pcall) local function safeRequire(...) local args = { ... } local ok, m = pcall(function() local cur = ReplicatedStorage for _, n in ipairs(args) do cur = cur:WaitForChild(n, 8) if not cur then error("missing " .. n) end end return require(cur) end) return ok and m or nil end -- NumberUtils của game → format $/s y HỆT in-game (vd "1.23M"); fallback tự format nếu load fail local NumberUtils = safeRequire("Utils", "NumberUtils") local function fmt(n) n = tonumber(n) or 0 if NumberUtils then local ok, s = pcall(function() return NumberUtils:ToString(n) end) if ok and type(s) == "string" and s ~= "" then return s end end for _, u in ipairs({ { 1e12, "T" }, { 1e9, "B" }, { 1e6, "M" }, { 1e3, "K" } }) do if n >= u[1] then return ("%.2f%s"):format(n / u[1], u[2]) end end return tostring(math.floor(n)) end -- ── Data / module game (CHỈ đọc bảng data tĩnh — giống kaitunsab; KHÔNG gọi Shared.Animals:GetGeneration) ── local Animals = safeRequire("Datas", "Animals") or {} local Mutations = safeRequire("Datas", "Mutations") or {} -- {mut={Modifier=...}} cho công thức $/s tĩnh local Traits = safeRequire("Datas", "Traits") -- {trait={MultiplierModifier=...}} — trait cộng vào $/s -- AnimalModels ĐÃ XOÁ (chỉ dùng cho meshImage — máy ảnh đã bỏ, giảm RAM) -- ★★ AUTO-SYNC OG (FIX miss-OG BỀN): TIER_NAMES.OG thủ công bị LỆCH khi game thêm OG mới — vd "Spyder Elephant" (1B/s, -- OG đắt nhất game) THIẾU trong list → tierOf=nil → ko xử như OG (ko instant-OG, sai tier routing) = MISS. Gộp MỌI brainrot -- Rarity=="OG" của game vào NAME_TIER="OG" (KHÔNG đè tên đã curated) → tự bắt cả OG game thêm sau, KHỎI sửa list tay nữa. do local added = 0 for nm, d in pairs(Animals) do if type(d) == "table" and tostring(d.Rarity) == "OG" then local key = tostring(nm):gsub("^%s+", ""):gsub("%s+$", ""):lower() if not NAME_TIER[key] then NAME_TIER[key] = "OG" added = added + 1 end end end if added > 0 then WARN(("[OG-SYNC] +%d con Rarity=OG game thiếu trong TIER_NAMES → đã auto thêm tier OG"):format(added)) end end -- ★★ knownPet: model = brainrot hợp lệ nếu CÓ trong Animals HOẶC tên thuộc TIER_NAMES (tierOf~=nil). -- FIX BUG "miss OG (Skibidi Toilet)": con tier đặt theo TÊN VẪN detect dù Animals thiếu key / Generation<=0 (OG collectible gen 0). local function knownPet(name) return name ~= nil and (Animals[name] ~= nil or tierOf(name) ~= nil) end -- ★ SELF-CHECK lúc load: tên trong TIER_NAMES mà KHÔNG có trong Animals (sai tên → MISS con đó) hoặc Generation<=0 → WARN để soi. -- In ra console (WARN luôn in). Đây là cách tìm CHÍNH XÁC vì sao 1 con tier bị bỏ qua (vd Skibidi Toilet). task.spawn(function() if not Animals or next(Animals) == nil then WARN("[TIER-CHECK] Animals data RỖNG (require fail) → detect hỏng nặng!") return end local keys = {} for k in pairs(Animals) do keys[#keys + 1] = tostring(k) end local function similar(n) -- tìm key Animals chứa 1 từ (>=4 ký tự) của tên → gợi ý tên ĐÚNG local hit, out = {}, {} for w in n:lower():gmatch("%a+") do if #w >= 4 then for _, k in ipairs(keys) do if k:lower():find(w, 1, true) then hit[k] = true end end end end for k in pairs(hit) do out[#out + 1] = k end return out end local bad = {} for tier, names in pairs(TIER_NAMES) do for _, n in ipairs(names) do local d = Animals[n] if not d then local s = similar(n) bad[#bad + 1] = ("%s [%s] KHÔNG có trong Animals%s"):format( n, tier, #s > 0 and (" → tên ĐÚNG có thể là: " .. table.concat(s, " / ")) or " (ko thấy key giống)" ) elseif (tonumber(d.Generation) or 0) <= 0 then bad[#bad + 1] = ("%s [%s] có trong Animals NHƯNG Generation=%s (<=0 → bị drop nếu ko fix)"):format( n, tier, tostring(d.Generation) ) end end end if #bad > 0 then WARN(("[TIER-CHECK] %d tên tier CÓ VẤN ĐỀ (lý do MISS con như Skibidi Toilet):"):format(#bad)) for _, l in ipairs(bad) do WARN(" • " .. l) end else LOG("[TIER-CHECK] OK — mọi tên tier khớp Animals + gen>0") end end) -- $/s TÍNH TĨNH (KHÔNG gọi GetGeneration → né anti-cheat): gen = base × (1 + Mutation.Modifier + Σ trait.MultiplierModifier). -- ★ Trait TÍNH VÀO money (để đủ số tiền); field trait vẫn để RỖNG (ko gửi/hiện — trait chỉ dùng cho công thức). local function genOf(name, model) local mut, traitList, seen local function addTrait(t) if t and t ~= "" then seen = seen or {} if not seen[t] then seen[t] = true traitList = traitList or {} traitList[#traitList + 1] = t end end end for _, c in ipairs(model:GetChildren()) do local cn = c.Name if cn:sub(1, 9) == "Mutation." then if not mut then mut = cn:sub(10) end -- lấy cái ĐẦU else local tn = cn:match("^_?Trait%.(.+)$") if tn then addTrait(tn) end end end if not mut then local m = model:GetAttribute("__mutation") or model:GetAttribute("Mutation") if m ~= nil and m ~= "" then mut = tostring(m) end end local attr = model:GetAttribute("Traits") or model:GetAttribute("Trait") if type(attr) == "string" then for t in attr:gmatch("[^,]+") do addTrait((t:gsub("^%s*(.-)%s*$", "%1"))) end end local d = Animals[name] local base = (d and tonumber(d.Generation)) or 0 if base <= 0 then return 0, mut, "" end local mult = 1 if mut and Mutations[mut] then mult = mult + (tonumber(Mutations[mut].Modifier) or 0) end if Traits and traitList then for _, tn in ipairs(traitList) do local td = Traits[tn] if td then mult = mult + (tonumber(td.MultiplierModifier) or 0) end end end return base * mult, mut, "" end -- ★★ gen từ DATA TRỰC TIẾP (Synchronizer): base × (1 + Mutation.Modifier + Σ trait.MultiplierModifier). traits = list tên trait. local function genFromData(name, mutation, traits) local d = Animals[name] local base = (d and tonumber(d.Generation)) or 0 if base <= 0 then return 0 end local mult = 1 if mutation and mutation ~= "" and Mutations[mutation] then mult = mult + (tonumber(Mutations[mutation].Modifier) or 0) end if Traits and type(traits) == "table" then for _, tn in pairs(traits) do local td = Traits[tn] if td then mult = mult + (tonumber(td.MultiplierModifier) or 0) end end end return base * mult end -- ── Ảnh brainrot: ĐÃ XOÁ HẲN máy ảnh (imageFor/fandomImage/meshImage/fandomFetch/urlEncode/imgCache/IMG_NAME_FIX) — ảnh tắt vĩnh viễn, giảm RAM. ── -- (urlEncode/fandomFetch/fandomImage/meshImage/imageFor ĐÃ XOÁ cùng block ảnh trên) -- ── SCAN brainrot ── -- chủ plot từ PlotSign: "X's Base" → X | "YOUR BASE" (label YourBase) BỎ QUA -- "Empty Base" → nil (bỏ) | nếu chỉ thấy "YOUR BASE" mà không có name khác → __SELF__ local function ownerFromSign(plot) local sign = plot:FindFirstChild("PlotSign") if not sign then return nil end local sawYourBase = false for _, d in ipairs(sign:GetDescendants()) do if d:IsA("TextLabel") and d.Text ~= "" then local t = d.Text -- label "YOUR BASE" (parent thường tên "YourBase") = nhà của bot, không phải tên chủ if t == "YOUR BASE" or d.Parent.Name == "YourBase" then sawYourBase = true elseif t == "Empty Base" then return nil else local name = t:match("^(.-)'s Base$") if name and name ~= "" then return name end end end end return sawYourBase and "__SELF__" or nil end -- gom brainrot model đã đặt trong 1 plot (direct child + AnimalPodiums slots). -- ★ THU CẢ con đang trong máy (fuse/craft/...) — sẽ gắn status loại máy khi báo (xem makePet/WS). local function collectPlaced(plot, list) -- direct children for _, obj in ipairs(plot:GetChildren()) do if obj:IsA("Model") and knownPet(obj.Name) then list[#list + 1] = obj end -- ★ knownPet: thu cả con tier-theo-tên (ko chỉ Animals) end -- AnimalPodiums slots local pod = plot:FindFirstChild("AnimalPodiums") if pod then for _, slot in ipairs(pod:GetChildren()) do for _, obj in ipairs(slot:GetChildren()) do if obj:IsA("Model") and knownPet(obj.Name) then list[#list + 1] = obj end end end end end -- (placed quét qua scanPlotAndReport, conveyor qua reportConveyor — xem cuối file) -- ── FLEET REGISTRY (hobeojob /api/usernames/{placeId}) — CHỈ GET (KHÔNG post username) để nhận diện acc fleet ── local USERNAMES_URL = "https://hobeojob.com/api/usernames/%d" local registrySet = {} -- lower(name) -> true local _registryOk = false -- ★ đã có set hợp lệ (GET/cache) ≥1 lần → KHÔNG clobber set tốt bằng rỗng khi sau đó fail local _registryExp = 0 -- os.time() mà data in-memory HẾT HẠN (0 = đã hết hạn → refresh sẽ đọc đĩa / GET) -- ── CACHE ĐĨA: 1 FILE CHUNG / placeId, MỌI tab+acc trên máy XÀI CHUNG (writefile/readfile của executor) ── -- File = ĐÚNG 2 TRƯỜNG {exp=, usernames={...}}. 1 acc GET → ghi file → -- các acc KHÁC đọc file (còn hạn) → KHÔNG GET lại thừa. Hết hạn → acc sớm nhất GET → ghi file mới → các acc đọc lại. local _wf = (writefile and readfile and isfile) and true or false -- executor có file API ko (ko thì cache no-op, GET như cũ) local function usernamesToSet(list) local set, c = {}, 0 for _, u in ipairs(list) do local n = (type(u) == "table") and u.username or u if type(n) == "string" and n ~= "" and not set[n:lower()] then set[n:lower()] = true c = c + 1 end end return set, c end local function _regCachePath() return ("WN_registry_%d.json"):format(game.PlaceId) end -- đọc cache đĩa → (set, exp, cnt) | nil. exp = os.time() file hết hạn (so với now để biết còn hạn ko). local function regCacheRead() if not _wf then return nil end local path = _regCachePath() local ok, raw = pcall(function() if isfile(path) then return readfile(path) end end) if not ok or type(raw) ~= "string" or raw == "" then return nil end local ok2, d = pcall(function() return HttpService:JSONDecode(raw) end) if not (ok2 and type(d) == "table" and type(d.usernames) == "table" and type(d.exp) == "number") then return nil end local set, cnt = usernamesToSet(d.usernames) return set, d.exp, cnt end local function regCacheWrite(usernames, exp) if not _wf then return end pcall(function() writefile(_regCachePath(), HttpService:JSONEncode({ exp = exp, usernames = usernames })) end) end -- refreshRegistry: CACHE-FIRST + RETRY. 1) in-memory còn hạn → thôi. 2) cache đĩa còn hạn → nạp, KHÔNG GET. -- 3) hết hạn → GET (retry GetTries×GetRetryGap) → ghi cache đĩa. 4) GET fail hết lượt → DÙNG TẠM cache đĩa CŨ (còn hơn rỗng). -- ★ jitter theo UserId: các tab hết-hạn-lệch-nhau → tab sớm nhất GET+ghi file, tab sau đọc file (KHÔNG cùng GET 1 lúc = ko bão host). local function refreshRegistry(force) if not (CONFIG.FriendRegistry and CONFIG.FriendRegistry.Enabled) then return false end local fr = CONFIG.FriendRegistry local ttl = fr.CacheTTL or 0 local now = os.time() if ttl > 0 and not force then -- ★ jitter theo UserId SPAN ~2 chu kỳ caller (RefreshInterval): các acc CÙNG MÁY coi file hết-hạn LỆCH nhau -- → acc sớm nhất GET+ghi file, acc sau đọc file mới → KHÔNG cùng GET 1 lúc (giảm bão host). -- (jitter < chu kỳ caller thì vô dụng vì caller chạy theo mốc cố định RefreshInterval → mọi acc hết hạn cùng 1 tick.) local jspan = math.max(1, math.min(math.floor(ttl / 2), 2 * (fr.RefreshInterval or 300))) local jit = (LocalPlayer and LocalPlayer.UserId or 0) % jspan -- coi như hết hạn sớm hơn 'jit' giây (lệch giữa acc) -- (1) in-memory còn hạn → khỏi đụng đĩa/mạng if _registryOk and now < (_registryExp - jit) then return true end -- (2) FILE CHUNG còn hạn + CÓ user (cnt>0) → nạp, KHÔNG GET. (file rỗng = bỏ qua, GET lại — ko tin cache rỗng) local set, exp, cnt = regCacheRead() if set and cnt and cnt > 0 and exp and now < (exp - jit) then registrySet = set _registryOk = true _registryExp = exp LOG(("[REGISTRY] dùng FILE CHUNG (còn %ds, %d user) — bỏ GET"):format(exp - now, cnt)) return true end end -- (3) hết hạn / tắt cache → GET có RETRY → ghi FILE CHUNG local tries = math.max(1, fr.GetTries or 1) local gap = fr.GetRetryGap or 3 for attempt = 1, tries do local raw = httpGet(USERNAMES_URL:format(game.PlaceId)) if raw then local ok, d = pcall(function() return HttpService:JSONDecode(raw) end) if ok and d and type(d.usernames) == "table" then local set, cnt = usernamesToSet(d.usernames) if cnt > 0 then local exp = os.time() + ttl registrySet = set _registryOk = true _registryExp = exp regCacheWrite(d.usernames, exp) -- ★ CHỈ ghi file khi CÓ user → KHÔNG ghi rỗng (tránh poison file chung) LOG(("[REGISTRY] GET OK (%d user) → ghi FILE CHUNG (hết hạn sau %ds)"):format(cnt, ttl)) return true end -- GET hợp lệ nhưng RỖNG (backend chưa populate username): KHÔNG ghi file, KHÔNG clobber data tốt cũ, ép thử lại sớm. if _registryOk then LOG( "[REGISTRY] GET trả RỖNG nhưng đang có data tốt → GIỮ data cũ (ko ghi file rỗng)" ) else registrySet = set _registryOk = true _registryExp = 0 -- exp=0 → luôn "hết hạn" → lần sau GET lại / đọc file acc khác (ko kẹt rỗng cả TTL) LOG("[REGISTRY] GET trả RỖNG (backend chưa có user) → ko ghi file, thử lại lần sau") end return true end end if attempt < tries then LOG(("[REGISTRY] GET /api/usernames fail (%d/%d) → thử lại sau %ss"):format(attempt, tries, gap)) safeTaskWait(gap) end end -- (4) GET fail hết lượt → fallback FILE CHUNG CŨ (kể cả quá hạn) — còn detect được clone, hơn là rỗng if not _registryOk then local set, exp, cnt = regCacheRead() if set and cnt and cnt > 0 then registrySet = set _registryOk = true _registryExp = now -- coi như vừa hết hạn → lần sau thử GET lại sớm WARN( ("[REGISTRY] GET fail hết lượt → DÙNG TẠM FILE CHUNG CŨ (hết hạn %ds trước, %d user) để vẫn detect clone"):format( now - (exp or now), cnt ) ) return true end WARN( "[REGISTRY] GET fail + ko có file chung hợp lệ → registry rỗng (deadlock-breaker recover lần refresh sau)" ) end return false end -- ── Webhook embed ── -- gửi 1 NHÓM pet (cùng tier) tới 1 webhook local TIER_COLOR = { OG = 16766720, PEAK = 16711680, HIGH = 16744192, MID = 10181046, LOW = 65340 } local WEBHOOK_OF = { OG = function() return CONFIG.WebhookOG end, PEAK = function() return CONFIG.WebhookPeak end, HIGH = function() return CONFIG.WebhookHigh end, MID = function() return CONFIG.WebhookMid end, LOW = function() return CONFIG.WebhookLow end, } -- ── FILTER tier-min: con phải nằm TIER_NAMES + $/s ≥ min của tier (peak/og = 0 → gửi luôn) ── local function shouldSend(pet) local t = tierOf(pet.name) -- ★★ TierFilter=false → POST MỌI con: pass hết, tier = tier-theo-tên (nil nếu ko thuộc list) → con nil-tier POST nhưng ko hop. if CONFIG.TierFilter == false then local hf = CONFIG.HighFloorGen or 0 if hf > 0 and (pet.gen or 0) >= hf and (t == nil or t == "LOW" or t == "MID") then t = "HIGH" end -- vẫn ép hàng to lên HIGH để hop return true, t end -- ★ con ≥ HighFloorGen (mặc định 1B/s) nhưng đang free(nil)/LOW/MID → ÉP lên HIGH (hàng to luôn lên high + hop) local hf = CONFIG.HighFloorGen or 0 if hf > 0 and (pet.gen or 0) >= hf and (t == nil or t == "LOW" or t == "MID") then t = "HIGH" end if not t then return false, nil end local min = (CONFIG.TierMinSend and CONFIG.TierMinSend[t]) or 0 if (pet.gen or 0) < min then return false, t end return true, t end -- ── CHỐNG GỬI TRÙNG: MỖI OWNER CHỈ GỬI 1 LẦN / mỗi lần exec ── -- sentOwners là biến LOCAL → tự RESET sạch mỗi khi exec lại script (inject mới = cache trống). -- Đã gửi owner X rồi → mọi lần scan lại nhà X (DescendantAdded / rescan / hop quay lại) đều BỎ. local sentOwners = {} -- lower(owner) -> true local function ownerAllowed(owner) local key = tostring(owner or "?"):lower() if sentOwners[key] then return false end sentOwners[key] = true return true end -- (ownerAlreadySent ĐÃ XOÁ — bỏ gate dedup-theo-owner ở scanPlotAndReport theo yêu cầu. ownerAllowed giữ lại cho conveyor.) -- tên kênh hiển thị theo tier (header webhook) local TIER_LABEL = { OG = "OG", PEAK = "Peaklights", HIGH = "Highlights", MID = "Midlights", LOW = "Lowlights" } -- (petLine ĐÃ XOÁ cùng sendMonitorWebhook — webhook tier dùng nameOf/brName riêng trong sendGroupWebhook) -- (imgUrl/petImage/IMG_CDN/_petImgCache ĐÃ XOÁ — ảnh tắt vĩnh viễn, giảm RAM) -- ── Gửi webhook 1 NHÓM (1 plot): Best (con cao nhất + ảnh) + Other (các con còn lại) ── -- group = { owner, best = pet, others = {pet,...}, source } local function sendGroupWebhook(group) local best = group.best local tier = best.tier or tierOf(best.name) local webhook = WEBHOOK_OF[tier] and WEBHOOK_OF[tier]() if not webhook or webhook == "" then LOG("tier " .. tostring(tier) .. " chưa có webhook — bỏ") return end -- ★ EMBED Moby-style — GIỮ tier/màu/webhook routing của mình; branding = W Notifier; ảnh = imageFor (KHÔNG dùng cdn.lura.blue). local function brName(p) -- "[mut] Name" return ((p.mut and p.mut ~= "") and ("[" .. p.mut .. "] ") or "") .. tostring(p.name) end local headName = brName(best) local players = tostring(math.max(2, #Players:GetPlayers() - 1)) .. "/" .. tostring(Players.MaxPlayers) -- field "All Brainrots": TẤT CẢ con trong 1 KHUNG (code-block) — best + others, canh cột $/s. Giữ < 1024 ký tự. local function nameOf(p) return ((p.mut and p.mut ~= "") and ("[" .. p.mut .. "] ") or "") .. tostring(p.name) end local rows = { { nameOf(best), "$" .. fmt(best.gen) .. "/s" } } for _, p in ipairs(group.others or {}) do rows[#rows + 1] = { nameOf(p), "$" .. fmt(p.gen) .. "/s" } end local maxn = 0 for _, r in ipairs(rows) do if #r[1] > maxn then maxn = #r[1] end end if maxn > 34 then maxn = 34 end -- cap độ rộng cột tên local blk, used = {}, 0 for i, r in ipairs(rows) do local pad = maxn - #r[1] if pad < 1 then pad = 1 end local line = r[1] .. string.rep(" ", pad) .. " " .. r[2] if used + #line + 1 > 950 then blk[#blk + 1] = ("... +%d con nua"):format(#rows - i + 1) break end blk[#blk + 1] = line used = used + #line + 1 end local allVal = "**🎭 All Brainrots**\n```\n" .. table.concat(blk, "\n") .. "\n```" local embed = { title = "🙉 Brainrot Notify", color = TIER_COLOR[tier] or 65340, fields = { { name = "🏷️ Name", value = "**" .. headName .. "**", inline = true }, { name = "💰 Money per sec", value = "**$" .. fmt(best.gen) .. "/s**", inline = true }, { name = "👤 Players", value = "**" .. LocalPlayer.Name .. "**", inline = true }, { name = "\226\128\139", value = allVal, inline = false }, -- zero-width name → block "All Brainrots" }, footer = { text = (CONFIG.DiscordInvite ~= "" and (CONFIG.DiscordInvite .. " • ") or "") .. "W Notifier • " .. tick(), }, } -- ★ ẢNH TẮT (tốc độ POST): không gọi petImage, không gắn thumbnail local payload = { username = "W Notifier | " .. (TIER_LABEL[tier] or tostring(tier)), embeds = { embed }, } -- ★ nổ OG → tag role (mention phải ở content mới ping được) if tier == "OG" and CONFIG.OGRoleTag and CONFIG.OGRoleTag ~= "" then payload.content = CONFIG.OGRoleTag payload.allowed_mentions = { parse = { "roles" } } end sendWebhook(webhook, payload) LOG( ("PING [%s] Best=%s $%s/s + %d other (@%s)"):format( tostring(tier), best.name, fmt(best.gen), group.others and #group.others or 0, tostring(group.owner or group.source) ) ) end -- (sendMonitorWebhook ĐÃ XOÁ — call site đã comment + sendWebhook bên trong cũng đã comment → dead hoàn toàn) -- (FREE-LOG ĐÃ XOÁ: freeSeen + sendFreeLogWebhook bỏ theo yêu cầu — con không-tier giờ chỉ bị bỏ, KHÔNG POST free) -- ── Hop (hobeojob) — pick RANDOM job_id + retry khi vào trúng sv FULL ── local rng = Random.new() local visited = {} local teleportFailed = false local _tpFailResult = nil -- ★ result của TeleportInitFailed gần nhất → phân biệt GameEnded (server CHẾT) vs GameFull/Flooded (còn sống, chỉ full) local _noSvRetries = 0 -- số lần LIÊN TIẾP fetchServers rỗng (≥ NoServerMaxRetries → Roblox random hop) local _noSvNextTry = 0 -- os.clock(): chưa tới mốc này thì KHÔNG request lại (no-server cooldown ~1 phút) TeleportService.TeleportInitFailed:Connect(function(plr, result, msg) if plr and plr.UserId == LocalPlayer.UserId then -- so UserId (cloneref phá == object) teleportFailed = true -- sv full / rate-limit → đánh dấu để retry jobId khác _tpFailResult = result -- ★ giữ result cho reportDead (GameEnded = ko tìm thấy sv = server chết) WARN("TeleportInitFailed: " .. tostring(result) .. " " .. tostring(msg)) end end) -- place đích để hop local function hopPlaceId() return CONFIG.Hop.HopCurrentPlace and game.PlaceId or (CONFIG.PlaceId or game.PlaceId) end -- nguồn 1: hobeojob (đã lọc server còn slot) → {job_id, playing, maxp}. Query đúng place sẽ hop tới. local function fetchHobeojob() local raw = httpGet(("https://hobeojob.com/api/jobs/%d"):format(hopPlaceId())) if not raw then return nil end local ok, data = pcall(function() return HttpService:JSONDecode(raw) end) if not (ok and data and data.servers) then return nil end local list = {} for _, s in ipairs(data.servers) do list[#list + 1] = { job = s.job_id, playing = tonumber(s.playing) or 0, maxp = tonumber(s.max_players) or 8 } end return list end -- nguồn 2 (fallback): API gốc Roblox, paginate tìm server còn slot local function fetchRobloxApi(placeId) local list, cursor = {}, nil for page = 1, (CONFIG.Hop.MaxPages or 6) do -- excludeFullGames=true → API chỉ trả server CÒN SLOT (đỡ phải lọc, né server full) local url = ("https://games.roblox.com/v1/games/%d/servers/Public?limit=100&excludeFullGames=true"):format( placeId ) .. (cursor and ("&cursor=" .. cursor) or "") local raw = httpGet(url) if not raw then break end local ok, data = pcall(function() return HttpService:JSONDecode(raw) end) if not (ok and data and data.data) then safeTaskWait(1.5) -- có thể rate-limit (429) → nghỉ chút rồi thử trang sau break end for _, s in ipairs(data.data) do list[#list + 1] = { job = s.id, playing = tonumber(s.playing) or 0, maxp = tonumber(s.maxPlayers) or 8 } end cursor = data.nextPageCursor if not cursor then break end end return list end -- gom server từ nguồn khả dụng: hobeojob (đã lọc free-slot) → Roblox API (excludeFullGames) local function fetchServers() local src = fetchHobeojob() if src and #src > 0 then LOG(("server source: hobeojob (%d)"):format(#src)) return src end if CONFIG.Hop.UseRobloxApiFallback then local r = fetchRobloxApi(hopPlaceId()) if r and #r > 0 then LOG(("server source: Roblox API (%d, hobeojob trống)"):format(#r)) return r end end return nil end -- blacklist hobeojob: POST jobId server ĐÍCH vào blacklist NGAY TRƯỚC khi teleport vào (reserve cho fleet). -- CHỈ dùng lúc HOP (lấy job id) — KHÔNG post định kỳ khi đang ở server. local function blacklistJob(placeId, jobId) if not CONFIG.Hop.PostBlacklist or not jobId or not httpRequest then return end pcall(httpRequest, { Url = ("https://hobeojob.com/api/jobs/%d/blacklist"):format(placeId), Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = '{"job_id":"' .. tostring(jobId) .. '"}', -- ★ jobId = UUID (ko ký tự cần escape) → nối chuỗi, né JSONEncode }) end -- ★ REPORT DEAD: teleport vào server mà result=GameEnded ("Could not find requested game instance") = server CHẾT → -- POST jobId lên hobeojob /api/jobs/{placeId}/dead → gỡ NGAY khỏi pool GET /api/jobs (clone khác ko bị đẩy vào server hỏng). KHÔNG cần auth. local function reportDead(placeId, jobId) if CONFIG.Hop.ReportDead == false or not jobId or not httpRequest then return end pcall(httpRequest, { Url = ("https://hobeojob.com/api/jobs/%d/dead"):format(placeId), Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = '{"job_id":"' .. tostring(jobId) .. '"}', -- 1 jobId (UUID, né JSONEncode); backend nhận cả job_id lẫn job_ids[] }) LOG("[DEAD] báo server chết → gỡ khỏi pool: " .. tostring(jobId)) end -- ── PRESENCE (hobeojob /api/presence) — heartbeat trạng thái acc cho cả fleet ── -- POST {account,placeId,jobId,players,ping,hops,uptime} → 201. account khớp ^[A-Za-z0-9_]+$ (≤20) = username. local _statsNet = cloneref(game:GetService("Stats")) local function netPingMs() -- ping mạng (ms), 0..600000. Stats.Network…GetNetworkPing (giây) nếu có; fallback 0. local ok, ms = pcall(function() local n = _statsNet and _statsNet.Network and _statsNet.Network.ServerStatsItem if n and n["Data Ping"] then return n["Data Ping"]:GetValue() end -- đã là ms return 0 end) ms = (ok and tonumber(ms)) or 0 if ms < 0 then ms = 0 elseif ms > 600000 then ms = 600000 end return math.floor(ms) end local function postPresence() if not (CONFIG.Presence and CONFIG.Presence.Enabled) or not httpRequest then return end local players = #Players:GetPlayers() if players > 200 then players = 200 end local uptime = sabUptime() pcall(httpRequest, { Url = "https://hobeojob.com/api/presence", Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = HttpService:JSONEncode({ account = LocalPlayer.Name, placeId = game.PlaceId, jobId = tostring(game.JobId), players = players, ping = netPingMs(), hops = getgenv().__SAB_Hops or 0, uptime = uptime, }), }) end local function startPresenceHeartbeat() if not (CONFIG.Presence and CONFIG.Presence.Enabled) then return end task.spawn(function() while true do postPresence() -- chỉ POST heartbeat, KHÔNG GET fleet LOG("presence heartbeat: posted") safeTaskWait(CONFIG.Presence.Interval or 60) end end) end local function hop() local placeId = hopPlaceId() local servers = fetchServers() -- ★ KHÔNG có server để hop (hobeojob+API rỗng): đừng request liên tục (all-acc cùng đói → spam → down sv). -- Chờ NoServerWaitSec rồi thử lại; đủ NoServerMaxRetries lần vẫn rỗng → HOP RANDOM của Roblox (matchmaking). if not servers or #servers == 0 then _noSvRetries = _noSvRetries + 1 if _noSvRetries >= (CONFIG.Hop.NoServerMaxRetries or 3) then WARN(("ko có server để hop %d lần → Roblox random hop (matchmaking Teleport)"):format(_noSvRetries)) _noSvRetries = 0 _noSvNextTry = 0 teleportFailed = false pcall(TeleportService.Teleport, TeleportService, placeId, LocalPlayer) -- Roblox tự đưa vào 1 sv ngẫu nhiên local t0 = os.clock() while os.clock() - t0 < (CONFIG.Hop.WaitPerTry or 6) do if teleportFailed then break end safeTaskWait(0.25) end if not teleportFailed then return true end -- teleport OK → script chết WARN("Roblox random hop FAIL") -- ★ random hop cũng ko được (đã thay Kick/Shutdown bằng messagebox test) pcall(function() _killLeave() end) -- fallback nếu Shutdown ko land → vẫn rời safeTaskWait(3) -- ★ chờ 3s cho kick "ăn" rồi shutdown pcall(function() _killLeave() end) return false, "noserver" end _noSvNextTry = os.clock() + (CONFIG.Hop.NoServerWaitSec or 60) -- chưa đủ N lần → chờ ~1 phút WARN( ("ko có server để hop (lần %d/%d) → chờ %ds rồi thử lại"):format( _noSvRetries, CONFIG.Hop.NoServerMaxRetries or 3, CONFIG.Hop.NoServerWaitSec or 60 ) ) return false, "noserver" end _noSvRetries = 0 _noSvNextTry = 0 -- CÓ server → reset trạng thái no-server -- LẤY TẤT CẢ job (KHÔNG filter slot/người, KHÔNG check blacklist). Chỉ trừ: sv hiện tại, đã thử. local cands = {} for _, s in ipairs(servers) do if s.job and s.job ~= game.JobId and not visited[s.job] then cands[#cands + 1] = s.job end end if #cands == 0 then visited = {} WARN("hết job mới — reset visited") return false end -- shuffle → pick NGẪU NHIÊN, join tất (kể cả sv 1 người) for i = #cands, 2, -1 do local j = rng:NextInteger(1, i) cands[i], cands[j] = cands[j], cands[i] end LOG(("candidate: %d job (random, join tất)"):format(#cands)) -- thử lần lượt: teleport tới jobId; GameFull → thử cái khác local tries = math.min(#cands, CONFIG.Hop.MaxRetries or 12) for i = 1, tries do local jobId = cands[i] visited[jobId] = true teleportFailed = false -- ★ RESERVE: post jobId ĐÍCH vào blacklist NGAY TRƯỚC khi teleport vào (chống clone khác nhảy trùng sv) if CONFIG.Hop.PostBlacklist then pcall(function() blacklistJob(placeId, jobId) end) LOG("[BLACKLIST RESERVE] " .. tostring(jobId)) safeTaskWait(0.2) end LOG(("HOP %d/%d → %s"):format(i, tries, jobId)) pcall(TeleportService.TeleportToPlaceInstance, TeleportService, placeId, jobId, LocalPlayer) local t0 = os.clock() while os.clock() - t0 < (CONFIG.Hop.WaitPerTry or 6) do if teleportFailed then break end safeTaskWait(0.25) end if not teleportFailed then return true end -- ★ result=GameEnded ("Could not find requested game instance") → server CHẾT (ko phải full/rate-limit) → báo dead để gỡ khỏi pool ngay if _tpFailResult == Enum.TeleportResult.GameEnded then pcall(function() reportDead(placeId, jobId) end) end safeTaskWait(CONFIG.Hop.RetryBackoff or 1.5) end -- FALLBACK: retry hết vẫn full (Main quá đông) → matchmaking, Roblox tự đưa vào sv còn chỗ if CONFIG.Hop.MatchmakingFallback then WARN(("%d lần đều GameFull → fallback matchmaking Teleport(%d)"):format(tries, placeId)) teleportFailed = false pcall(TeleportService.Teleport, TeleportService, placeId, LocalPlayer) local t0 = os.clock() while os.clock() - t0 < (CONFIG.Hop.WaitPerTry or 6) do if teleportFailed then break end safeTaskWait(0.25) end if not teleportFailed then return true end WARN("matchmaking fallback cũng fail — loop lại") else WARN(("hop fail sau %d lần thử (toàn sv full?) — loop lại"):format(tries)) end return false end -- (HUB ĐÃ XOÁ — SAB Hub REST + crypto + license/PSK/push-secret bỏ; giữ stub Enabled=false cho UI/dispatch tham chiếu) local HUB = { Enabled = false } -- ============================================================================ -- CDEV REALTIME (PRODUCER) — sab_scanner là máy FARM: POST data LÊN server (HTTP stateless, nhẹ cho 20K tab). -- Mỗi find → 1 HTTP POST http://g_http1.cdev.my/PostData.do? với Body = JSON data (hàm mới Quốc Anh). -- ★ Data POST = JSON THUẦN (plaintext), KHÔNG tự mã hoá — SERVER tự mã hoá XOR-hex khi -- relay cho từng consumer (theo key ConnectK của consumer). (post plaintext → consumer DecodeT ra đúng JSON.) -- Consumer (ClientSab) "GetData||" sẽ nhận "UpdateData||||" mỗi lần ta POST. -- WS gửi heartbeat `Ping||` mỗi 30s; khi mất kết nối mới retry mỗi 2s, socket khỏe thì không poll reconnect. -- ★ status: mặc định CDEV.Status; script khác đổi qua getgenv().__CDEV.setStatus("fusing"/"crafting") -- hoặc gán thẳng getgenv().__CDEV_Status = "crafting". -- ============================================================================ local CDEV = { Enabled = true, -- ★★ TRANSPORT — chọn cách bắn find LÊN server (đổi 1 dòng này): -- [1] "ws" = WebSocket tới server LOCAL (MẶC ĐỊNH, SIÊU NHANH): giữ 1 socket bền, mỗi find chỉ :Send -- "PostJData3||||;name,mutation,traits,owner;..." (CHUỖI, ko JSON) — KHÔNG handshake → onDone tức thì → hop sớm nhất. -- [2] "http" = POST HTTP cũ (stateless) tới cdev.my Hosts bên dưới (giữ nguyên làm phương án dự phòng). Transport = "ws", WsUrl = "ws://127.0.0.1:3000/AdminPost", -- ★ server WS local (1 máy 1 server → spam connect vô tư, ko vấn đề) LogWs = true, -- ★ IN log vòng đời WS (connecting/CONNECTED/fail/closed/reconnect/send-fail) ra console — theo dõi "phần connect". false = tắt. WsReconnectSec = 2, -- ★ retry mỗi 2s chỉ trong lúc mất kết nối; socket khỏe thì worker dừng -- ★ PRODUCER cũ = HTTP POST stateless (Quốc Anh) — nhẹ tài nguyên, chạy tốt 20K tab (KHÔNG giữ WS). Dùng khi Transport="http". -- Mỗi find = 1 POST ://? với Body = JSON data thuần. -- Server nhận → tự mã hoá XOR-hex → relay "UpdateData||||" cho consumer (ClientSab) đang GetData. Scheme = "http", -- g_http1 dùng http thường (executor KHÔNG làm wss/TLS tới cdev.my) Host = "g_http1.cdev.my", -- ★ host mặc định (fallback nếu Hosts rỗng) Hosts = { "g_http5.cdev.my", "g_http2.cdev.my", "g_http3.cdev.my", "g_http4.cdev.my", "g_http6.cdev.my", "g_http7.cdev.my", "g_http8.cdev.my", "g_http9.cdev.my", }, -- ★ POST tới CẢ host — mỗi host 1 task.spawn (song song, KHÔNG đè nhau). g_http6=SV6, g_http7=SV7, g_http8=SV8 Alante, g_http9=SV9 france-paris (chỉ nhánh Transport="http"; mặc định "ws" post local relay region-agnostic) PostPath = "/PostData.do", -- endpoint: POST /PostData.do?, Body = data Name = "Game01", -- ★ "kênh" data (consumer GetData||Game01 mới nhận) Status = "normal", -- status mặc định khi con KHÔNG ở máy (con thường, cướp được) PostRetries = 0, -- ★ RAW FAST: 0 = POST 1 lần, KHÔNG retry (chỉ áp dụng transport HTTP; WS vốn gửi 1 lần). (cũ 5.) PostRetryDelay = 0.5, -- giây giữa các lần retry, backoff tăng dần (0.5, 1.0, ...) PostSettle = 0, -- ★ POST-speed: onDone NGAY khi 1 host OK (relay tự đẩy; ko chờ settle) PostSettleMax = 10, -- ★ TRẦN chờ THÊM sau PostSettle: nếu hết 5s chưa host nào OK mà host VẪN đang retry (backoff>5s) -- → chờ tới khi có host OK / tất cả host xong / chạm trần này. Tránh báo nhầm "all fail" khi 1 host OK muộn. } -- (★ WS disconnect/reconnect alert ĐÃ BỎ — producer HTTP stateless không có "kết nối" để rớt; -- 20K tab cùng POST-fail mà mỗi máy alert sẽ spam vỡ webhook. Theo dõi sức khoẻ ở phía server.) local Cdev = (function() -- httpRequest: theo mẫu Quốc Anh (request/http_request/http.request) + syn.request cho chắc executor. local httpRequest = request or http_request or (http and http.request) or (syn and syn.request) or (getgenv and getgenv().request) -- ★ trạng thái cho UI: connected(=POST gần nhất OK), số gói đã gửi, lúc gửi cuối, url, lỗi gần nhất -- err khởi tạo theo Transport: "ws" → để rỗng (trạng thái WS báo ở lần send đầu); "http" → cảnh báo nếu thiếu httpRequest. local ST = { connected = false, sent = 0, lastAt = 0, url = "", err = ((CDEV.Transport or "ws") == "ws") and "" or (httpRequest and "" or "executor không hỗ trợ HTTP request"), } -- PostData: POST Body=data(string) → url. Trả body nếu Success/2xx, else nil+err. (mẫu Quốc Anh — KHÔNG JSONEncode lại) local function PostData(url, data) if not httpRequest then return nil, "no http" end local ok, resp = pcall(httpRequest, { Url = url, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = data, }) if not ok then return nil, "request error" end local code = tonumber(resp and resp.StatusCode) if resp and (resp.Success or (code and code >= 200 and code < 300)) then return resp.Body or "" end return nil, "HTTP " .. tostring(code or "?") end -- PostDataGame: POST data lên kênh tới . url = :///PostData.do? local function PostDataGame(data, name, host) name = name or CDEV.Name or "Game01" host = host or CDEV.Host local url = (CDEV.Scheme or "http") .. "://" .. host .. (CDEV.PostPath or "/PostData.do") .. "?" .. name ST.url = url return PostData(url, data) end local function statusNow() local g = getgenv and getgenv().__CDEV_Status if type(g) == "string" and g ~= "" then return g end return CDEV.Status or "normal" end -- ★★ WS PRODUCER (Transport="ws") — gửi "PostJData3||||;name,mutation,traits,owner;..." (CHUỖI) qua WebSocket tới server LOCAL. -- Giữ 1 socket BỀN (mở 1 lần, tái dùng cho mọi find = nhanh nhất, ko handshake mỗi lần). Rớt/lỗi → reconnect. -- 1 máy = 1 server local → spam connect vô tư; localhost gần như 0 latency. -- pcall-bọc: nếu 1 global lạ (vd WebSocket là function/userdata ko index được) → ko ném lỗi lúc load (rớt về HTTP), KHÔNG vỡ Cdev. local wsConnect = nil pcall(function() wsConnect = (WebSocket and (WebSocket.connect or WebSocket.Connect)) or (syn and syn.websocket and (syn.websocket.connect or syn.websocket.Connect)) or (Krnl and Krnl.WebSocket and (Krnl.WebSocket.connect or Krnl.WebSocket.Connect)) or (Fluxus and Fluxus.WebSocket and Fluxus.WebSocket.connect) end) -- ★ logger riêng cho vòng đời WS (low-frequency: connect/close/reconnect) — IN ra console, gated CDEV.LogWs. pcall chống print hook/nil. local function wlog(...) if CDEV.LogWs ~= false then pcall(print, "[CDEV-WS]", ...) end end if not wsConnect then wlog("KHÔNG tìm thấy WebSocket lib ở executor → sẽ fallback HTTP cdev.my") end local sock, sockReady, sockConnecting, connectStartedAt = nil, false, false, 0 local _wsFirstSendLogged = false local function _rawSend(s, msg) if s.Send then return s:Send(msg) end if s.send then return s:send(msg) end error("ws: no Send method") end local function wsEnsure() if sockReady and sock then return true end if sockConnecting then -- coroutine khác ĐANG connect (wsConnect có thể yield) → đừng mở socket thứ 2 (leak). NHƯNG nếu connect TREO -- >5s (yield ko bao giờ resume — socket blackhole/half-open) → bỏ cờ cho thử LẠI, KHÔNG pin sockConnecting=true vĩnh viễn. if os.clock() - connectStartedAt > 5 then sockConnecting = false else return false end end if not wsConnect then return false end sockConnecting, connectStartedAt = true, os.clock() local url = CDEV.WsUrl or "ws://127.0.0.1:7654/AdminPost" wlog("connecting →", url) local ok, s = pcall(function() return wsConnect(url) end) sockConnecting = false if not ok or not s then sock, sockReady = nil, false wlog("connect FAIL →", url, "|", tostring(s)) return false end sock, sockReady = s, true wlog("CONNECTED ✓ →", url) -- ★ đánh dấu socket chết khi đóng — hỗ trợ CẢ 2 kiểu OnClose tuỳ executor: signal (:Connect) LẪN callback-field (s.OnClose=fn). -- Nếu ko bắt được close, socket chết mà sockReady vẫn true → :Send vào socket chết có thể KHÔNG báo lỗi = mất gói âm thầm. -- ★ onDead theo ĐÚNG socket (sock==s): close MUỘN của socket CŨ ko được wipe socket MỚI vừa reconnect. local function onDead() ST.markWsDead(s, "socket CLOSED") end pcall(function() local oc = s.OnClose or s.onClose if oc ~= nil and type(oc) ~= "function" then local okc = pcall(function() oc:Connect(onDead) end) -- signal-style if not okc then pcall(function() s.OnClose = onDead end) end -- ko Connect được → callback-field else pcall(function() s.OnClose = onDead end) -- callback-field / chưa set → gán hàm end end) return true end function ST.startWsRetry() if not wsConnect or ST.wsRetrying or (sockReady and sock) then return end ST.wsRetrying = true task.spawn(function() while CDEV.Enabled and (CDEV.Transport or "ws") == "ws" and not (sockReady and sock) do pcall(wsEnsure) if sockReady and sock then break end safeTaskWait(CDEV.WsReconnectSec or 2) end ST.wsRetrying = false end) end function ST.markWsDead(exact, reason) if sock ~= exact then return end sock, sockReady = nil, false pcall(function() local close = exact.Close or exact.close if close then close(exact) end end) wlog(reason, "→ retry mỗi", CDEV.WsReconnectSec or 2, "s tới khi connected") ST.startWsRetry() end local function wsSend(msg) if not wsEnsure() then ST.startWsRetry() return false end local current = sock local ok = pcall(_rawSend, current, msg) if not ok then ST.markWsDead(current, "send FAIL") end return ok and true or false end -- report: fields {best=, mutation=, traits=, owner=} (tự thêm jobId/time/status) → build JSON → POST NGAY (stateless, mỗi find 1 POST). -- ★ Data = JSON THUẦN (plaintext) — SERVER tự mã hoá XOR-hex khi relay cho consumer. jobId đã mã hoá sẵn (encJob). -- task.spawn để POST (yield mạng ~100-300ms) KHÔNG block vòng scan/dispatch. POST fail = mất con đó (HTTP stateless, -- ko queue) — đánh đổi để nhẹ cho 20K tab; cần chắc chắn thì thêm retry/queue sau. -- onDone(ok): gọi SAU khi POST xong (ok=true success / false sau khi retry hết) — caller dùng để HOP đúng lúc. local function report(fields, onDone) -- WS chỉ cần wsConnect; HTTP cần httpRequest. Gate đúng theo Transport (đừng chặn WS chỉ vì thiếu httpRequest). local canSend = ((CDEV.Transport or "ws") == "ws" and wsConnect) or httpRequest if not (CDEV.Enabled and canSend) then if onDone then pcall(onDone, false) end return end fields = fields or {} fields.jobId = fields.jobId or encJob() fields.time = fields.time or os.time() fields.status = fields.status or statusNow() -- ★★ TRANSPORT="ws" (mặc định): bắn "PostJData3||||;name,mutation,traits,owner" qua WS local NGAY → onDone tức thì. if (CDEV.Transport or "ws") == "ws" and wsConnect then local payload = "PostJData3||" .. (CDEV.Name or "Game01") .. "||" .. encJob() -- ★ jobId MÃ HOÁ WNLite (encJob) — ClientSab giải mã .. ";" .. tostring(fields.best or "") .. "," .. tostring(fields.mutation or "") .. "," .. tostring(fields.traits or "") .. "," .. tostring(fields.owner or "") local sent = wsSend(payload) ST.url = CDEV.WsUrl or "ws://127.0.0.1:7654/AdminPost" ST.connected = sent if sent then ST.sent = ST.sent + 1 ST.lastAt = os.time() ST.err = "" if not _wsFirstSendLogged then _wsFirstSendLogged = true wlog( "FIRST send OK ✓ — pipeline WS chạy (PostJData3||" .. (CDEV.Name or "Game01") .. "||…)" ) end else ST.err = "ws send fail" end -- ★ onDone qua task.spawn (KHÔNG gọi đồng bộ): deliver-loop onDone khi POST FAIL làm safeTaskWait(1)+attempt() lại → -- gọi đồng bộ thì mỗi retry CHỒNG stack (attempt→report→onDone→attempt…) → WS down lâu = C-stack overflow. -- task.spawn cho mỗi retry chạy coroutine MỚI (stack nông). SUCCESS: onDone ko yield → chạy XONG ngay trong spawn -- (cùng tick) → set pendingHopReason tức thì → hop KHÔNG chậm. (mirror cách HTTP path gọi onDone từ coroutine riêng.) if onDone then task.spawn(function() pcall(onDone, sent) end) end return end if (CDEV.Transport or "ws") == "ws" and not wsConnect then ST.err = "executor ko hỗ trợ WebSocket → fallback HTTP" -- WS ko khả dụng → rớt xuống POST cũ (cdev.my) end -- ── HTTP POST (Transport="http" hoặc fallback khi ko có WS) ── -- ★ JSONEncode CHỈ build Ở ĐÂY (nhánh HTTP) — WS path dùng chuỗi, ko cần → né JSONEncode vô ích khi Transport="ws". local ok, json = pcall(function() return HttpService:JSONEncode(fields) end) if not ok then if onDone then pcall(onDone, false) end return end local hosts = CDEV.Hosts or { CDEV.Host } local nHosts = #hosts local anyOK, doneN = false, 0 -- ★ POST tới TỪNG host trong task.spawn RIÊNG → chạy SONG SONG, KHÔNG đè nhau. Mỗi host retry PostRetries lần. for _, host in ipairs(hosts) do task.spawn(function() local body local tries = (CDEV.PostRetries or 2) + 1 for attempt = 1, tries do body = PostDataGame(json, CDEV.Name, host) if body ~= nil then break end if attempt < tries then safeTaskWait((CDEV.PostRetryDelay or 0.5) * attempt) end end if body ~= nil then anyOK = true end doneN = doneN + 1 -- ★ host này XONG (OK hoặc hết retry) → cho resolver biết end) end -- ★ ĐỢI tối thiểu PostSettle giây (relay kịp đẩy tới consumer) RỒI mới onDone (caller hop/việc tiếp). -- FIX RACE: backoff 1 host có thể >5s → nếu hết settle mà CHƯA host nào OK và host VẪN đang chạy → chờ thêm tới khi -- có host OK / mọi host xong / chạm trần PostSettleMax. Không còn báo nhầm "all fail" cho find ĐÃ giao (chỉ OK muộn). task.spawn(function() if (CDEV.PostSettle or 0) > 0 then safeTaskWait(CDEV.PostSettle) end -- ★ POST-speed: PostSettle=0 → bỏ frame chết local ceil = os.clock() + (CDEV.PostSettleMax or 10) while (not anyOK) and doneN < nHosts and os.clock() < ceil do safeTaskWait(0.03) end -- ★ poll 0.03s thay 0.25s → onDone/hop fire sớm ~220ms ST.connected = anyOK if anyOK then ST.sent = ST.sent + 1 ST.lastAt = os.time() ST.err = "" else ST.err = "all post fail" end if onDone then pcall(onDone, anyOK) end -- best pet dùng để bật cờ hop end) end local M = { -- connect: Transport="ws" → một worker retry mỗi 2s chỉ tồn tại trong lúc mất kết nối. connect = function() if (CDEV.Transport or "ws") ~= "ws" then return end ST.startWsRetry() if ST.wsHeartbeatStarted then return end ST.wsHeartbeatStarted = true task.spawn(function() while CDEV.Enabled and (CDEV.Transport or "ws") == "ws" do safeTaskWait(60) if not CDEV.Enabled or (CDEV.Transport or "ws") ~= "ws" then return end local current = sock if sockReady and current then local ok = pcall(_rawSend, current, "Ping||" .. tostring(os.clock())) if not ok then ST.markWsDead(current, "heartbeat FAIL") end end end end) end, report = report, -- ★ send chuỗi THÔ qua WS; send fail đánh dấu socket chết và bật retry loop. OG/PEAK retry payload ở dispatchGroup. send = function(msg) if (CDEV.Transport or "ws") ~= "ws" or not wsConnect then return false end local sent = wsSend(msg) ST.url = CDEV.WsUrl or "ws://127.0.0.1:7654/AdminPost" ST.connected = sent if sent then ST.sent = ST.sent + 1 ST.lastAt = os.time() ST.err = "" if not _wsFirstSendLogged then _wsFirstSendLogged = true wlog("FIRST send OK ✓ — pipeline WS (PostJData3||" .. (CDEV.Name or "Game01") .. "||…)") end else ST.err = "ws send fail" end return sent end, setStatus = function(s) if getgenv then getgenv().__CDEV_Status = s end end, state = function() return ST end, } -- ★ cho UI đọc trạng thái POST if getgenv then getgenv().__CDEV = M end -- ★ cho script khác gọi report/setStatus return M end)() -- ============================================================================ -- PATCHED BASE SCANNER -- • Tier/Data/Normalize/Group + BaseDetector theo patched_get_hopping_scanner.luau. -- • Lifecycle fallback resolve exact channel giữa các patch retry; patch thành công thì cleanup fallback trước patched Get. -- • Không GetAllChannels, không permanent poll; restore xong mới consume kết quả patched Get. -- ============================================================================ -- (a) tạo bản ghi pet từ 1 model (đọc gen/mut/trait). nil nếu chưa hợp lệ. -- ★ Con đang ở MÁY active (fuse/craft/...) = KHÔNG cướp được → BỎ HẲN (giống remake), gate FuseDetect.Skip. local pendingHopReason = nil -- (monitor-hop: 8người/friend-clone/no-server) → tryHop. Find hit KHÔNG còn set cái này (đã đổi sang KICK). local _hopRejoin = false -- ★ true (HIGH) → tryHop dùng Teleport(PlaceId) REJOIN matchmaking; false → hop() jobId (low/mid-full, sv chết/nghèo) local _hopKick = false -- legacy monitor-hop state; find OG/PEAK rời trực tiếp trong dispatchGroup. local _exiting = false -- hit action đã được acknowledge → ngừng xử find mới, chống duplicate leave/teleport. -- (globalSeen + tierToCategory + postGameData ĐÃ XOÁ — Dedup bỏ (mọi find POST) + GameData tắt) local _joinPostT = {} -- ★ [name lower] = os.clock() lúc scanner LẦN ĐẦU thấy player này (join-live HOẶC có-sẵn-lúc-scanner-vào) → đo detect-player→send. Set bởi tracker chung (chạy CẢ 2 config), clear khi POST pet họ lần đầu. local _joinedLive = {} -- ★ [name lower] = true nếu thấy qua PlayerAdded (join SAU scanner). Ko có = có sẵn lúc scanner vào (mốc = _svJoinAt). local _svJoinAt = os.clock() -- ★ mốc scanner VÀO server (VM này) — mỗi hop reload VM → reset. Dùng làm mốc "detect" cho người CÓ SẴN. local _boostFps -- ★ forward-decl: gán = boostFps sau khi boostFps định nghĩa (cuối file). Cho hàm TRƯỚC đó (dispatchGroup/detect callback) boost fps được. nil lúc đầu load = no-op (an toàn). local _TOP = { OG = true, PEAK = true, HIGH = true } -- ★ tier "đáng boost fps khi có find" (top tier → cắt settle/hop ở 30fps) local function scheduleHitAction(action, wsMessage, wsSent, onSent) if not action or _exiting or not wsMessage or (action == "teleport" and wsSent ~= true) then return end _exiting = true task.spawn(function() while action == "leave" and wsSent ~= true do local ok, sent = pcall(Cdev.send, wsMessage) wsSent = ok and sent == true if wsSent ~= true then task.wait() end end if wsSent ~= true then return end if onSent then onSent() end if action == "leave" then task.defer(_killLeave) else pcall(TeleportService.Teleport, TeleportService, CONFIG.PlaceId or game.PlaceId, LocalPlayer) end end) end local function dispatchGroup(group) if pendingHopReason or _exiting then return end -- ★ đã quyết hit action → NGỪNG xử find mới (chống spam webhook + duplicate leave/teleport). local b = group.best local action = (b.tier == "OG" or b.tier == "PEAK") and "leave" or (b.tier == "HIGH" and "teleport" or nil) for _, pet in ipairs(group.others or {}) do if pet.tier == "OG" or pet.tier == "PEAK" then action = "leave" break elseif pet.tier == "HIGH" and not action then action = "teleport" end end -- ★ KHÔNG còn dedup (globalSeen/Dedup ĐÃ XOÁ) → MỌI find đều POST (chấp nhận post lại con cũ khi rescan/settle/instant-OG). local wsMessage, wsSent do STATS.sent = STATS.sent + 1 if _boostFps and _TOP[tostring(group.best and group.best.tier)] then _boostFps(0.6) end -- ★ find HIGH+ → boost fps ngắn → WS-flush/hop/kick + burst pet tiếp xử ở 30fps (bounded, tự hết) -- ★★ ƯU TIÊN: cdev POST chạy TRƯỚC TIÊN — server nhận find NHANH NHẤT. Webhook task.spawn riêng → KHÔNG chặn POST. -- (1) WS cdev realtime: đẩy CHUỖI "PostJData3||Game01||;name,mutation,traits,owner;..." (mọi con pass) lên relay. if CDEV.Enabled then pcall(function() local parts = { encJob() } -- ★ jobId[0] = MÃ HOÁ WNLite (encJob) — ClientSab giải mã; pet nối sau local others = group.others or {} for index = 0, #others do local p = index == 0 and b or others[index] parts[#parts + 1] = tostring(p.name) .. "," .. tostring(p.mut or "") .. "," .. tostring(p.trait or "") .. "," .. (tostring(p.owner or ""):gsub(",", "")) -- ★ mutation, trait, owner end wsMessage = "PostJData3||" .. (CDEV.Name or "Game01") .. "||" .. table.concat(parts, ";") if CONFIG.VerboseScan then print("[SEND] " .. wsMessage) end wsSent = Cdev.send(wsMessage) == true end) -- ★ LOG MỖI POST (luôn in): con + owner + source. Kèm join→send (giây) NẾU owner là người VỪA join server -- (PlayerAdded → post lần đầu); owner đã ở sẵn lúc scanner vào = ko có mốc join → ghi "(owner có sẵn)". do local _who = tostring(group.owner or "-") local _con = tostring(group.best and group.best.name) local _src = tostring(group.source) local _k = group.owner and tostring(group.owner):lower() local _t0 = _k and _joinPostT[_k] -- ★ event→send = từ AnimalList/detector event tới Cdev.send (gồm debounce nếu có). join→send là metric riêng bên dưới. -- sync ≈ 0 (POST cùng frame OnChanged); plot = +settle debounce; conveyor = +ConveyorDelay. local _sendS = group.detectAt and (os.clock() - group.detectAt) or nil local _sendStr = _sendS and ("event→send %.3fs"):format(_sendS) or "event→send ?" if _t0 then -- ★ join→send = từ lúc scanner thấy player (join-live hoặc có sẵn lúc vào server) → POST pet đầu tiên. local _tag = _joinedLive[_k] and "join live" or "có sẵn lúc scanner vào" print( ("[SENT] %s | %s | %s | %s | join→send %.2fs (%s)"):format( _con, _who, _src, _sendStr, os.clock() - _t0, _tag ) ) _joinPostT[_k] = nil _joinedLive[_k] = nil -- one-shot: chỉ đo POST đầu tiên của player này else print( ("[SENT] %s | %s | %s | %s | (post lại / owner ko phải player theo dõi)"):format( _con, _who, _src, _sendStr ) ) end end end -- (2) WEBHOOK public theo tier. (con đang máy đã bị bỏ từ makePet/petFromSyncItem → ko tới đây.) if CONFIG.WebhooksEnabled ~= false then task.spawn(function() pcall(sendGroupWebhook, group) end) end end scheduleHitAction(action, wsMessage, wsSent) end -- ★ KHÔNG CHECK BEST CHÍNH XÁC nữa (server tự xử best từ data WS). Chỉ lấy con TIER CAO NHẤT (first-match, BỎ so $/s = "bừa") -- làm đại diện cho: hop (OG/PEAK→kick) + headline webhook. others = phần còn lại. WS vẫn gửi HẾT con (top+others). local ScanConfig = { Debug = CONFIG.Debug == true, ModuleWaitSeconds = CONFIG.SyncModuleWaitSec or 8, PlotsWaitSeconds = 3, JoinSettleSeconds = CONFIG.JoinSettle or 0.3, FpsBoostSeconds = CONFIG.FpsBoostSec or 2, ConveyorRetryDelay = 0.5, Transport = "ws", } local function scanDebug(...) if ScanConfig.Debug then LOG("[PATCHED-SCAN]", ...) end end local ScanFps = { untilAt = 0, boosted = false } function ScanFps.boost(seconds) local base = CONFIG.FpsBase or 10 local boosted = CONFIG.FpsBoost or 60 if boosted <= base then return end ScanFps.untilAt = math.max(ScanFps.untilAt, os.clock() + (tonumber(seconds) or ScanConfig.FpsBoostSeconds)) if ScanFps.boosted then return end ScanFps.boosted = true pcall(setfpscap, boosted) task.spawn(function() while os.clock() < ScanFps.untilAt do safeTaskWait(0.1) end ScanFps.boosted = false pcall(setfpscap, base) end) end _boostFps = ScanFps.boost local onDataReady local TierPolicy = { HighFloorGen = 1000000000, OGMoney = 1000000000, minimum = { LOW = 200000000, MID = 50000000, HIGH = 0, PEAK = 0, OG = 0 }, rank = { LOW = 1, MID = 2, HIGH = 3, PEAK = 4, OG = 5 }, } local PatchedTierNames = { OG = { -- no min (list thủ công — auto-sync theo rarity OG bên dưới sẽ bổ sung con game thêm sau) "Headless Horseman", "John Pork", "Meowl", "Skibidi Toilet", "Strawberry Elephant", "Spyder Elephant", "Arcadragon", "Griffin", "Signore Carapace", }, PEAK = { -- no min, hop ngay sau khi gá»­i "Antonio", "Tenini Ballini", "Bunny and Eggy", "Digi Narwhal", "Dragon Aquanini", "Dragon Cannelloni", "Dragon Gingerini", "Elefanto Frigo", "Fishino Clownino", "Ginger Gerat", "Hydra Bunny", "Hydra Dragon Cannelloni", "Jelly Moby", "Kalika Bros", "Ketupat Bros", "Kraken", "La Casa Boo", "La Supreme Combinasion", "Love Love Bear", "Pancake and Syrup", "Rico Dinero", "Rubrikiko", "Tirilikalika Tirilikalako", "Venuspino", "Los Admins", "Moby Bros", "Grabatron", }, HIGH = { -- no min "Los Secret Combinasionas", "Pizza and Ranch", "Bearito Cabinito", "Foxini Lanternini", "Noodle Noodle Poodle", "Cangurato Gelato", "Rubiko and Kubiko", "Boppin Bunny", "Capitano Moby", "Cash or Card", "Celestial Pegasus", "Celularcini Viciosini", "Cerberus", "Cloverat Clapat", "Coco and Mango", "Cooki and Milki", "Duggy Bros", "Dug dug dug", "Festive 67", "Fortunu and Cashuru", "Fragola La La La", "Fragrama and Chocrama", "Guest 666", "Gym Bros", "Hopilikalika Hopilikalako", "Jolly Jolly Sahur", "Los Amigos", "Los Chillis", "Los Hackers", "Los Sekolahs", "Money Money Bros", "Popcuru and Fizzuru", "Reinito Sleighito", "Rosey and Teddy", "Sammyni Cakini", "Sand Sand Sand", "Spooky and Pumpky", "Steakini Fattini", "Tralaledon", "La Food Combinasion", "Noo my examine", "Globa Steppa", "Lazy Ducky", "Quackini Snackini", "Los Tangcitos", "Los Tictacs", }, MID = { -- min 50M "Abyssaloco", "Avocadorilla", "Brutto Gialutto", "Burguro And Fryuro", "Caylusaurus", "Chillin Chili", "Chipso and Queso", "Eviledon", "Ganganzelli Trulala", "Garama and Madundung", "Gobblino Uniciclino", "Gold Gold Gold", "Gorillo Subwoofero", "Ketupat Kepat", "Ketchuru and Musturu", "La Anniversary Grande", "La Easter Grande", "La Extinct Grande", "La Ginger Sekolah", "La Jolly Grande", "La Romantic Grande", "La Secret Combinasion", "La Spooky Grande", "Los Tacoritas", "La Taco Combinasion", "Las Sis", "Los Bros", "Los Cupids", "Los Fruits", "Los Hotspotsitos", "Los Jolly Combinasionas", "Los Planitos", "Los Puggies", "Los Primos", "Los Spaghettis", "Los Spooky Combinasionas", "Lovin Rose", "Money Money Puggy", "Money Money Reindeer", "Nacho Spyder", "Nuclearo Dinossauro", "Orcaledon", "Rhino Helicopterino", "Rosetti Tualetti", "Sammyni Fattini", "Tacorita Bicicleta", "Tang Tang Keletang", "Tictac Sahur", "Tob Tobi Tobi", "Tuff Toucan", "Ventoliero Pavonero", "W or L", "Pineaplino", "La Lucky Grande", "Pretzo Robo", "Unclito Samito", "Capitano Americano", }, LOW = { -- min 200M "Bacuru and Egguru", "Bananito", "Baskito", "Camera Ramena", "Capitano Gullini", "Chicleteira Cupideira", "Chicleteira Noelteira", "Chimnino", "Churrito Bunnito", "Cigno Fulgoro", "Craburger", "DJ Panda", "John Doe", "La Grande Combinasion", "Los 25", "Los 67", "Los Candies", "Los Combinasionas", "Los Mobilis", "Los Sweethearts", "Mariachi Corazoni", "Mieteteira Bicicleteira", "Noo my Gold", "Noo my Heart", "Octoball", "Rocketini Frostini", "Snailo Clovero", "Spaghetti Tualetti", "Spinny Hammy", "Sushi Inu", "Swag Soda", "Swaggy Bros", "Tacorillo Crocodillo", "Chicleteira Surfeiteira", "Girafini Raftini", }, } TierPolicy.byName = {} for tier, names in pairs(TIER_NAMES) do for _, name in ipairs(names) do TierPolicy.byName[name:lower()] = tier end end function TierPolicy.tierOf(name) return type(name) == "string" and TierPolicy.byName[name:lower()] or nil end function TierPolicy.syncOgRarity(animals) for name, data in pairs(animals) do if type(data) == "table" and data.Rarity == "OG" and not TierPolicy.byName[tostring(name):lower()] then TierPolicy.byName[tostring(name):lower()] = "OG" end end end function TierPolicy.resolveSendTier(generation, tier) if generation >= TierPolicy.HighFloorGen and (not tier or tier == "LOW" or tier == "MID") then tier = "HIGH" end if not tier or generation < TierPolicy.minimum[tier] then return nil end return tier end local Data = { modules = {} } function Data.safeRequire(name) if Data.modules[name] then return Data.modules[name] end local datas = ReplicatedStorage:FindFirstChild("Datas") local module = datas and cloneref(datas:FindFirstChild(name)) if not module then return nil end local ok, value = pcall(require, module) if ok and type(value) == "table" then Data.modules[name] = value if name == "Animals" then TierPolicy.syncOgRarity(value) end return value end return nil end function Data.generation(name, mutation, traits) local animals = Data.safeRequire("Animals") if not animals then return nil, false end local animal = animals[name] local base = type(animal) == "table" and tonumber(animal.Generation) or 0 if base <= 0 then return 0, true end local mutations if mutation ~= "" then mutations = Data.safeRequire("Mutations") if not mutations then return nil, false end end local traitsData if #traits > 0 then traitsData = Data.safeRequire("Traits") if not traitsData then return nil, false end end local multiplier, sleepy = 1, false local mutationData = mutations and mutations[mutation] if type(mutationData) == "table" then multiplier = multiplier + (tonumber(mutationData.Modifier) or 0) end for _, trait in ipairs(traits) do local traitData = traitsData and traitsData[trait] if trait == "Sleepy" then sleepy = true elseif type(traitData) == "table" then multiplier = multiplier + (tonumber(traitData.MultiplierModifier) or 0) end end return math.round(base * multiplier * (sleepy and 0.5 or 1)), true end function Data.start() task.spawn(function() local deadline = os.clock() + ScanConfig.ModuleWaitSeconds repeat Data.safeRequire("Animals") Data.safeRequire("Mutations") Data.safeRequire("Traits") if Data.modules.Animals and Data.modules.Mutations and Data.modules.Traits then if onDataReady then onDataReady() end return end task.wait(0.1) until os.clock() >= deadline end) end local Normalize = {} function Normalize.machineActive(item) local machine = type(item) == "table" and item.Machine if machine == true or (type(item) == "table" and item.Duel == true) then return true end if type(machine) == "table" then return machine.Active == true end return type(machine) == "string" and machine:find('"Active"%s*:%s*true') ~= nil end function Normalize.traits(raw) local list, seen = {}, {} if type(raw) == "table" then for key, value in pairs(raw) do local trait = type(value) == "string" and value or (value == true and type(key) == "string" and key or nil) if trait and not seen[trait] then seen[trait] = true list[#list + 1] = trait end end end return list end function Normalize.build(name, mutation, traits, owner, source) local tier = TierPolicy.tierOf(name) local generation, complete if tier == "OG" then generation, complete = TierPolicy.OGMoney, true else generation, complete = Data.generation(name, mutation, traits) end if not complete then return nil, false end tier = TierPolicy.resolveSendTier(generation, tier) if not tier then return nil, true end return { name = name, gen = generation, mutation = mutation, traits = table.concat(traits, "/"), owner = owner or "", source = source, tier = tier, }, true end function Normalize.fromSyncItem(item, owner, traits, machineActive) if type(item) ~= "table" or type(item.Index) ~= "string" or item.Index == "" then return nil, true end if type(traits) ~= "table" or type(machineActive) ~= "boolean" then return nil, false end if machineActive then return nil, true end local mutation = item.Mutation ~= nil and tostring(item.Mutation) or "" return Normalize.build(item.Index, mutation, traits, owner, "base") end function Normalize.fromModel(model) if not model:IsA("Model") then return nil, true end local mutation, traits, seen = "", {}, {} local function addTrait(value) if value ~= "" and not seen[value] then seen[value] = true traits[#traits + 1] = value end end for _, child in ipairs(model:GetChildren()) do local name = child.Name if mutation == "" and name:sub(1, 9) == "Mutation." then mutation = name:sub(10) else local trait = name:match("^_?Trait%.(.+)$") if trait then addTrait(trait) end end end if mutation == "" then local value = model:GetAttribute("__mutation") or model:GetAttribute("Mutation") mutation = value ~= nil and tostring(value) or "" end local attr = model:GetAttribute("Traits") or model:GetAttribute("Trait") if type(attr) == "string" then for value in attr:gmatch("[^,]+") do addTrait(value:match("^%s*(.-)%s*$")) end end if #traits > 1 then table.sort(traits) end return Normalize.build(model.Name, mutation, traits, "", "conveyor") end function Normalize.fromLogical(instance, rawTraits, knownName) if typeof(instance) ~= "Instance" or not instance:IsA("Model") then return nil, true, {} end local name = knownName or instance:GetAttribute("Index") if type(name) ~= "string" or name == "" then return nil, false end local mutation = instance:GetAttribute("Mutation") local traits = Normalize.traits(rawTraits) local pet, complete = Normalize.build(name, mutation ~= nil and tostring(mutation) or "", traits, "", "conveyor") return pet, complete, traits end local Group = {} function Group.selectBest(pets) local best, bestIndex, bestRank = pets[1], 1, TierPolicy.rank[pets[1].tier] or 0 for index = 2, #pets do local rank = TierPolicy.rank[pets[index].tier] or 0 if rank > bestRank then best, bestIndex, bestRank = pets[index], index, rank end end local ordered = { best } for index, pet in ipairs(pets) do if index ~= bestIndex then ordered[#ordered + 1] = pet end end return best, ordered end function Group.bestKey(pet) return pet.name .. "|" .. pet.mutation .. "|" .. pet.tier .. "|" .. tostring(math.floor(pet.gen / 1000000)) end local BaseDetector local ScannerRuntime = { retryScheduled = false } function ScannerRuntime.isCurrent() return not pendingHopReason and not _exiting end function ScannerRuntime.requestRetry() if ScannerRuntime.retryScheduled or not ScannerRuntime.isCurrent() then return end ScannerRuntime.retryScheduled = true task.delay(ScanConfig.ConveyorRetryDelay, function() ScannerRuntime.retryScheduled = false if ScannerRuntime.isCurrent() and BaseDetector then BaseDetector.retryDelivery() end end) end local Dispatch = {} function Dispatch.group(pets, eventAt, onComplete) if not ScannerRuntime.isCurrent() or #pets == 0 then return false, false end local converted = {} for index, pet in ipairs(pets) do converted[index] = { name = pet.name, gen = pet.gen, mut = pet.mutation, trait = pet.traits, source = pet.source == "base" and "sync" or pet.source, owner = pet.owner ~= "" and pet.owner or nil, tier = pet.tier, } end STATS.seen = STATS.seen + #converted local others = {} for index = 2, #converted do others[#others + 1] = converted[index] end dispatchGroup({ owner = converted[1].owner, best = converted[1], others = others, source = converted[1].source, detectAt = eventAt, }) if onComplete then pcall(onComplete, true) end return true, true end local Conveyor = { logicalSeen = setmetatable({}, { __mode = "k" }), active = false, starting = false, allowed = false, eventWatching = false, } function Conveyor.warn(message) if not Conveyor.warned then Conveyor.warned = true warn(message) end end function Conveyor.stop() Conveyor.allowed = false Conveyor.active = false Conveyor.starting = false Conveyor.logicalSeen = setmetatable({}, { __mode = "k" }) local cleanup = Conveyor.connection Conveyor.connection = nil if ScanConfig.Debug then scanDebug("[CONVEYOR] status", "OFF", "observer cleanup") end if type(cleanup) == "function" then pcall(cleanup) end end function Conveyor.consume(logical, uid, detectAt) if ScanConfig.Debug and typeof(logical) == "Instance" then scanDebug( "[CONVEYOR] Animal detected", "uid=" .. tostring(uid), "index=" .. tostring(logical:GetAttribute("Index")), "mutation=" .. tostring(logical:GetAttribute("Mutation")), "instance=" .. logical:GetFullName() ) end if typeof(logical) ~= "Instance" or Conveyor.logicalSeen[logical] then return false end if type(uid) ~= "string" or uid == "" then return false end local name = logical:GetAttribute("Index") if type(name) ~= "string" or name == "" then return false end local rawTraits local okTraits, traits = pcall(Conveyor.traits.TryIndex, Conveyor.traits, { "traits", uid }) if not okTraits then return false end rawTraits = traits local pet, complete = Normalize.fromLogical(logical, rawTraits, name) if not complete or not pet then return false end local best, ordered = Group.selectBest({ pet }) if not best then return false end Conveyor.logicalSeen[logical] = true Dispatch.group(ordered, detectAt or os.clock()) return true end function Conveyor.tryStart(deadline) local attempt = { valid = true, done = false } local worker = task.spawn(function() local ok, observers, traits = pcall(function() local packages = ReplicatedStorage:FindFirstChild("Packages") local observersModule = packages and cloneref(packages:FindFirstChild("Observers")) local replicatorModule = packages and cloneref(packages:FindFirstChild("ReplicatorClient")) if not observersModule or not replicatorModule then error("logical modules unavailable") end local observers = require(observersModule) local replicator = require(replicatorModule) local traits = type(replicator) == "table" and replicator.get and replicator.get("AnimalTraits") return observers, traits end) if attempt.valid and os.clock() < deadline then attempt.ok, attempt.observers, attempt.traits, attempt.done = ok, observers, traits, true end end) while not attempt.done and os.clock() < deadline do safeTaskWait(math.min(0.03, math.max(0, deadline - os.clock()))) end attempt.valid = false if not attempt.done then pcall(task.cancel, worker) return false end local observers, traits = attempt.observers, attempt.traits if not attempt.ok or type(observers) ~= "table" or type(observers.observeTag) ~= "function" or type(traits) ~= "table" or type(traits.TryIndex) ~= "function" then return false end if not Conveyor.allowed then return false end Conveyor.traits = traits Conveyor.active = true local okConnection, cleanup = pcall(observers.observeTag, "Animal", function(logical) if Conveyor.active then local uid = typeof(logical) == "Instance" and logical.Name or nil local ok = pcall(Conveyor.consume, logical, uid, os.clock()) if not ok then Conveyor.warn("[SCAN] Animal observer callback failed; base scanner remains active") end end if typeof(logical) == "Instance" then return function() Conveyor.logicalSeen[logical] = nil if ScanConfig.Debug then scanDebug("[CONVEYOR] Animal removed; weak entry cleared", "instance=" .. tostring(logical)) end end end return nil end) if not okConnection or type(cleanup) ~= "function" then Conveyor.active = false return false end Conveyor.connection = cleanup return true end function Conveyor.startWhenBee() if Conveyor.eventWatching then return end Conveyor.eventWatching = true task.spawn(function() local synchronizer local deadline = os.clock() + ScanConfig.ModuleWaitSeconds repeat synchronizer = BaseDetector.getSynchronizer() if not synchronizer then safeTaskWait(0.05) end until synchronizer or os.clock() >= deadline if type(synchronizer) ~= "table" or type(synchronizer.Wait) ~= "function" then Conveyor.eventWatching = false Conveyor.warn("[SCAN] Synchronizer.Wait unavailable; base scanner remains active") return end local restore local patchDeadline = os.clock() + ScanConfig.ModuleWaitSeconds * 2 repeat restore = BaseDetector.patchRelateChannels(synchronizer) if type(restore) ~= "function" and not BaseDetector.patchRestoreFailed then safeTaskWait(0.05) end until type(restore) == "function" or BaseDetector.patchRestoreFailed or os.clock() >= patchDeadline if type(restore) ~= "function" then Conveyor.eventWatching = false Conveyor.warn("[SCAN] Events patch unavailable; base scanner remains active") return end local callDone, callOk, events = false, false, nil local callThread = task.spawn(function() callOk, events = pcall(synchronizer.Wait, synchronizer, "Events") callDone = true end) local callDeadline = os.clock() + ScanConfig.ModuleWaitSeconds while not callDone and os.clock() < callDeadline do safeTaskWait(0.03) end local cancelOk = callDone or pcall(task.cancel, callThread) local restoreOk, restored = pcall(restore) if not cancelOk or not restoreOk or restored ~= true then BaseDetector.patchRestoreFailed = true BaseDetector.patchBusy = true Conveyor.eventWatching = false Conveyor.stop() Conveyor.warn("[SCAN] Events Wait restore/cancel failed; conveyor disabled") return end if not callDone or not callOk or type(events) ~= "table" or type(events.Get) ~= "function" or type(events.OnArrayInserted) ~= "function" or type(events.OnArrayRemoved) ~= "function" then Conveyor.eventWatching = false Conveyor.warn("[SCAN] Events channel unavailable; base scanner remains active") return end local function readEvents() local scanned, activeEvents = pcall(events.Get, events, "ActiveEvents") if not scanned or type(activeEvents) ~= "table" then return nil, "read-failed" end local names, bee = {}, false for _, event in pairs(activeEvents) do local name = type(event) == "table" and event.eventName if name ~= nil then names[#names + 1] = tostring(name) bee = bee or name == "Bee" end end table.sort(names) return bee, #names > 0 and table.concat(names, ",") or "none" end local function logEvents(reason, bee, names) if ScanConfig.Debug then scanDebug( "[CONVEYOR] Events", reason, "active=" .. tostring(bee), "events=" .. tostring(names), "conveyor=" .. (Conveyor.active and "ON" or "OFF") ) end end local function hasBee() local bee, names = readEvents() logEvents("snapshot", bee, names) return bee end local function startIfBee(first, second) local event = type(first) == "table" and first or type(second) == "table" and second or nil local eventName = event and event.eventName if ScanConfig.Debug then scanDebug( "[CONVEYOR] ActiveEvents insert", "arg1=" .. tostring(first), "arg2=" .. tostring(second), "event=" .. tostring(eventName) ) end local bee = eventName == "Bee" or hasBee() == true if bee then Conveyor.allowed = true if ScanConfig.Debug then scanDebug("[CONVEYOR] Bee inserted", "conveyor=STARTING") end pcall(Conveyor.start) end end local function stopIfNoBee(event) if type(event) == "table" and event.eventName == "Bee" and hasBee() == false then Conveyor.stop() if ScanConfig.Debug then scanDebug("[CONVEYOR] Bee removed", "conveyor=OFF") end end end local removedHooked = pcall(events.OnArrayRemoved, events, "ActiveEvents", stopIfNoBee) local insertedHooked = removedHooked and pcall(events.OnArrayInserted, events, "ActiveEvents", startIfBee) if not insertedHooked then Conveyor.eventWatching = false Conveyor.warn("[SCAN] Events callbacks unavailable; base scanner remains active") return end if ScanConfig.Debug then scanDebug("[CONVEYOR] ActiveEvents callbacks connected") end local bee = hasBee() if bee == true then Conveyor.allowed = true pcall(Conveyor.start) end if ScanConfig.Debug then scanDebug("[CONVEYOR] Events ready", "conveyor=" .. (Conveyor.active and "ON" or "OFF")) end end) end function Conveyor.start() if not Conveyor.allowed or Conveyor.active or Conveyor.starting then return end Conveyor.starting = true task.spawn(function() local deadline = os.clock() + 8 while Conveyor.allowed and not Conveyor.active and os.clock() < deadline do if Conveyor.tryStart(deadline) then Conveyor.starting = false if Conveyor.allowed then LOG("conveyor scanner: Animal observer active") if ScanConfig.Debug then scanDebug("[CONVEYOR] status", "ON") end else Conveyor.stop() end return end safeTaskWait(math.min(0.1, math.max(0, deadline - os.clock()))) end Conveyor.starting = false Conveyor.warn("[SCAN] Animal observer unavailable; base scanner remains active") end) end BaseDetector = { Synchronizer = nil, moduleLoading = false, moduleToken = nil, moduleThread = nil, channels = setmetatable({}, { __mode = "k" }), resolved = {}, pending = {}, created = {}, destroyed = setmetatable({}, { __mode = "k" }), hooked = setmetatable({}, { __mode = "k" }), pendingDispatch = setmetatable({}, { __mode = "k" }), uuidSeen = setmetatable({}, { __mode = "k" }), deliveryRetry = setmetatable({}, { __mode = "k" }), lastSignature = setmetatable({}, { __mode = "k" }), lastBest = setmetatable({}, { __mode = "k" }), plotsRoot = nil, plotsConnection = nil, plotsRemovedConnection = nil, workspaceConnection = nil, workspaceRemovedConnection = nil, channelCreatedConnection = nil, channelDestroyedConnection = nil, retryTokens = setmetatable({}, { __mode = "k" }), lifecycleToken = nil, earlyLifecycleToken = nil, enumerationAttempted = false, enumerationRunning = false, enumerationToken = nil, enumerationDeadline = nil, patchReady = false, patchFailed = false, patchApplied = false, patchAttempt = nil, lifecycleFallbackActive = false, warnedModule = false, warnedCache = false, warnedChanged = false, warnedLifecycle = false, warnedUnresolved = false, patchRestoreFailed = false, patchBusy = false, patchFailureReported = false, patchRetrySeconds = 180, patchRetryDelay = 0.5, getMaxAttempts = 3, getRetryDelay = 0.1, } function BaseDetector.reportPatchFailure(attempt) if BaseDetector.patchFailureReported then return end BaseDetector.patchFailureReported = true local webhook = CONFIG.PatchFailureWebhook if type(webhook) ~= "string" or webhook == "" then return end task.spawn(function() pcall(sendWebhook, webhook, { embeds = { { title = "RelateChannels patch unavailable", fields = { { name = "Username", value = LocalPlayer.Name, inline = true }, { name = "Message", value = ("Patch unavailable after 3 minutes (%d attempts)"):format(attempt), }, }, }, }, allowed_mentions = { parse = {} }, }) end) end function BaseDetector.getSynchronizer() if BaseDetector.Synchronizer then return BaseDetector.Synchronizer end if BaseDetector.moduleLoading then return nil end local packages = ReplicatedStorage:FindFirstChild("Packages") local module = packages and cloneref(packages:FindFirstChild("Synchronizer")) if not module then if not BaseDetector.warnedModule then BaseDetector.warnedModule = true warn("[SCAN] Synchronizer unavailable; base detection will retry") end return nil end local token = {} BaseDetector.moduleToken = token BaseDetector.moduleLoading = true local thread = task.spawn(function() local ok, value = pcall(require, module) if BaseDetector.moduleToken ~= token then return end BaseDetector.moduleLoading = false BaseDetector.moduleThread = nil if ok and type(value) == "table" and type(value.Get) == "function" then BaseDetector.Synchronizer = value BaseDetector.warnedModule = false elseif not BaseDetector.warnedModule then BaseDetector.warnedModule = true warn("[SCAN] Synchronizer unavailable; base detection will retry") end end) BaseDetector.moduleThread = BaseDetector.moduleLoading and thread or nil task.delay(ScanConfig.ModuleWaitSeconds, function() if BaseDetector.moduleToken ~= token or not BaseDetector.moduleLoading then return end BaseDetector.moduleToken = {} BaseDetector.moduleLoading = false BaseDetector.moduleThread = nil pcall(task.cancel, thread) end) return nil end function BaseDetector.rawEntryCount(value, stopAfter) if type(value) ~= "table" then return -1 end local count = 0 for _ in next, value do count = count + 1 if stopAfter and count > stopAfter then return count end end return count end function BaseDetector.findVirtualRelate(synchronizer) scanDebug("virtual discovery: start") if type(synchronizer) ~= "table" or type(synchronizer.Get) ~= "function" or type(islclosure) ~= "function" then scanDebug("virtual discovery: Synchronizer.Get/debug API unavailable") return nil end local getUpvalues = debug.getupvalues local rootUpvaluesOk, rootUpvalues = pcall(getUpvalues, synchronizer.Get) if not rootUpvaluesOk or type(rootUpvalues) ~= "table" then scanDebug("virtual discovery: Get upvalues unavailable") return nil end local tableReplacements = { ["[C]"] = "[JACKY]", ["writefile"] = "[Jacky]", ["bahah"] = "[Jacky]", -- ["string cần thay"] = "[JACKY]", } local maxDepth = 8 local maxTables = 1000 local maxFunctions = 1000 local candidates = {} local candidateCount = 0 local function scanCandidate(target, captures) local visitedTables = {} local visitedFunctions = {} local tablePatches = {} local tableCount = 0 local functionCount = 0 local foundWritefile = false local scanValue local function scanFunction(fn, scanUpvalues, depth) if visitedFunctions[fn] or functionCount >= maxFunctions then return end visitedFunctions[fn] = true functionCount = functionCount + 1 if not scanUpvalues then return end local upvaluesOk, upvalues = pcall(getUpvalues, fn) if upvaluesOk and type(upvalues) == "table" then scanValue(upvalues, depth + 1) end end scanValue = function(value, depth) local valueType = type(value) if valueType == "function" then local closureOk, isClosure = pcall(islclosure, value) if closureOk and isClosure then scanFunction(value, false, depth) end return end if valueType ~= "table" or visitedTables[value] or depth > maxDepth or tableCount >= maxTables then return end visitedTables[value] = true tableCount = tableCount + 1 for key, child in next, value do local replacement = type(child) == "string" and tableReplacements[child] if replacement then foundWritefile = foundWritefile or child == "writefile" tablePatches[#tablePatches + 1] = { table = value, key = key, original = child, replacement = replacement, } end scanValue(child, depth + 1) end end scanFunction(target, true, 0) if not foundWritefile then return nil end return { fn = target, tablePatches = tablePatches, captures = captures, tables = tableCount, functions = functionCount, } end for rootIndex, rootValue in next, rootUpvalues do local target = type(rootValue) == "table" and rawget(rootValue, 0) local closureOk, isClosure = pcall(islclosure, target) if type(target) == "function" and closureOk and isClosure then candidateCount = candidateCount + 1 local targetUpvaluesOk, targetUpvalues = pcall(getUpvalues, target) local captures = targetUpvaluesOk and type(targetUpvalues) == "table" and rawget(targetUpvalues, rootIndex) if type(captures) == "table" and rawget(captures, 0) == false then local candidate = scanCandidate(target, captures) if candidate then candidate.rootIndex = rootIndex candidates[#candidates + 1] = candidate end end end end if #candidates ~= 1 then scanDebug( "virtual discovery: candidate mismatch", "closures=" .. tostring(candidateCount), "matches=" .. tostring(#candidates) ) return nil end local candidate = candidates[1] scanDebug( "virtual discovery: graph scanned", "slot=" .. tostring(candidate.rootIndex), "tables=" .. tostring(candidate.tables), "functions=" .. tostring(candidate.functions), "patches=" .. tostring(#candidate.tablePatches) ) return { mode = "virtual", fn = candidate.fn, tablePatches = candidate.tablePatches, captures = candidate.captures, flagIndex = 0, originalFlag = false, } end function BaseDetector.findAnimalsRelate() local shared = ReplicatedStorage:FindFirstChild("Shared") local module = shared and shared:FindFirstChild("Animals") if not module then return nil end local required, animals = pcall(require, module) local getGeneration = required and type(animals) == "table" and animals.GetGeneration if type(getGeneration) ~= "function" then return nil end local upvalues = debug.getupvalues(getGeneration) local relate = type(upvalues) == "table" and rawget(upvalues, 1) if type(relate) ~= "function" or tostring(debug.info(relate, "s")):gsub("%s+$", "") ~= "ReplicatedStorage.Shared.Animals" then return nil end local relateUpvalues = debug.getupvalues(relate) local runService = type(relateUpvalues) == "table" and rawget(relateUpvalues, 1) local remote = type(relateUpvalues) == "table" and rawget(relateUpvalues, 2) local guid = type(relateUpvalues) == "table" and rawget(relateUpvalues, 3) if BaseDetector.rawEntryCount(relateUpvalues, 3) ~= 3 or runService ~= game:GetService("RunService") or typeof(remote) ~= "Instance" or not remote:IsA("RemoteEvent") or remote.Parent == nil or type(guid) ~= "string" or #guid ~= 36 then return nil end local constants = debug.getconstants(relate) if type(constants) ~= "table" then return nil end local replacements = { ["[C]"] = "[JACKY]", ["writefile"] = "jackywashere", ["bahah"] = "jackywashere", } local patches = {} local foundWritefile = false for index = 1, rawlen(constants) do local value = rawget(constants, index) if value == "jackywashere" then return nil end local replacement = replacements[value] if replacement then foundWritefile = foundWritefile or value == "writefile" patches[#patches + 1] = { index = index, original = value, replacement = replacement, } end end if not foundWritefile then return nil end return { mode = "native", fn = relate, constantPatches = patches, originalFlag = false, } end function BaseDetector.patchRelateChannels(synchronizer) if BaseDetector.patchBusy then return nil end BaseDetector.patchBusy = true BaseDetector.patchRestoreFailed = false if type(debug) ~= "table" or type(debug.info) ~= "function" or type(debug.getconstants) ~= "function" or type(debug.getupvalues) ~= "function" or type(debug.setconstant) ~= "function" then BaseDetector.patchBusy = false return nil end local stage = "animals" scanDebug("RelateChannels patch: start") local ok, restore = pcall(function() local animalsTarget = BaseDetector.findAnimalsRelate() if not animalsTarget then scanDebug("RelateChannels patch: Shared.Animals target unavailable") return nil end stage = "virtual" local synchronizerTarget = BaseDetector.findVirtualRelate(synchronizer) if not synchronizerTarget then scanDebug("RelateChannels patch: virtual target unavailable") return nil end stage = "apply/verify" local restored = false local function constantValue(target, patch) local readOk, constants = pcall(debug.getconstants, target.fn) if not readOk or type(constants) ~= "table" then return nil, false end return rawget(constants, patch.index), true end local function tableValue(patch) local readOk, value = pcall(rawget, patch.table, patch.key) return value, readOk end local function restoreConstants(target) local success = true for _, patch in ipairs(target.constantPatches) do local value, valueOk = constantValue(target, patch) if not valueOk then success = false elseif value == patch.replacement then if not pcall(debug.setconstant, target.fn, patch.index, patch.original) then success = false end elseif value ~= patch.original then success = false end local restoredValue, restoredOk = constantValue(target, patch) if not restoredOk or restoredValue ~= patch.original then success = false end end return success end local function restoreTables(target) local success = true for _, patch in ipairs(target.tablePatches) do local value, valueOk = tableValue(patch) if not valueOk then success = false elseif value == patch.replacement then if not pcall(rawset, patch.table, patch.key, patch.original) then success = false end elseif value ~= patch.original then success = false end local restoredValue, restoredOk = tableValue(patch) if not restoredOk or restoredValue ~= patch.original then success = false end end return success end local function restoreOwned() if restored then return true end local success = true local flag = rawget(synchronizerTarget.captures, synchronizerTarget.flagIndex) if flag == true then if not pcall( rawset, synchronizerTarget.captures, synchronizerTarget.flagIndex, synchronizerTarget.originalFlag ) then success = false end elseif flag ~= synchronizerTarget.originalFlag then success = false end local synchronizerRestored = restoreTables(synchronizerTarget) local animalsRestored = restoreConstants(animalsTarget) if not synchronizerRestored or not animalsRestored then success = false end if rawget(synchronizerTarget.captures, synchronizerTarget.flagIndex) ~= synchronizerTarget.originalFlag then success = false end restored = success return success end local preflight = rawget(synchronizerTarget.captures, synchronizerTarget.flagIndex) == synchronizerTarget.originalFlag for _, patch in ipairs(animalsTarget.constantPatches) do local value, valueOk = constantValue(animalsTarget, patch) preflight = preflight and valueOk and value == patch.original end for _, patch in ipairs(synchronizerTarget.tablePatches) do local value, valueOk = tableValue(patch) preflight = preflight and valueOk and value == patch.original end if not preflight then scanDebug("RelateChannels patch: preflight failed") return nil end local applied = pcall(function() for _, patch in ipairs(animalsTarget.constantPatches) do debug.setconstant(animalsTarget.fn, patch.index, patch.replacement) end for _, patch in ipairs(synchronizerTarget.tablePatches) do rawset(patch.table, patch.key, patch.replacement) end rawset(synchronizerTarget.captures, synchronizerTarget.flagIndex, true) end) local verified = applied for _, patch in ipairs(animalsTarget.constantPatches) do local value, valueOk = constantValue(animalsTarget, patch) verified = verified and valueOk and value == patch.replacement end for _, patch in ipairs(synchronizerTarget.tablePatches) do local value, valueOk = tableValue(patch) verified = verified and valueOk and value == patch.replacement end if verified then verified = rawget(synchronizerTarget.captures, synchronizerTarget.flagIndex) == true end if not verified then if not restoreOwned() then BaseDetector.patchRestoreFailed = true end return nil end scanDebug( "RelateChannels patch: verified", "virtualPatches=" .. tostring(#synchronizerTarget.tablePatches), "animalsPatches=" .. tostring(#animalsTarget.constantPatches) ) print( "[SCAN] Patched constants:", table.concat( (function() local names = {} for _, patch in ipairs(animalsTarget.constantPatches) do names[#names + 1] = patch.original end return names end)(), ", " ), "| module: ReplicatedStorage.Shared.Animals" ) return restoreOwned end) if not ok then BaseDetector.patchRestoreFailed = true warn("[SCAN] RelateChannels patch error at " .. stage) return nil end if type(restore) ~= "function" then BaseDetector.patchBusy = false return nil end return function() local restoreOk, restored = pcall(restore) if restoreOk and restored == true then BaseDetector.patchBusy = false end if not restoreOk then error(restored, 0) end return restored end end function BaseDetector.rawCandidate(item) if type(item) ~= "table" or type(item.Index) ~= "string" or item.Index == "" then return false, true end if TierPolicy.tierOf(item.Index) then return true, true end local animals = Data.safeRequire("Animals") if not animals then return false, false end return animals[item.Index] ~= nil, true end function BaseDetector.scanChannel(channel, eventAt) eventAt = eventAt or os.clock() local scanStartedAt = os.clock() local cache = type(channel) == "table" and channel.CacheTable local list = cache and cache.AnimalList if type(list) ~= "table" then return end local owner = cache.Owner owner = typeof(owner) == "Instance" and owner.Name or tostring(owner or "") -- Fast base path: tier allow-list + UUID session cache, then shared acknowledged hit action. -- Keep PostJData3 shape; WS owns valuation, so no generation/machine/state work. if ScannerRuntime.isCurrent() and CDEV.Enabled then local seen = BaseDetector.uuidSeen[channel] or {} BaseDetector.uuidSeen[channel] = seen local parts, uuids, sentNames = { encJob() }, {}, {} local action for _, item in pairs(list) do local name = type(item) == "table" and item.Index local uuid = type(item) == "table" and item.UUID local tier = TierPolicy.tierOf(name) if tier and not Normalize.machineActive(item) and type(uuid) == "string" and uuid ~= "" and not seen[uuid] then if tier == "OG" or tier == "PEAK" then action = "leave" elseif tier == "HIGH" and not action then action = "teleport" end local traits = {} if type(item.Traits) == "table" then for key, value in pairs(item.Traits) do traits[#traits + 1] = type(value) == "string" and value or (value == true and type(key) == "string" and key or "") end end parts[#parts + 1] = name .. "," .. tostring(item.Mutation or "") .. "," .. table.concat(traits, "/") .. "," .. owner:gsub(",", "") uuids[#uuids + 1] = uuid sentNames[#sentNames + 1] = name end end local sent = false if #uuids > 0 then local cdevStartedAt = os.clock() sent = Cdev.send("PostJData3||" .. (CDEV.Name or "Game01") .. "||" .. table.concat(parts, ";")) == true if CONFIG.VerboseScan then print( "[BASE-WS-TIME] event_to_scan_start=" .. tostring(scanStartedAt - eventAt) .. "s scan_to_send_start=" .. tostring(cdevStartedAt - scanStartedAt) .. "s cdev_send=" .. tostring(os.clock() - cdevStartedAt) .. "s sent=" .. tostring(sent) ) end local function markSent() for _, uuid in ipairs(uuids) do seen[uuid] = true end end if sent and not action then markSent() end if action then scheduleHitAction( action, "PostJData3||" .. (CDEV.Name or "Game01") .. "||" .. table.concat(parts, ";"), sent, markSent ) end if sent then local joinAt = _joinPostT[owner:lower()] for _, name in ipairs(sentNames) do print( ("[SENT] %s | %s | sync | event→send %s s | join→send %s (%s)"):format( name, owner, tostring(os.clock() - eventAt), joinAt and tostring(os.clock() - joinAt) or "n/a", joinAt and "join live" or "owner có sẵn" ) ) end end end if CONFIG.VerboseScan then print("[BASE-WS] queued=" .. tostring(#uuids) .. " prep=" .. tostring(os.clock() - scanStartedAt) .. "s") end return end local count, candidates, candidateTraits, candidateMachineActive, scratch = 0, {}, {}, {}, {} for _, item in pairs(list) do local candidate, ready = BaseDetector.rawCandidate(item) if not ready then return end if candidate then count = count + 1 candidates[count] = item local traits = Normalize.traits(item.Traits) local machineActive = Normalize.machineActive(item) candidateTraits[count] = traits candidateMachineActive[count] = machineActive scratch[count] = tostring(item.Index) .. "\1" .. tostring(item.Mutation or "") .. "\1" .. table.concat(traits, "/") .. "\1" .. (machineActive and "1" or "0") end end if count == 0 then BaseDetector.pendingDispatch[channel] = nil BaseDetector.lastSignature[channel] = nil BaseDetector.lastBest[channel] = nil return end local signature = owner .. "#" .. table.concat(scratch, "|", 1, count) local pending = BaseDetector.pendingDispatch[channel] if BaseDetector.lastSignature[channel] == signature then if pending and pending.signature ~= signature then BaseDetector.pendingDispatch[channel] = nil end return end local passing = {} for index, item in ipairs(candidates) do local pet, complete = Normalize.fromSyncItem(item, owner, candidateTraits[index], candidateMachineActive[index]) if not complete then return end if pet then passing[#passing + 1] = pet end end if #passing == 0 then BaseDetector.pendingDispatch[channel] = nil BaseDetector.lastSignature[channel] = signature BaseDetector.lastBest[channel] = nil return end local best, ordered = Group.selectBest(passing) local bestKey = Group.bestKey(best) pending = BaseDetector.pendingDispatch[channel] if pending and (pending.signature ~= signature or pending.bestKey ~= bestKey) then BaseDetector.pendingDispatch[channel] = nil pending = nil end if BaseDetector.lastBest[channel] == bestKey then BaseDetector.lastSignature[channel] = signature return end if pending then return end local token if ScanConfig.Transport == "webhook" then token = { bestKey = bestKey, signature = signature } BaseDetector.pendingDispatch[channel] = token end local onComplete if ScanConfig.Transport == "webhook" then onComplete = function(delivered) if BaseDetector.pendingDispatch[channel] ~= token then return end BaseDetector.pendingDispatch[channel] = nil if delivered and BaseDetector.isCurrent(channel) then BaseDetector.deliveryRetry[channel] = nil BaseDetector.lastSignature[channel] = signature BaseDetector.lastBest[channel] = bestKey elseif BaseDetector.isCurrent(channel) then BaseDetector.deliveryRetry[channel] = eventAt ScannerRuntime.requestRetry() end end end local sent, accepted = Dispatch.group(ordered, eventAt, onComplete) if sent then BaseDetector.pendingDispatch[channel] = nil BaseDetector.deliveryRetry[channel] = nil BaseDetector.lastSignature[channel] = signature BaseDetector.lastBest[channel] = bestKey elseif token and not accepted and BaseDetector.pendingDispatch[channel] == token then BaseDetector.pendingDispatch[channel] = nil if BaseDetector.isCurrent(channel) then BaseDetector.deliveryRetry[channel] = eventAt ScannerRuntime.requestRetry() end end end function BaseDetector.isCurrent(channel) local plot = BaseDetector.channels[channel] if not plot then return false end local record = BaseDetector.resolved[plot.Name] return Workspace:FindFirstChild("Plots") == BaseDetector.plotsRoot and plot.Parent == BaseDetector.plotsRoot and record ~= nil and record.plot == plot and record.channel == channel end function BaseDetector.retryDelivery() local pending = {} for channel, eventAt in pairs(BaseDetector.deliveryRetry) do BaseDetector.deliveryRetry[channel] = nil pending[#pending + 1] = { channel, eventAt } end for _, entry in ipairs(pending) do local channel, eventAt = entry[1], entry[2] if BaseDetector.isCurrent(channel) and not BaseDetector.pendingDispatch[channel] then BaseDetector.scanChannel(channel, eventAt) end end end function BaseDetector.disconnect(connection) if connection then pcall(function() connection:Disconnect() end) end end function BaseDetector.isConnected(connection) if not connection then return false end local ok, connected = pcall(function() return connection.Connected end) return not ok or connected ~= false end function BaseDetector.clearChannel(channel) local eventChannel = channel and channel.EventChannel if eventChannel then BaseDetector.disconnect(BaseDetector.hooked[eventChannel]) BaseDetector.hooked[eventChannel] = nil end BaseDetector.channels[channel] = nil BaseDetector.lastSignature[channel] = nil BaseDetector.lastBest[channel] = nil BaseDetector.pendingDispatch[channel] = nil BaseDetector.uuidSeen[channel] = nil BaseDetector.deliveryRetry[channel] = nil end function BaseDetector.hookChannel(channel) if not BaseDetector.isCurrent(channel) then return false end local eventChannel = channel.EventChannel if type(eventChannel) ~= "table" or type(eventChannel.OnChanged) ~= "function" then return false end local existing = BaseDetector.hooked[eventChannel] if BaseDetector.isConnected(existing) then return true end BaseDetector.disconnect(existing) BaseDetector.hooked[eventChannel] = nil local function onAnimalList() local eventAt = os.clock() if not BaseDetector.isCurrent(channel) or channel.EventChannel ~= eventChannel then return end -- ScanFps.boost(ScanConfig.FpsBoostSeconds) if type(eventChannel.CacheTable) == "table" then channel.CacheTable = eventChannel.CacheTable end BaseDetector.scanChannel(channel, eventAt) end local ok, connection = pcall(eventChannel.OnChanged, eventChannel, "AnimalList", onAnimalList) if ok and type(connection) == "table" and type(connection.Disconnect) == "function" then BaseDetector.hooked[eventChannel] = connection BaseDetector.warnedChanged = false onAnimalList() return true end if not BaseDetector.warnedChanged then BaseDetector.warnedChanged = true warn("[SCAN] AnimalList listener unavailable; bounded targeted retry active") end return false end function BaseDetector.registerPlot(plot) if plot then BaseDetector.pending[plot.Name] = plot end end function BaseDetector.tryResolve(plot, eventChannel) if BaseDetector.patchFailed or not (BaseDetector.patchReady or BaseDetector.lifecycleFallbackActive) then return false end if not plot or plot.Parent ~= BaseDetector.plotsRoot then return false end if type(eventChannel) ~= "table" or BaseDetector.destroyed[eventChannel] or tostring(eventChannel.Index) ~= plot.Name then if not BaseDetector.warnedCache then BaseDetector.warnedCache = true warn("[SCAN] exact plot channel unavailable; bounded targeted retry active") end return false end if type(eventChannel.CacheTable) ~= "table" then if not BaseDetector.warnedCache then BaseDetector.warnedCache = true warn("[SCAN] exact plot channel cache not ready; bounded targeted retry active") end return false end BaseDetector.warnedCache = false local previous = BaseDetector.resolved[plot.Name] local channel = previous and previous.plot == plot and previous.channel.EventChannel == eventChannel and previous.channel or nil if not channel then if previous then BaseDetector.clearChannel(previous.channel) end channel = { Index = plot.Name, EventChannel = eventChannel } BaseDetector.resolved[plot.Name] = { plot = plot, channel = channel } BaseDetector.channels[channel] = plot end channel.CacheTable = eventChannel.CacheTable channel.EventChannel = eventChannel if not BaseDetector.hookChannel(channel) then return false end if not BaseDetector.isCurrent(channel) or channel.EventChannel ~= eventChannel then return false end BaseDetector.created[plot.Name] = eventChannel if BaseDetector.pending[plot.Name] == plot then BaseDetector.pending[plot.Name] = nil end scanDebug("base channel processed", plot.Name) return true end function BaseDetector.schedulePlot(plot) if not plot or plot.Parent ~= BaseDetector.plotsRoot or BaseDetector.retryTokens[plot] then return end BaseDetector.registerPlot(plot) local token = {} BaseDetector.retryTokens[plot] = token task.spawn(function() local deadline = os.clock() + ScanConfig.ModuleWaitSeconds repeat BaseDetector.scheduleLifecycle() if BaseDetector.tryResolve(plot, BaseDetector.created[plot.Name]) then BaseDetector.retryTokens[plot] = nil return end task.wait(0.1) until BaseDetector.retryTokens[plot] ~= token or plot.Parent ~= BaseDetector.plotsRoot or BaseDetector.patchFailed or os.clock() >= deadline if BaseDetector.retryTokens[plot] == token then BaseDetector.retryTokens[plot] = nil if plot.Parent == BaseDetector.plotsRoot and not BaseDetector.patchFailed and not BaseDetector.warnedUnresolved then BaseDetector.warnedUnresolved = true warn("[SCAN] exact plot channel unresolved after bounded retry") end end end) end function BaseDetector.removePlot(plot) if not plot then return end BaseDetector.retryTokens[plot] = nil local record = BaseDetector.resolved[plot.Name] if BaseDetector.pending[plot.Name] == plot then BaseDetector.pending[plot.Name] = nil end if not record or record.plot ~= plot then return end if BaseDetector.created[plot.Name] == record.channel.EventChannel then BaseDetector.created[plot.Name] = nil end BaseDetector.clearChannel(record.channel) BaseDetector.resolved[plot.Name] = nil end function BaseDetector.hookPlots(root) if BaseDetector.plotsRoot == root then BaseDetector.scheduleLifecycle() return end BaseDetector.disconnect(BaseDetector.plotsConnection) BaseDetector.disconnect(BaseDetector.plotsRemovedConnection) for channel in pairs(BaseDetector.channels) do BaseDetector.clearChannel(channel) end BaseDetector.plotsConnection = nil BaseDetector.plotsRemovedConnection = nil BaseDetector.plotsRoot = root BaseDetector.channels = setmetatable({}, { __mode = "k" }) BaseDetector.hooked = setmetatable({}, { __mode = "k" }) BaseDetector.lastSignature = setmetatable({}, { __mode = "k" }) BaseDetector.lastBest = setmetatable({}, { __mode = "k" }) BaseDetector.deliveryRetry = setmetatable({}, { __mode = "k" }) BaseDetector.resolved = {} BaseDetector.pending = {} BaseDetector.retryTokens = setmetatable({}, { __mode = "k" }) BaseDetector.warnedUnresolved = false if not root then return end BaseDetector.plotsConnection = root.ChildAdded:Connect(function(plot) BaseDetector.schedulePlot(plot) end) BaseDetector.plotsRemovedConnection = root.ChildRemoved:Connect(BaseDetector.removePlot) for _, plot in ipairs(root:GetChildren()) do BaseDetector.schedulePlot(plot) end BaseDetector.scheduleLifecycle() end function BaseDetector.startLifecycleFallback() if BaseDetector.lifecycleFallbackActive or BaseDetector.patchReady or BaseDetector.patchFailed then return end BaseDetector.lifecycleFallbackActive = true local root = BaseDetector.plotsRoot if not root then return end for _, plot in ipairs(root:GetChildren()) do BaseDetector.retryTokens[plot] = nil BaseDetector.schedulePlot(plot) end end function BaseDetector.stopLifecycleFallback() if not BaseDetector.lifecycleFallbackActive then return end BaseDetector.lifecycleFallbackActive = false BaseDetector.retryTokens = setmetatable({}, { __mode = "k" }) for channel in pairs(BaseDetector.channels) do BaseDetector.clearChannel(channel) end BaseDetector.channels = setmetatable({}, { __mode = "k" }) BaseDetector.hooked = setmetatable({}, { __mode = "k" }) BaseDetector.pendingDispatch = setmetatable({}, { __mode = "k" }) BaseDetector.uuidSeen = setmetatable({}, { __mode = "k" }) BaseDetector.deliveryRetry = setmetatable({}, { __mode = "k" }) BaseDetector.lastSignature = setmetatable({}, { __mode = "k" }) BaseDetector.lastBest = setmetatable({}, { __mode = "k" }) BaseDetector.resolved = {} BaseDetector.pending = {} BaseDetector.warnedUnresolved = false end function BaseDetector.openPatchGate() if BaseDetector.patchReady or BaseDetector.patchFailed then return end BaseDetector.patchReady = true local root = BaseDetector.plotsRoot if not root then return end for _, plot in ipairs(root:GetChildren()) do BaseDetector.retryTokens[plot] = nil BaseDetector.schedulePlot(plot) end end function BaseDetector.enumerateExistingChannels() if BaseDetector.enumerationAttempted or BaseDetector.enumerationRunning then return end local root = BaseDetector.plotsRoot if not root or not game:IsLoaded() then return end BaseDetector.enumerationDeadline = BaseDetector.enumerationDeadline or (os.clock() + ScanConfig.ModuleWaitSeconds) local synchronizer = BaseDetector.Synchronizer if not synchronizer and os.clock() < BaseDetector.enumerationDeadline then synchronizer = BaseDetector.getSynchronizer() end if not synchronizer or type(synchronizer.Get) ~= "function" then if os.clock() >= BaseDetector.enumerationDeadline then BaseDetector.enumerationAttempted = true BaseDetector.startLifecycleFallback() BaseDetector.openPatchGate() warn("[SCAN] patchRelate readiness timed out; opening lifecycle fallback") end return end if not BaseDetector.connectLifecycle() then if os.clock() >= BaseDetector.enumerationDeadline then BaseDetector.enumerationAttempted = true BaseDetector.patchFailed = true BaseDetector.disconnectLifecycle() warn("[SCAN] Synchronizer lifecycle readiness timed out; intake disabled") end return end local token = {} BaseDetector.enumerationToken = token BaseDetector.enumerationRunning = true BaseDetector.enumerationAttempted = true task.spawn(function() local restore local attempt = 0 local retryDeadline = os.clock() + BaseDetector.patchRetrySeconds repeat attempt = attempt + 1 local currentSynchronizer = BaseDetector.getSynchronizer() or synchronizer if type(currentSynchronizer) == "table" and type(currentSynchronizer.Get) == "function" then synchronizer = currentSynchronizer restore = BaseDetector.patchRelateChannels(synchronizer) else restore = nil end if type(restore) ~= "function" and not BaseDetector.patchRestoreFailed then BaseDetector.startLifecycleFallback() if os.clock() < retryDeadline then task.wait(BaseDetector.patchRetryDelay) end end until type(restore) == "function" or BaseDetector.patchRestoreFailed or os.clock() >= retryDeadline if type(restore) ~= "function" then BaseDetector.enumerationRunning = false BaseDetector.enumerationToken = nil if BaseDetector.patchRestoreFailed then BaseDetector.patchFailed = true BaseDetector.disconnectLifecycle() warn("[SCAN] RelateChannels rollback failed; Synchronizer intake disabled") else BaseDetector.openPatchGate() BaseDetector.reportPatchFailure(attempt) warn("[SCAN] RelateChannels patch unavailable after 3 minutes; opening lifecycle fallback") end return end BaseDetector.patchApplied = true BaseDetector.patchAttempt = attempt getgenv().__SAB_PatchStatus = "OK #" .. tostring(attempt) print( ("[SCAN] RelateChannels patch success on attempt %d; acquiring current channels with Get"):format(attempt) ) BaseDetector.stopLifecycleFallback() local callDone, callOk, callRestoreFailed, channels = false, true, false, {} local callThread = task.spawn(function() for _, plot in ipairs(root:GetChildren()) do local index = plot.Name if BaseDetector.plotsRoot ~= root or plot.Parent ~= root or root:FindFirstChild(index) ~= plot then callOk = false break end local getOk, eventChannel for getAttempt = 1, BaseDetector.getMaxAttempts do getOk, eventChannel = pcall(synchronizer.Get, synchronizer, index) if not getOk or type(eventChannel) == "table" then break end if getAttempt < BaseDetector.getMaxAttempts then warn( ("[SCAN] Get channel not ready; restoring and retrying (%d/%d)"):format( getAttempt + 1, BaseDetector.getMaxAttempts ) ) local retryRestoreOk, retryRestored = pcall(restore) if not retryRestoreOk or retryRestored ~= true then callRestoreFailed = true callOk = false break end task.wait(BaseDetector.getRetryDelay) local nextRestore = BaseDetector.patchRelateChannels(synchronizer) if type(nextRestore) ~= "function" then callOk = false break end restore = nextRestore end end if not getOk then warn("[SCAN] Get failed") callOk = false break end if not callOk then break end local invalidReason if BaseDetector.plotsRoot ~= root or plot.Parent ~= root or plot.Name ~= index or root:FindFirstChild(index) ~= plot then invalidReason = "plot changed" elseif type(eventChannel) ~= "table" then invalidReason = "channel is not ready" elseif BaseDetector.destroyed[eventChannel] then invalidReason = "channel was destroyed" elseif tostring(eventChannel.Index) ~= index then invalidReason = "channel index mismatch" end if invalidReason then warn("[SCAN] Get returned an invalid channel: " .. invalidReason) callOk = false break end channels[index] = { plot = plot, eventChannel = eventChannel } end callDone = true end) local callDeadline = os.clock() + ScanConfig.ModuleWaitSeconds while BaseDetector.enumerationToken == token and not callDone and os.clock() < callDeadline do task.wait(0.05) end local cancelOk = true if not callDone then cancelOk = pcall(task.cancel, callThread) end local restoreOk, restored = pcall(restore) BaseDetector.enumerationRunning = false BaseDetector.enumerationToken = nil if callRestoreFailed or not cancelOk or not restoreOk or restored ~= true then BaseDetector.patchRestoreFailed = true BaseDetector.patchBusy = true BaseDetector.patchFailed = true BaseDetector.disconnectLifecycle() warn("[SCAN] RelateChannels restore/cancel failed; Synchronizer intake disabled") return end if not callDone then BaseDetector.startLifecycleFallback() BaseDetector.openPatchGate() warn("[SCAN] Get batch timed out after restore; opening lifecycle fallback") return end if not callOk then BaseDetector.startLifecycleFallback() BaseDetector.openPatchGate() warn("[SCAN] Get batch failed after restore; opening lifecycle fallback") return end local pendingChannels = {} for index, record in pairs(channels) do local plot = record.plot local eventChannel = record.eventChannel if BaseDetector.plotsRoot == root and plot.Parent == root and plot.Name == index and root:FindFirstChild(index) == plot and not BaseDetector.destroyed[eventChannel] and tostring(eventChannel.Index) == index then pendingChannels[index] = record BaseDetector.created[index] = eventChannel end end BaseDetector.openPatchGate() local hooked = 0 for index, record in pairs(pendingChannels) do local plot = record.plot local eventChannel = record.eventChannel if BaseDetector.plotsRoot == root and plot.Parent == root and plot.Name == index and root:FindFirstChild(index) == plot and not BaseDetector.destroyed[eventChannel] then if BaseDetector.tryResolve(plot, eventChannel) then hooked = hooked + 1 end end end scanDebug("Get batch", hooked, "plot channels hooked") end) end function BaseDetector.disconnectLifecycle() BaseDetector.disconnect(BaseDetector.channelCreatedConnection) BaseDetector.disconnect(BaseDetector.channelDestroyedConnection) BaseDetector.channelCreatedConnection = nil BaseDetector.channelDestroyedConnection = nil end function BaseDetector.connectLifecycle() if BaseDetector.patchFailed then return false end local synchronizer = BaseDetector.getSynchronizer() if not synchronizer then return false end if not BaseDetector.isConnected(BaseDetector.channelCreatedConnection) then BaseDetector.disconnect(BaseDetector.channelCreatedConnection) BaseDetector.channelCreatedConnection = nil end if not BaseDetector.isConnected(BaseDetector.channelDestroyedConnection) then BaseDetector.disconnect(BaseDetector.channelDestroyedConnection) BaseDetector.channelDestroyedConnection = nil end if not BaseDetector.channelCreatedConnection and synchronizer.OnChannelCreated then local ok, connection = pcall(function() return synchronizer.OnChannelCreated:Connect(function(channel) if type(channel) ~= "table" or channel.Index == nil then return end local index = tostring(channel.Index) BaseDetector.destroyed[channel] = nil BaseDetector.created[index] = channel local root = BaseDetector.plotsRoot local plot = root and root:FindFirstChild(index) if plot and not BaseDetector.tryResolve(plot, channel) then BaseDetector.schedulePlot(plot) end end) end) if ok and type(connection) == "table" and type(connection.Disconnect) == "function" then BaseDetector.channelCreatedConnection = connection end end if not BaseDetector.channelDestroyedConnection and synchronizer.OnChannelDestroyed then local ok, connection = pcall(function() return synchronizer.OnChannelDestroyed:Connect(function(channel) if type(channel) ~= "table" or channel.Index == nil then return end local index = tostring(channel.Index) BaseDetector.destroyed[channel] = true if BaseDetector.created[index] == channel then BaseDetector.created[index] = nil end local record = BaseDetector.resolved[index] if record and record.channel.EventChannel == channel then BaseDetector.clearChannel(record.channel) BaseDetector.resolved[index] = nil BaseDetector.schedulePlot(record.plot) end end) end) if ok and type(connection) == "table" and type(connection.Disconnect) == "function" then BaseDetector.channelDestroyedConnection = connection end end local ready = BaseDetector.isConnected(BaseDetector.channelCreatedConnection) and BaseDetector.isConnected(BaseDetector.channelDestroyedConnection) if ready then BaseDetector.warnedLifecycle = false elseif not BaseDetector.warnedLifecycle then BaseDetector.warnedLifecycle = true warn("[SCAN] Synchronizer lifecycle unavailable; bounded targeted retry active") end return ready end function BaseDetector.startEarlyLifecycle() if BaseDetector.earlyLifecycleToken or BaseDetector.patchFailed then return end local token = {} BaseDetector.earlyLifecycleToken = token task.spawn(function() local deadline = os.clock() + ScanConfig.ModuleWaitSeconds repeat if BaseDetector.connectLifecycle() then break end task.wait(math.min(0.1, math.max(0, deadline - os.clock()))) until BaseDetector.earlyLifecycleToken ~= token or BaseDetector.patchFailed or os.clock() >= deadline if BaseDetector.earlyLifecycleToken == token then BaseDetector.earlyLifecycleToken = nil end end) end function BaseDetector.scheduleLifecycle() BaseDetector.enumerateExistingChannels() if BaseDetector.patchFailed then return false end local lifecycleReady = BaseDetector.connectLifecycle() if lifecycleReady then return true end if BaseDetector.lifecycleToken or not BaseDetector.plotsRoot then return false end local token = {} BaseDetector.lifecycleToken = token task.spawn(function() local deadline = (not BaseDetector.enumerationAttempted and BaseDetector.enumerationDeadline) or (os.clock() + ScanConfig.ModuleWaitSeconds) repeat BaseDetector.enumerateExistingChannels() if BaseDetector.patchFailed then BaseDetector.lifecycleToken = nil return end lifecycleReady = BaseDetector.connectLifecycle() if lifecycleReady then BaseDetector.lifecycleToken = nil return end task.wait(0.1) until BaseDetector.lifecycleToken ~= token or not BaseDetector.plotsRoot or os.clock() >= deadline if BaseDetector.lifecycleToken == token then BaseDetector.enumerateExistingChannels() lifecycleReady = BaseDetector.connectLifecycle() BaseDetector.lifecycleToken = nil if BaseDetector.patchReady and not lifecycleReady then warn("[SCAN] Synchronizer lifecycle unresolved after bounded retry") end end end) return false end function BaseDetector.refresh() local root = Workspace:FindFirstChild("Plots") if root ~= BaseDetector.plotsRoot then BaseDetector.hookPlots(root) else BaseDetector.scheduleLifecycle() end for _, plot in pairs(BaseDetector.pending) do if plot.Parent == BaseDetector.plotsRoot then BaseDetector.schedulePlot(plot) end end end function BaseDetector.start() BaseDetector.workspaceConnection = Workspace.ChildAdded:Connect(function(child) if child.Name == "Plots" then BaseDetector.hookPlots(child) end end) BaseDetector.workspaceRemovedConnection = Workspace.ChildRemoved:Connect(function(child) if child == BaseDetector.plotsRoot then BaseDetector.hookPlots(nil) end end) BaseDetector.startEarlyLifecycle() task.spawn(function() local deadline = os.clock() + ScanConfig.PlotsWaitSeconds repeat local root = Workspace:FindFirstChild("Plots") if root then task.wait(ScanConfig.JoinSettleSeconds) BaseDetector.hookPlots(root) return end task.wait(0.03) until os.clock() >= deadline end) end local function hasFriendClone() for _, p in ipairs(Players:GetPlayers()) do if p.UserId ~= LocalPlayer.UserId and (registrySet[p.Name:lower()] or registrySet[(p.DisplayName or ""):lower()]) then return true, p.Name end end return false end -- ── DEADLOCK-BREAKER standoff: liệt kê fleet-clone đang chung sv + RANK của mình theo UserId ── -- rank = số clone fleet có UserId NHỎ HƠN mình (acc UserId nhỏ nhất = rank 0 = grace ngắn nhất = hop TRƯỚC). -- Acc hop trước rời đi → các acc còn lại thấy clone biến mất sẽ RESET đồng hồ → chỉ 1 acc rời mỗi đợt, -- tự hội tụ về đúng 1 fleet/sv. Robust cả khi acc "kẹt" là acc GET-fail (acc GET-ok rank thấp hơn sẽ hop thay). local function fleetCloneStandoff() local cloneName, count, rank = nil, 0, 0 for _, p in ipairs(Players:GetPlayers()) do if p.UserId ~= LocalPlayer.UserId and (registrySet[p.Name:lower()] or registrySet[(p.DisplayName or ""):lower()]) then count = count + 1 cloneName = cloneName or p.Name if p.UserId < LocalPlayer.UserId then rank = rank + 1 end end end if count == 0 then return false end return true, rank, cloneName, count end -- (parseCash / readPlayerCash / allOthersPoor ĐÃ XOÁ — HopAllOthersPoor bỏ theo yêu cầu) -- ── Hop executor + monitor ── local hopping = false local function tryHop(reason) if hopping or not CONFIG.Hop.Enabled then return end -- ★ PEAK/OG → _killLeave() (ĐÓNG SERVER LUÔN). Set bởi dispatchGroup (_hopKick). Kick = FALLBACK nếu Shutdown ko hiệu lực → bot vẫn rời, KHÔNG kẹt. if _hopKick then hopping = true getgenv().__SAB_Hops = (getgenv().__SAB_Hops or 0) + 1 LOG("SHUTDOWN game (peak/og) vì:", reason) pcall(function() _killLeave() end) -- fallback: Shutdown ko ăn → vẫn rời server (tránh kẹt hopping=true) safeTaskWait(3) -- ★ chờ 3s cho kick "ăn" rồi shutdown pcall(function() _killLeave() end) return end -- ★ no-server cooldown: chưa tới giờ thử lại → KHÔNG làm gì (ko request, ko tăng hop, ko kick). if os.clock() < _noSvNextTry then return end hopping = true getgenv().__SAB_Hops = (getgenv().__SAB_Hops or 0) + 1 LOG("HOP vì:", reason) local ok, why if _hopRejoin then -- ★ HIGH/PEAK/OG → REJOIN bằng Teleport(PlaceId): rời sv NGAY, Roblox matchmaking tự đưa vào sv mới (ko tìm jobId). -- ★ FIX (deadlock): Teleport fail BẤT ĐỒNG BỘ (TeleportInitFailed: sv full/rate-limit 769/770) → pcall VẪN trả true -- dù chưa đi đâu. Nếu coi true = thành công + return thì `hopping` kẹt true mãi → KHÔNG BAO GIỜ hop lại được. -- → mirror hop(): bắn teleport rồi CHỜ teleportFailed; chỉ ok=true khi thật sự đi được, fail thì rớt xuống logic kick. teleportFailed = false pcall(function() TeleportService:Teleport(CONFIG.PlaceId or game.PlaceId, LocalPlayer) end) local t0 = os.clock() while os.clock() - t0 < (CONFIG.Hop.WaitPerTry or 6) do if teleportFailed then break end safeTaskWait(0.25) end ok = not teleportFailed why = ok and nil or "rejoin-fail" else ok, why = hop() -- ok=true → đang teleport (script sẽ chết) end if ok then return -- hop đã bắn teleport → VM sẽ chết. ⚠️ ĐÃ BỎ detect false-success: teleport CÂM (captcha) sẽ KHÔNG tự kick. end if why == "noserver" then hopping = false return -- chờ có chủ đích → vòng monitor sau thử lại end -- hop FAIL (sv full/kẹt) → nghỉ rồi vòng monitor thử lại. ⚠️ ĐÃ BỎ KickAfterFailSeconds: kẹt lâu sẽ KHÔNG tự kick. safeTaskWait(3) hopping = false end -- ★ đọc tiền 1 player (leaderstats Cash/Money/Coins → attribute). nil = KO đọc được → coi KHÔNG nghèo (ko hop nhầm). -- (KHÔNG dùng Synchronizer ở đây vì getSync/_get định nghĩa SAU hàm này — forward-ref sẽ thành global nil.) local function playerCash(plr) local ls = plr:FindFirstChild("leaderstats") if ls then for _, nm in ipairs({ "Cash", "Money", "Coins", "Cash$", "$" }) do local v = ls:FindFirstChild(nm) if v and tonumber(v.Value) then return tonumber(v.Value) end end end for _, nm in ipairs({ "Cash", "Money", "Coins" }) do local a = plr:GetAttribute(nm) if tonumber(a) then return tonumber(a) end end return nil end local function startServerHopMonitor() local countSince = nil local lastPlayerCount = 0 local nextSoloCheckAt = os.clock() + (CONFIG.Hop.SoloCheckInterval or 300) local _lastJoinAt = os.clock() -- ★ HopNoJoin: mốc JOIN gần nhất (bot vào + mỗi player MỚI) → đếm N phút ko ai vào pcall(function() Players.PlayerAdded:Connect(function() _lastJoinAt = os.clock() end) end) -- ai đó join → reset đồng hồ task.spawn(function() while true do local n = #Players:GetPlayers() local currentTime = os.clock() -- [1] Nếu bật, mỗi 5 phút mới kiểm tra server có chỉ còn bot hay không. if CONFIG.Hop.Enabled and CONFIG.Hop.HopWhenSolo and currentTime >= nextSoloCheckAt then nextSoloCheckAt = currentTime + (CONFIG.Hop.SoloCheckInterval or 300) if not pendingHopReason and n == 1 then pendingHopReason = "Server chỉ còn 1 player -> hop" _hopRejoin = false _hopKick = false end end -- [3] HOP THEO SỐ NGƯỜI: 8 người liên tục FullSeconds (HopFull8). (HopSeven 7-người ĐÃ XOÁ theo yêu cầu.) -- if not pendingHopReason then -- if n == 8 then -- if lastPlayerCount == 8 then -- countSince = countSince or currentTime -- if CONFIG.Hop.HopFull8 and (currentTime - countSince) >= (CONFIG.Hop.FullSeconds or 300) then -- [3] Đủ FullSeconds (5') -- pendingHopReason = "Server 8 người liên tục 5 phút -> hop" -- _hopRejoin = false -- _hopKick = false -- end -- else -- lastPlayerCount = 8 -- countSince = currentTime -- end -- else -- lastPlayerCount = n -- số người ≠ 8 → reset bộ đếm -- countSince = nil -- end -- end -- [3] (gated, mặc định OFF) HOP NGAY: server > PoorMinPlayers người mà MỌI người KHÁC đọc được tiền đều nghèo ( (CONFIG.Hop.PoorMinPlayers or 6) then local others, read, rich = 0, 0, false for _, p in ipairs(Players:GetPlayers()) do if p ~= LocalPlayer then others = others + 1 local c = playerCash(p) if c ~= nil then read = read + 1 if c >= (CONFIG.Hop.PoorCashThreshold or 10000) then rich = true break end end end end -- chỉ hop khi ĐỌC ĐƯỢC ≥ nửa số người khác VÀ KHÔNG ai giàu → đủ tự tin server nghèo thật (đọc ko ra = ko hop) if (not rich) and read > 0 and read >= math.ceil(others / 2) then pendingHopReason = ("Server %d nguoi deu ngheo (<%d) -> hop"):format( n, CONFIG.Hop.PoorCashThreshold or 10000 ) _hopRejoin = false _hopKick = false end end -- [4] (HopNoJoin) server ≥ NoJoinMinPlayers người mà NoJoinMinutes phút KHÔNG ai JOIN mới → matchmaking đứng (ko nạn nhân mới) → hop if not pendingHopReason and CONFIG.Hop.HopNoJoin and n >= (CONFIG.Hop.NoJoinMinPlayers or 7) and (currentTime - _lastJoinAt) >= (CONFIG.Hop.NoJoinMinutes or 30) * 60 then pendingHopReason = ("Server %d nguoi, %d phut ko ai join -> hop"):format( n, CONFIG.Hop.NoJoinMinutes or 30 ) _hopRejoin = false _hopKick = false end -- GỌI LỆNH HOP (đã bỏ watchdog KickAfterFailSeconds + block [5] NoJoinHopMinutes — hop fail thì tryHop tự thử lại mỗi vòng monitor) if pendingHopReason then tryHop(pendingHopReason) end -- NGỦ 5 GIÂY ĐỂ TRÁNH LAG CPU safeTaskWait(CONFIG.Hop.MonitorInterval or 5) end end) end -- ── UI ĐEN (uptime, placeId, jobId, players, stats) ── local function buildUI() local CoreGui = (gethui and gethui()) or cloneref(game:GetService("CoreGui")) pcall(function() local o = CoreGui:FindFirstChild("SAB_Scanner_UI") if o then o:Destroy() end end) local function corner(p, r) local c = Instance.new("UICorner") c.CornerRadius = UDim.new(0, r or 6) c.Parent = p end local gui = Instance.new("ScreenGui") gui.Name = "SAB_Scanner_UI" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling gui.DisplayOrder = 9999 pcall(function() if syn and syn.protect_gui then syn.protect_gui(gui) end end) gui.Parent = CoreGui local main = Instance.new("Frame") main.Size = UDim2.fromOffset(268, 270) main.Position = UDim2.new(0, 14, 0, 70) main.BackgroundColor3 = Color3.fromRGB(10, 10, 12) main.BorderSizePixel = 0 main.Active = true main.Parent = gui corner(main, 8) local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(45, 48, 58) stroke.Thickness = 1 stroke.Parent = main local bar = Instance.new("Frame") bar.Size = UDim2.new(1, 0, 0, 30) bar.BackgroundColor3 = Color3.fromRGB(16, 16, 20) bar.BorderSizePixel = 0 bar.Parent = main corner(bar, 8) local title = Instance.new("TextLabel") title.BackgroundTransparency = 1 title.Position = UDim2.fromOffset(10, 0) title.Size = UDim2.new(1, -44, 1, 0) title.Font = Enum.Font.GothamBold title.Text = "🛰 SAB SCANNER" title.TextColor3 = Color3.fromRGB(0, 230, 140) title.TextSize = 13 title.TextXAlignment = Enum.TextXAlignment.Left title.Parent = bar local closeBtn = Instance.new("TextButton") closeBtn.Size = UDim2.fromOffset(24, 22) closeBtn.Position = UDim2.new(1, -28, 0.5, -11) closeBtn.BackgroundColor3 = Color3.fromRGB(150, 50, 50) closeBtn.Text = "✕" closeBtn.Font = Enum.Font.GothamBold closeBtn.TextColor3 = Color3.fromRGB(240, 240, 240) closeBtn.TextSize = 12 closeBtn.BorderSizePixel = 0 closeBtn.Parent = bar corner(closeBtn, 5) local body = Instance.new("TextLabel") body.Position = UDim2.fromOffset(12, 38) body.Size = UDim2.new(1, -20, 1, -46) body.BackgroundTransparency = 1 body.Font = Enum.Font.Code body.TextSize = 12.5 body.TextColor3 = Color3.fromRGB(220, 224, 232) body.TextXAlignment = Enum.TextXAlignment.Left body.TextYAlignment = Enum.TextYAlignment.Top body.RichText = true body.Text = "..." body.Parent = main closeBtn.MouseButton1Click:Connect(function() gui:Destroy() end) -- drag local UIS = cloneref(game:GetService("UserInputService")) local drag, dStart, sPos bar.InputBegan:Connect(function(i) if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then drag = true dStart = i.Position sPos = main.Position i.Changed:Connect(function() if i.UserInputState == Enum.UserInputState.End then drag = false end end) end end) UIS.InputChanged:Connect(function(i) if drag and (i.UserInputType == Enum.UserInputType.MouseMovement or i.UserInputType == Enum.UserInputType.Touch) then local d = i.Position - dStart main.Position = UDim2.new(sPos.X.Scale, sPos.X.Offset + d.X, sPos.Y.Scale, sPos.Y.Offset + d.Y) end end) local function gray(s) return ('%s'):format(s) end local function val(s, c) return ('%s'):format(c or "e6eaf0", tostring(s)) end local placeName = (game.PlaceId == (CONFIG.NewPlayersPlaceId or 96342491571673)) and "New Players" or "Main" task.spawn(function() while gui.Parent do local up = sabUptime() local hh = math.floor(up / 3600) local mm = math.floor((up % 3600) / 60) local ss = up % 60 local jid = tostring(game.JobId) local txt = table.concat({ gray("⏱ Uptime ") .. val(("%02d:%02d:%02d"):format(hh, mm, ss), "00e68c"), gray("📍 Place ") .. val(placeName .. " (" .. tostring(game.PlaceId) .. ")"), gray("🌐 Job ") .. val(jid:sub(1, 18) .. "…"), gray("👥 Players") .. " " .. val(#Players:GetPlayers() .. "/" .. tostring(Players.MaxPlayers)), "", gray("🔍 Pets seen ") .. val(STATS.seen, "ffd24d"), gray("📤 Sent ") .. val(STATS.sent, "ffd24d"), gray("🪝 WH ok ") .. val(STATS.whOk or 0, "00e68c") .. gray(" 429 ") .. val( STATS.wh429 or 0, (STATS.wh429 or 0) > 0 and "ffb02a" or "8a8f99" ) .. gray(" drop ") .. val(STATS.whFail or 0, (STATS.whFail or 0) > 0 and "ff6b6b" or "8a8f99"), gray("🦘 Hops ") .. val(getgenv().__SAB_Hops or 0, "ffd24d"), gray("📡 Hub ") .. val(HUB.Enabled and "ON" or "off", HUB.Enabled and "00e68c" or "8a8f99") .. gray( " 👤 Acc " ) .. val(LocalPlayer.Name), (function() local cs = (Cdev.state and Cdev.state()) or {} if not CDEV.Enabled then return gray("🛰 POST ") .. val("off", "8a8f99") end local on = cs.connected local line = gray("🛰 POST ") .. val(on and "OK" or "...", on and "00e68c" or "ff6b6b") .. gray(" 📤 ") .. val(cs.sent or 0, "ffd24d") if cs.lastAt and cs.lastAt > 0 then line = line .. gray(" (" .. (os.time() - cs.lastAt) .. "s)") end if cs.err and cs.err ~= "" then line = line .. "\n" .. gray(" ↳ " .. cs.err) end return line end)(), }, "\n") body.Text = txt safeTaskWait(2) -- ★ TỐI ƯU CPU: refresh stats 1s→2s. Build chuỗi (nhiều string.format+concat+IIFE) chạy liên tục -- = alloc đều đặn góp GC pressure; 2s vẫn realtime với mắt (uptime/stats), giảm nửa alloc UI. Cosmetic, ko đụng scan/POST. end end) LOG("UI panel loaded.") end -- ════════════════════════════════════════════════════════════════════════════ getgenv().isloaded = true -- ── MAIN: init patched base scanner → monitor hop/presence/guards ── task.spawn(function() -- ★ CONNECT WS NGAY TỪ ĐẦU — mở socket local SONG SONG lúc game đang load (KHÔNG chờ game:IsLoaded, KHÔNG chờ find/data). -- Retry mỗi 2s chỉ khi mất kết nối; heartbeat 30s phát hiện socket chết. (Transport="http" → no-op.) if CDEV.Enabled then pcall(Cdev.connect) end if CONFIG.UseSynchronizer then pcall(BaseDetector.startEarlyLifecycle) end repeat safeTaskWait() until game:IsLoaded() -- Chờ live Workspace.Plots tối đa 3s rồi settle ngắn; BaseDetector vẫn có bounded root/module readiness riêng. do local _t0 = os.clock() while not Workspace:FindFirstChild("Plots") and (os.clock() - _t0) < 3 do safeTaskWait(0.03) end end -- ★ poll 0.03s → bắt Plots sớm hơn ~tens ms safeTaskWait(CONFIG.JoinSettle or 0.3) -- settle ngắn cho lứa pet đầu replicate (events/rescan lo phần còn lại) if CONFIG.ShowUI then task.spawn(function() pcall(buildUI) end) end -- ★ UI nền (ko chặn scan/POST) LOG( ("started (event-driven) — tier-min low=%s mid=%s peak/og=0, hop=%s"):format( fmt(CONFIG.TierMinSend.LOW), fmt(CONFIG.TierMinSend.MID), tostring(CONFIG.Hop.Enabled) ) ) -- ★★ TRACKER PLAYER-SEEN (chạy CẢ 2 config — trước đây bị kẹt trong connectBaseEvents chỉ chạy khi UseSynchronizer=false): -- mốc "scanner LẦN ĐẦU thấy player này" → log [SENT] đo detect-player→send cho MỌI owner. -- Người có sẵn lúc scanner vào = mốc _svJoinAt; người join sau = mốc PlayerAdded (+_joinedLive để phân biệt). pcall(function() for _, p in ipairs(Players:GetPlayers()) do if p.UserId ~= LocalPlayer.UserId then _joinPostT[p.Name:lower()] = _svJoinAt end -- có sẵn → mốc = scanner vào sv end Players.PlayerAdded:Connect(function(p) if p.UserId ~= LocalPlayer.UserId then local n = p.Name:lower() _joinPostT[n] = os.clock() _joinedLive[n] = true end -- join sau → mốc = lúc join end) Players.PlayerRemoving:Connect(function(p) local n = p.Name:lower() _joinPostT[n] = nil _joinedLive[n] = nil end) end) -- ★ Nếu đang ở place "[New Players]" (toàn nhà thấp) → hop sang Main NGAY, không scan ở đây -- if game.PlaceId == (CONFIG.NewPlayersPlaceId or 96342491571673) then -- WARN(("[SAB] đang ở [New Players] place → hop sang Main (%d)"):format(CONFIG.PlaceId or 109983668079237)) -- for _ = 1, 6 do -- pcall(function() TeleportService:Teleport(CONFIG.PlaceId or 109983668079237, LocalPlayer) end) -- safeTaskWait(8) -- teleport OK → script chết; bị bounce lại → thử tiếp -- end -- WARN("[SAB] không thoát được [New Players] (acc bị gate?) → scan tạm tại đây") -- end -- ★ MỖI bước init bọc pcall RIÊNG: 1 bước lỗi KHÔNG làm chết các bước sau (init luôn chạy hết → -- scanner thực sự lên dù 1 phần lỗi → cờ isloaded set sớm vẫn ĐÚNG, ko cần đụng timing loader). -- ★★ ƯU TIÊN TỐC ĐỘ POST: scan + cdev POST lên SỚM NHẤT. KHÔNG để init mạng đồng bộ chặn first-POST. -- ★ startSender/sendQueue BỎ — dispatchGroup giờ gọi INLINE ngay tại detect (cdev POST cùng frame). pendingHopReason guard ở đầu dispatchGroup THAY cho queue-flush (chống spam webhook khi hop kẹt). -- (Cdev.connect ĐÃ gọi một lần ở ĐẦU init phía trên để mở socket song song lúc game load.) onDataReady = function() if CONFIG.UseSynchronizer then task.delay(5, function() BaseDetector.refresh() end) end end Data.start() if CONFIG.UseSynchronizer then task.delay(5, function() pcall(BaseDetector.start) end) else LOG("base Synchronizer scanner: disabled") end if CONFIG.DetectConveyor == "always" then task.delay(5, function() Conveyor.allowed = true pcall(Conveyor.start) end) elseif CONFIG.DetectConveyor == "event" then task.delay(5, function() pcall(Conveyor.startWhenBee) end) elseif CONFIG.DetectConveyor ~= "off" then WARN("[SCAN] DetectConveyor must be always, event, or off; conveyor disabled") end pcall(function() Players.PlayerAdded:Connect(function() if CONFIG.BoostOnPlayerJoin then ScanFps.boost(CONFIG.BoostOnJoinSec) end end) end) pcall(startServerHopMonitor) -- (4) hop monitor pcall(startPresenceHeartbeat) -- presence (tự task.spawn bên trong → ko chặn) -- ★ BACKGROUND — refreshRegistry dùng game:HttpGet ĐỒNG BỘ, host chậm/treo có thể block ~55s (đây là nguyên nhân -- "55s mới send"). Đẩy xuống nền + friend-clone-hop để KHÔNG chặn fullScan/POST. Bot scan/POST trước, rồi mới -- xét hop-vì-clone (vài find sớm trước khi hop là chấp nhận được, đúng yêu cầu "POST nhanh nhất"). task.spawn(function() local fr = CONFIG.FriendRegistry or {} pcall(refreshRegistry) -- GET /api/usernames (có retry) — NỀN -- [JOIN] clone đã ở sẵn + mình tới SAU → hop NGAY (đường nhanh, giữ nguyên hành vi cũ) if CONFIG.Hop.HopOnFriendClone then pcall(function() local fc, who = hasFriendClone() if fc then LOG("JOIN thấy fleet clone (" .. tostring(who) .. ") → hop") tryHop("join gặp fleet clone: " .. tostring(who)) else LOG("JOIN không có fleet clone → ở lại scan") end end) end -- [ĐỊNH KỲ] refresh registry (recover GET-fail lúc join + bắt clone vào sau) + DEADLOCK-BREAKER. -- Xử lý đúng case của anh: A vào trước, B vào sau mà B ko hop (B GET-fail) → sau grace A (hoặc acc rank thấp) hop phá kẹt. local refreshEvery = fr.RefreshInterval or 0 local grace = fr.GraceSeconds or 180 -- ★ stagger PHẢI > thời gian hop TỐI ĐA khi CÓ server (~MaxRetries×(WaitPerTry+RetryBackoff) ≈ 90-100s): acc rank-trước -- hop xong (rời sv) TRƯỚC khi acc rank-sau tới hạn → acc sau thấy clone biến mất → reset → ko double-hop HẠI. -- (No-server thì cả 2 acc đều kẹt → ko double-hop hại; nên ko cần > KickAfterFailSeconds 700.) local stagger = fr.StaggerSeconds or 120 local jitter = (LocalPlayer.UserId or 0) % 11 -- de-sync 2 acc CÙNG rank (registry lệch tạm thời) — tránh hop cùng tick if CONFIG.Hop.HopOnFriendClone and (refreshEvery > 0 or grace > 0) then task.spawn(function() local lastRefresh = os.clock() local cloneSince = nil -- mốc bắt đầu thấy clone LIÊN TỤC (reset khi clone rời / đang hop) while true do safeTaskWait(15) -- cadence check (đủ mịn cho grace, nhẹ CPU — refresh nặng vẫn theo RefreshInterval) -- ★ BỌC CẢ THÂN VÒNG trong pcall: 1 throw (vd index player disconnect giữa chừng / config lẻ) KHÔNG được -- giết coroutine — deadlock-breaker chết âm thầm = acc kẹt vĩnh viễn (đúng thứ cần tránh). safeTaskWait ở -- ĐẦU vòng = luôn yield kể cả khi lỗi liên tục → ko busy-loop CPU. pcall(function() if hopping or pendingHopReason then cloneSince = nil -- đã/đang hop (nguồn khác) → ko đụng vào, reset đồng hồ standoff return end if refreshEvery > 0 and (os.clock() - lastRefresh) >= refreshEvery then lastRefresh = os.clock() pcall(refreshRegistry) -- GET lại list (có retry) — recover + bắt clone mới end local ok, rank, who, cnt = fleetCloneStandoff() if not ok then cloneSince = nil return end -- clone đã rời / ko detect → reset cloneSince = cloneSince or os.clock() -- tie-break: rank nhỏ (UserId nhỏ) hop TRƯỚC; rank-trước GET-fail (ko arm) → rank-sau vẫn hop (eff lớn hơn) ⇒ KHÔNG kẹt. local eff = grace + (rank or 0) * stagger + jitter if (os.clock() - cloneSince) < eff then return end -- ★ re-check SAU yield (refreshRegistry/fleetCloneStandoff đã nhường) + GUARD `not pendingHopReason` như mọi -- producer khác (1681/1753/...): KHÔNG ghi đè quyết định hop đang bay (vd PEAK/OG Shutdown của dispatchGroup). if hopping or pendingHopReason then cloneSince = nil return end LOG( ("[STANDOFF] clone fleet (%s) kẹt chung sv %ds (rank %d → grace %ds, %d acc) → HOP phá kẹt"):format( tostring(who), math.floor(os.clock() - cloneSince), rank or 0, math.floor(eff), cnt or 1 ) ) pendingHopReason = ("fleet clone ket chung sv >%ds (%d acc) -> hop pha ket"):format( math.floor(eff), cnt or 1 ) _hopRejoin = false _hopKick = false -- hop() jobId (sang sv khác), KHÔNG matchmaking cloneSince = nil end) end end) end end) -- ★ STUCK GUARD: N phút STATS.seen KHÔNG đổi (script kẹt / server chết / conveyor đứng) → rejoin/kick do local sg = CONFIG.StuckGuard if sg and sg.Enabled then task.spawn(function() local stall = (sg.Minutes or 10) * 60 local lastSeen, lastChange = STATS.seen, os.clock() while true do task.wait(30) if STATS.seen ~= lastSeen then lastSeen = STATS.seen lastChange = os.clock() end if os.clock() - lastChange >= stall then if (sg.Action or "rejoin") == "kick" then WARN(("STUCK: %d' không quét thêm pet → KICK"):format(sg.Minutes or 10)) pcall(function() _killLeave() end) safeTaskWait(3) -- ★ chờ 3s cho kick "ăn" rồi shutdown pcall(function() _killLeave() end) else WARN(("STUCK: %d' không quét thêm pet → REJOIN"):format(sg.Minutes or 10)) local ok = pcall( TeleportService.TeleportToPlaceInstance, TeleportService, game.PlaceId, game.JobId, LocalPlayer ) if not ok then pcall(TeleportService.Teleport, TeleportService, game.PlaceId, LocalPlayer) end end lastChange = os.clock() -- chống lặp liên tục nếu teleport chậm task.wait(30) end end end) end end -- ★ RAM REFRESH (chống tràn RAM khi treo lâu): VM chạy nhiều giờ → RAM phình (tích luỹ cache + GC) → full CPU → gửi chậm. -- Audit cũ: KHÔNG có 1 leak cụ thể để vá → fix BỀN = RESET VM định kỳ. Sau RamKickMinutes phút → rejoin (autoexec re-run → VM mới, RAM về 0). do local mins = CONFIG.RamKickMinutes or 0 if mins > 0 then task.spawn(function() local deadline = os.clock() + mins * 60 while os.clock() < deadline do safeTaskWait(10) end WARN(("[RAM] treo %d phut → rejoin reset RAM/VM"):format(mins)) safeTaskWait(0.5) pcall(function() _killLeave() end) -- LocalPlayer:Kick(("[SAB] treo %d phut → rejoin reset RAM/VM"):format(mins)) end) end end LOG("event-driven scanner READY") end)