NL_CWD = NL_CWD.replace(/\//g, '\\') let localeObj const filesystem = Neutralino.filesystem const createCmdWindow = async (command) => { debug.log('Running command in new window: ' + command) Neutralino.os.execCommand(`cmd.exe /c start "" ${command}`, { background: true }) } const openInExplorer = async (path) => { debug.log('Opening path in explorer: ' + path) createCmdWindow(`explorer.exe "${path}"`) } /** * Enable play buttons */ async function enableButtons() { const offBtn = document.querySelector('#playOfficial') const privBtn = document.querySelector('#playPrivate') debug.log('Enabling buttons') offBtn.classList.remove('disabled') offBtn.disabled = false // Check if the proxy server is installed if (await proxyIsInstalled()) { privBtn.classList.remove('disabled') privBtn.disabled = false } } /** * Enable server launch button */ async function enableServerButton() { const serverBtn = document.querySelector('#serverLaunch') debug.log('Enabling server button') serverBtn.classList.remove('disabled') serverBtn.disabled = false } /** * Disable buttons when the game folder is not set */ async function handleGameNotSet() { // Set buttons to greyed out and disable document.querySelector('#gamePath').innerHTML = localeObj.folderNotSet || 'Not set' debug.log('Handling game not set') // Set official server background to default document.querySelector('#firstPanel').style.backgroundImage = 'url("../bg/private/default.png")' const offBtn = document.querySelector('#playOfficial') const privBtn = document.querySelector('#playPrivate') offBtn.classList.add('disabled') offBtn.disabled = true privBtn.classList.add('disabled') privBtn.disabled = true // TODO show a dialog of sorts } async function handleServerNotSet() { // Set buttons to greyed out and disable document.querySelector('#serverPath').innerHTML = localeObj.folderNotSet || 'Not set' debug.log('Handling server not set') // Set official server background to default // document.querySelector('#firstPanel').style.backgroundImage = `url("../bg/private/default.png")` const privBtn = document.querySelector('#serverLaunch') privBtn.classList.add('disabled') privBtn.disabled = true } /** * Show the game folder under the select button */ async function displayGameFolder() { const elm = document.querySelector('#gamePath') const config = await getCfg() debug.log('Displaying game exe') elm.innerHTML = config.gameexe } /** * Show the server folder under the select button */ async function displayServerFolder() { const elm = document.querySelector('#serverPath') const config = await getCfg() debug.log('Displaying server folder') elm.innerHTML = config.serverFolder } /** * Set the background images of both the private and public sections */ async function setBackgroundImage() { const config = await getCfg() const privImages = (await filesystem.readDirectory(NL_CWD + '/resources/bg/private')).filter(file => file.type === 'FILE' && !file.entry.includes('default')) const privImage = privImages[Math.floor(Math.random() * privImages.length)].entry const servImages = (await filesystem.readDirectory(NL_CWD + '/resources/bg/server')).filter(file => file.type === 'FILE' && !file.entry.includes('default')) const servImage = servImages[Math.floor(Math.random() * servImages.length)].entry // Set default image, it will change if the bg folder exists document.querySelector('#firstPanel').style.backgroundImage = 'url("https://webstatic.hoyoverse.com/upload/event/2020/11/04/7fd661b5184e1734f91f628b6f89a31f_7367318474207189623.png")' debug.log('Setting second panel to: ' + privImage) // Set the private background image document.querySelector('#secondPanel').style.backgroundImage = `url("../bg/private/${privImage}")` debug.log('Setting third panel to: ' + servImage) // Set the server background image document.querySelector('#thirdPanel').style.backgroundImage = `url("../bg/server/${servImage}")` return // Check if resources folder exists const mainDir = await filesystem.readDirectory(NL_CWD) if (!mainDir.find(dir => dir.entry === 'resources')) { await filesystem.createDirectory(NL_CWD + '/resources') } // Ensure bg folder exists const bgDir = await filesystem.readDirectory(NL_CWD + '/resources') if (!bgDir.find(dir => dir.entry === 'bg')) { await filesystem.createDirectory(NL_CWD + '/resources/bg') } // Ensure official folder exists const officialDir = await filesystem.readDirectory(NL_CWD + '/resources/bg') if (!officialDir.find(dir => dir.entry === 'official')) { await filesystem.createDirectory(NL_CWD + '/resources/bg/official') } if (config.gameexe) { // See if bg folder exists in parent dir const parentDir = await filesystem.readDirectory(config.gameexe + '/..') if (parentDir.find(dir => dir.entry === 'bg')) { const officialImages = (await filesystem.readDirectory(config.gameexe + '/../bg')).filter(file => file.type === 'FILE') if (officialImages.length > 0) { for (const bg of officialImages) { const path = config.gameexe.replace('\\', '/') + '/../bg/' + bg.entry // See if the file exists already const currentBgs = (await filesystem.readDirectory(NL_CWD + '/resources/bg/official/')).filter(file => file.type === 'FILE') if (!currentBgs.find(file => file.entry === bg.entry)) { await filesystem.copyFile(path, NL_CWD + '/resources/bg/official/' + bg.entry).catch(e => { // TODO: Handle error }) } } // Pick one of the images const localImg = (await filesystem.readDirectory(NL_CWD + '/resources/bg/official')).filter(file => file.type === 'FILE') const image = localImg[Math.floor(Math.random() * localImg.length)].entry // Set background image document.querySelector('#firstPanel').style.backgroundImage = `url("../bg/official/${image}")` } } } } /** * When an IP is being input, check if it is part of the favorites */ async function handleFavoriteInput() { const ip = document.querySelector('#ip').value const port = document.querySelector('#port').value || '443' const ipArr = await getFavIps() const addr = `${ip}:${port}` debug.log('Checking if IP is a favorite: ' + addr) if (!ip || !ipArr.includes(addr)) { document.querySelector('#star').src = 'icons/star_empty.svg' } else { document.querySelector('#star').src = 'icons/star_filled.svg' } } /** * Set the IP input value * * @param {String} ip */ async function setIp(ip) { const ipInput = document.querySelector('#ip') const portInput = document.querySelector('#port') const parseIp = ip.split(':')[0] const parsePort = ip.split(':')[1] debug.log('Setting IP input elm to: ' + parseIp + ' and port to: ' + parsePort) // Set star if (ip) { document.querySelector('#star').src = 'icons/star_filled.svg' } ipInput.value = parseIp portInput.value = parsePort } /** * Create/hide the favorites list */ async function handleFavoriteList() { const ipArr = await getFavIps() const ipList = document.querySelector('#ipList') if (ipList.style.display === 'none') { debug.log('IP list was closed, opening it') ipList.innerHTML = '' const list = ipList.appendChild( document.createElement('ul') ) if (ipArr.length < 1) { console.log('No favorites found') const listItem = list.appendChild( document.createElement('li') ) listItem.innerHTML = localeObj.noFavorites || 'No favorites set' } for (const ip of ipArr) { const elm = document.createElement('li') elm.innerHTML = ip elm.addEventListener('click', () => setIp(ip)) list.appendChild(elm) } ipList.style.display = 'block' const transform = window.getComputedStyle(document.querySelector('#ipList')).transform const xy = [ transform.split(',')[4], transform.split(',')[5] ] let newY = (27 * ipArr.length) * window.devicePixelRatio debug.log('IP list height: 56vh - ' + newY) if (ipArr.length === 0 || ipArr.length === 1) newY = 0 ipList.style.transform = `translate(${xy[0]}px, calc(56vh - ${newY}px)` } } async function openDownloads() { const downloads = document.querySelector('#downloadPanel') const config = await getCfg() debug.log('Opening downloads panel') if (downloads.style.display === 'none') { downloads.style.removeProperty('display') } // Disable the resource download button if a serverFolder path is not set if (!config.serverFolder) { debug.log('Server folder not set, disabling resource download button and data download button') document.querySelector('#resourceInstall').disabled = true document.querySelector('#resourceInstall').classList.add('disabled') // Disable data installer document.querySelector('#dataInstall').disabled = true document.querySelector('#dataInstall').classList.add('disabled') } else { debug.log('Server folder is set, enabling resource download button and data download button') document.querySelector('#resourceInstall').disabled = false document.querySelector('#resourceInstall').classList.remove('disabled') // Enable data installer document.querySelector('#dataInstall').disabled = false document.querySelector('#dataInstall').classList.remove('disabled') } } async function closeDownloads() { const downloads = document.querySelector('#downloadPanel') debug.log('Closing downloads panel') downloads.style.display = 'none' } async function openSettings() { const settings = document.querySelector('#settingsPanel') const config = await getCfg() debug.log('Opening settings panel') if (settings.style.display === 'none') { settings.style.removeProperty('display') } // Fill setting options with what is currently set in config const killSwitch = document.querySelector('#killswitchOption') const serverLaunch = document.querySelector('#serverLaunchOption') const httpsCheckbox = document.querySelector('#httpsOption') killSwitch.checked = config.enableKillswitch serverLaunch.checked = config.serverLaunchPanel httpsCheckbox.checked = config.useHttps debug.log('Set killswitch to: ' + config.enableKillswitch) debug.log('Set server launch to: ' + config.serverLaunchPanel) debug.log('Set https to: ' + config.useHttps) // Load languages getLanguages() // Check for updates //checkForUpdatesAndShow() } async function closeSettings() { const settings = document.querySelector('#settingsPanel') const config = await getCfg() debug.log('Closing settings panel') settings.style.display = 'none' // In case we installed the proxy server if (await proxyIsInstalled() && config.gameexe) { debug.log('Proxy has been installed and EXE is set, enabling playPrivate') const playPriv = document.querySelector('#playPrivate') playPriv.classList.remove('disabled') playPriv.disabled = false } } async function openLogin() { const login = document.querySelector('#loginPanel') const ip = document.querySelector('#ip').value const port = document.querySelector('#port').value const loginIpDisplay = document.querySelector('#loginPopupServer') const registerIpDisplay = document.querySelector('#registerPopupServer') const config = await getCfg() const useHttps = config.useHttps const url = `${useHttps ? 'https' : 'http'}://${ip}:${port}` debug.log('Opening login panel') debug.log('Url: ' + url) // Check if we even need to authenticate try { const { data } = await axios.get(url + '/authentication/type') debug.log('Request successful') if (!data.includes('GCAuthAuthenticationHandler')) { debug.log('No authentication required') launchPrivate() return } } catch(e) { debug.warn('Request failed') launchPrivate() return } debug.log('Login panel opening') loginIpDisplay.innerText = ip registerIpDisplay.innerText = ip if (login.style.display === 'none') { login.style.removeProperty('display') } } async function closeLogin() { const login = document.querySelector('#loginPanel') debug.log('Closing login panel') login.style.display = 'none' setLoginSection() } async function closeFirstTimePopup() { const firstTimePopup = document.querySelector('#firstTimeNotice') debug.log('Closing first time popup') firstTimePopup.style.display = 'none' } async function runInstallScript() { debug.log('Running install script') createCmdWindow(`.\\scripts\\install.cmd "${NL_CWD}" true`) // Create an interval that will check for the proxy server installation finish const interval = setInterval(async () => { debug.log('Checking if proxy server is installed') if (await proxyIsInstalled()) { clearInterval(interval) debug.log('Proxy server installed') enableButtons() } }, 1000) closeFirstTimePopup() } async function updateResources() { } async function checkForUpdatesAndShow() { const updateBtn = document.querySelector('#updateBtn') const subtitle = document.querySelector('#updateSubtitle') const url = 'https://github.com/Grasscutters/GrassClipper/releases/latest/download/' const manifest = await Neutralino.updater.checkForUpdates(url) // Version mismatch? Update! if (manifest?.version !== NL_APPVERSION) { debug.log('New update available') subtitle.innerHTML = 'New update available!' updateBtn.classList.remove('disabled') } else { debug.log('New update not available') subtitle.innerHTML = 'You are on the latest version! :)' updateBtn.classList.add('disabled') } } async function displayServerLaunchSection() { const serverPanel = document.querySelector('#thirdPanel') const bottomBtnSection = document.querySelector('#serverPath').parentElement debug.log('Displaying server launch section') if (serverPanel.style.display === 'none') { debug.log('Showing server launch section') serverPanel.style.removeProperty('display') bottomBtnSection.style.removeProperty('display') } else { debug.log('Hiding server launch section') serverPanel.style.display = 'none' bottomBtnSection.style.display = 'none' } } /** * Set the game folder by opening a folder picker */ async function setGameExe() { const gameExe = await Neutralino.os.showOpenDialog(localeObj.gameFolderDialog || 'Select game folder', { filters: [ { name: 'Executable files', extensions: ['exe'] } ] }) debug.log('Game exe selected: ' + gameExe[0]) if (!gameExe[0]) return if (hasForeignChars(gameExe[0])) displayAlert(localeObj.foreignCharacterAlert || 'The file path set contains foreign characters, this may cause problems!') // Set the folder in our configuration const config = await getCfg() // It's an array of selections, so only get the first one config.gameexe = gameExe[0].replace(/\//g, '\\') debug.log('Setting game exe to: ' + config.gameexe) Neutralino.storage.setData('config', JSON.stringify(config)) // Refresh background and path setBackgroundImage() displayGameFolder() enableButtons() } async function setGrasscutterFolder() { const folder = await Neutralino.os.showOpenDialog(localeObj.grasscutterFileDialog || 'Select Grasscutter server jar file', { filters: [ { name: 'Jar files', extensions: ['jar'] } ] }) debug.log('Grasscutter folder selected: ' + folder[0]) if (!folder[0]) return if (hasForeignChars(folder[0])) displayAlert(localeObj.foreignCharacterAlert || 'The file path set contains foreign characters, this may cause problems!') // Set the folder in our configuration const config = await getCfg() config.serverFolder = folder[0] debug.log('Setting grasscutter folder to: ' + config.serverFolder) Neutralino.storage.setData('config', JSON.stringify(config)) displayServerFolder() enableServerButton() } /** * Launch the game with no modifications nor proxy */ async function launchOfficial() { const config = await getCfg() debug.log('Launching game at ' + config.gameexe) Neutralino.os.execCommand(`"${config.gameexe}"`) } /** * Launch the game with a proxy */ async function launchPrivate() { const ip = document.getElementById('ip').value || '127.0.0.1' const port = document.getElementById('port').value || '443' const config = await getCfg() debug.log('Connecting to ' + ip + ':' + port) // Set the last connect config.lastConnect = ip Neutralino.storage.setData('config', JSON.stringify(config)) // Pass IP and game folder to the private server launcher Neutralino.os.execCommand( `.\\scripts\\private_server_launch.cmd ${ip} ${port} ${config.useHttps} "${config.gameexe}" "${NL_CWD}" ${config.enableKillswitch} true`, { background: true } ).catch(e => debug.error(e)) } async function launchLocalServer() { const config = await getCfg() debug.log('Launching local server') createCmdWindow(`.\\scripts\\local_server_launch.cmd "${config.serverFolder}"`).catch(e => console.log(e)) }