diff --git a/.gitignore b/.gitignore index 2ccb550..66207a5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ bin/ dist/ ext/ temp/ +gc*/ resources/js/neutralino.js resources/bg/official diff --git a/languages/en.json b/languages/en.json index a7ecafe..1fecb4d 100644 --- a/languages/en.json +++ b/languages/en.json @@ -63,5 +63,21 @@ "alertAuthNoRegister": "Authentication is disabled, no need to register!", "alertRegisterSuccess": "Registration successful!", + "downloadTitle": "Downloads", + "grassclipperTitle": "GrassClipper", + "grasscutterTitle": "Grasscutter", + "installerTitle": "Installer", + "installerSubtitle": "Installs proxy and other tools. Required for Grasscutter servers.", + "downloadStable": "Download Grasscutter Stable Build", + "stableSubtitle": "Install Grasscutter stable branch. This build usually has less bugs, but also less features.", + "downloadDev": "Download Grasscutter Development Build", + "downloadSubtitle": "Install Grasscutter development branch. This build sometimes has bugs, and is frequently updated. Use at your own risk.", + "downloadResources": "Download Grasscutter Resources", + "devSubtitle": "Downloads Grasscutter resources into the currently set Grasscutter folder. This should be done unless you plan on getting resources externally.", + + "gcScriptRunning": "Running...", + "stableInstall": "Download", + "devInstall": "Download", + "updateNotifText": "A new update is available! Newest version: " } diff --git a/resources/icons/download.svg b/resources/icons/download.svg new file mode 100644 index 0000000..8baf9ed --- /dev/null +++ b/resources/icons/download.svg @@ -0,0 +1,11 @@ + +Created with Fabric.js 1.7.22 + + + + + + + + + \ No newline at end of file diff --git a/resources/index.html b/resources/index.html index f59f5ea..bb71778 100644 --- a/resources/index.html +++ b/resources/index.html @@ -8,6 +8,7 @@ + @@ -114,6 +115,59 @@ + + + +
diff --git a/resources/js/gcdownloader.js b/resources/js/gcdownloader.js new file mode 100644 index 0000000..e45a0f4 --- /dev/null +++ b/resources/js/gcdownloader.js @@ -0,0 +1,102 @@ +async function clearGCInstallation() { + Neutralino.os.execCommand(`del /s /q "./gc"`) +} + +async function setDownloadButtonsToLoading() { + const stableBtn = document.querySelector('#stableInstall') + const devBtn = document.querySelector('#devInstall') + + stableBtn.innerText = localeObj.gcScriptRunning || 'Running...' + + devBtn.innerText = localeObj.gcScriptRunning || 'Running...' + + // Set btns to disabled + stableBtn.disabled = true + stableBtn.classList.add('disabled') + + devBtn.disabled = true + devBtn.classList.add('disabled') +} + +async function resetDownloadButtons() { + const stableBtn = document.querySelector('#stableInstall') + const devBtn = document.querySelector('#devInstall') + + stableBtn.innerText = localeObj.stableInstall || 'Download' + devBtn.innerText = localeObj.devInstall || 'Download' + + // Set btns to enabled + stableBtn.disabled = false + stableBtn.classList.remove('disabled') + + devBtn.disabled = false + devBtn.classList.remove('disabled') +} + +async function downloadGC(branch) { + const config = await getCfg() + + // If we are pulling from a new branch, delete the old installation + if (config.grasscutterBranch !== branch) await clearGCInstallation() + + // Set current installation in config + config.grasscutterBranch = branch + + // Set gc path for people with launcher enabled + config.serverFolder = `${NL_CWD}/gc-${branch}/grasscutter.jar` + + // Enable server launcher + config.serverLaunchPanel = true + + Neutralino.storage.setData('config', JSON.stringify(config)) + + setDownloadButtonsToLoading() + + // Keystore for branch (since they can differ) + const keystoreUrl = `https://github.com/Grasscutters/Grasscutter/raw/${branch}/keystore.p12` + + // External service that allows un-authed artifact downloading + const artiUrl = `https://nightly.link/Grasscutters/Grasscutter/workflows/build/${branch}/Grasscutter.zip` + + // For data files + const dataFiles = await axios.get(`https://api.github.com/repos/Grasscutters/Grasscutter/contents/data?ref=${branch}`) + const dataList = dataFiles.data + .map(file => ({ path: file.path, filename: file.name })) + .map(o => ({ url: `https://raw.githubusercontent.com/Grasscutters/Grasscutter/${branch}/${o.path}`, filename: o.filename })) + + // For key files + const keyFiles = await axios.get(`https://api.github.com/repos/Grasscutters/Grasscutter/contents/keys?ref=${branch}`) + const keyList = keyFiles.data + .map(file => ({ path: file.path, filename: file.name })) + .map(o => ({ url: `https://raw.githubusercontent.com/Grasscutters/Grasscutter/${branch}/${o.path}`, filename: o.filename })) + + const serverFolderFixed = config.serverFolder.match(/.*\\|.*\//g, '')[0].replace(/\//g, '\\') + + // Ensure data and key folders exist + + await Neutralino.os.execCommand(`mkdir ${serverFolderFixed}\\data`) + await Neutralino.os.execCommand(`mkdir ${serverFolderFixed}\\keys`) + + // Download data files + for (const o of dataList) { + const folder = 'data' + await Neutralino.os.execCommand(`powershell Invoke-WebRequest -Uri ${o.url} -OutFile "${serverFolderFixed}\\${folder}\\${o.filename}"`) + } + + // Download key files + for (const o of keyList) { + const folder = 'keys' + await Neutralino.os.execCommand(`powershell Invoke-WebRequest -Uri ${o.url} -OutFile "${serverFolderFixed}\\${folder}\\${o.filename}"`) + } + + // Run installer + createCmdWindow(`.\\scripts\\gc_download.cmd ${artiUrl} ${keystoreUrl} ${branch}`) + + // Fix buttons + resetDownloadButtons() + + // Display folder after saving config + displayServerFolder() + enableServerButton() + displayServerLaunchSection() +} \ No newline at end of file diff --git a/resources/js/helpers.js b/resources/js/helpers.js index c0b31c0..4e3a38b 100644 --- a/resources/js/helpers.js +++ b/resources/js/helpers.js @@ -12,6 +12,7 @@ serverLaunchPanel: false, language: 'en', useHttps: true, + grasscutterBranch: '', } const cfgStr = await Neutralino.storage.getData('config').catch(e => { // The data isn't set, so this is our first time opening @@ -97,7 +98,7 @@ async function openGameFolder() { async function openGrasscutterFolder() { const config = await getCfg() - const folder = config.serverFolder.match(/.*\\/g, '')[0] + const folder = config.serverFolder.match(/.*\\|.*\//g, '')[0] openInExplorer(folder) } diff --git a/resources/js/index.js b/resources/js/index.js index 98b22d8..a9a0bee 100644 --- a/resources/js/index.js +++ b/resources/js/index.js @@ -246,6 +246,30 @@ async function handleFavoriteList() { } } +async function openDownloads() { + const downloads = document.querySelector('#downloadPanel') + const config = await getCfg() + + if (downloads.style.display === 'none') { + downloads.style.removeProperty('display') + } + + // Disable the resource download button if a serverFolder path is not set + if (!config.serverFolder) { + document.querySelector('#resourceInstall').disabled = true + document.querySelector('#resourceInstall').classList.add('disabled') + } else { + document.querySelector('#resourceInstall').disabled = false + document.querySelector('#resourceInstall').classList.remove('disabled') + } +} + +async function closeDownloads() { + const downloads = document.querySelector('#downloadPanel') + + downloads.style.display = 'none' +} + async function openSettings() { const settings = document.querySelector('#settingsPanel') const config = await getCfg() @@ -387,6 +411,8 @@ async function setGameExe() { ] }) + if (!gameExe[0]) return; + // Set the folder in our configuration const config = await getCfg() @@ -408,6 +434,8 @@ async function setGrasscutterFolder() { ] }) + if (!folder[0]) return; + // Set the folder in our configuration const config = await getCfg() diff --git a/resources/js/onLoad.js b/resources/js/onLoad.js index a1e8773..bc96832 100644 --- a/resources/js/onLoad.js +++ b/resources/js/onLoad.js @@ -38,6 +38,7 @@ window.addEventListener("click", function(e) { const favList = document.querySelector('#ipList') const settingsPanel = document.querySelector('#settingsPanel') + const downloadPanel = document.querySelector('#downloadPanel') // This will close the favorites list no matter what is clicked if (favList.style.display !== 'none') { @@ -46,23 +47,32 @@ } // This will close the settings panel no matter what is clicked - let settingCheckElm = e.target + let checkElm = e.target - while(settingCheckElm.tagName !== 'BODY') { - if (settingCheckElm.id === 'settingsPanel' - || settingCheckElm.id === 'settingsBtn') { + while(checkElm.tagName !== 'BODY') { + if (checkElm.id === 'settingsPanel' + || checkElm.id === 'settingsBtn') { return } - settingCheckElm = settingCheckElm.parentElement + if (checkElm.id === 'downloadPanel' || + checkElm.id === 'downloadBtn') { + return + } + + checkElm = checkElm.parentElement } // We travelled through the parents, so if we are at the body, we clicked outside of the settings panel - if (settingCheckElm.tagName === 'BODY') { + if (checkElm.tagName === 'BODY') { // This will close the settings panel only when something outside of it is clicked if (settingsPanel.style.display !== 'none') { settingsPanel.style.display = 'none' } + + if (downloadPanel.style.display !== 'none') { + downloadPanel.style.display = 'none' + } } }); diff --git a/resources/js/translation.js b/resources/js/translation.js index 986b03c..dd80b83 100644 --- a/resources/js/translation.js +++ b/resources/js/translation.js @@ -90,6 +90,21 @@ async function doTranslation() { set('loginPopupContentBodyBtnRegister', 'authRegisterBtn') set('noLoginBtn', 'launchWithoutAuth') + // Downloads section + set('downloadTitle', 'downloadTitle') + set('grassclipperTitle', 'grassclipperTitle') + set('grasscutterTitle', 'grasscutterTitle') + set('installerTitle', 'installerTitle') + set('installerSubtitle', 'installerSubtitle') + set('downloadStable', 'downloadStable') + set('stableSubtitle', 'stableSubtitle') + set('downloadDev', 'downloadDev') + set('devSubtitle', 'downloadSubtitle') + set('downloadResources', 'downloadResources') + set('devSubtitle', 'devSubtitle') + set('stableInstall', 'stableInstall') + set('devInstall', 'devInstall') + // update notification set('updateNotifText', 'updateNotifText') } \ No newline at end of file diff --git a/resources/style/index.css b/resources/style/index.css index ff31870..f738e24 100644 --- a/resources/style/index.css +++ b/resources/style/index.css @@ -11,6 +11,10 @@ a { color: #fff; } +img { + height: 20px; +} + .darken { filter: brightness(0.6); } @@ -157,6 +161,7 @@ a { #firstTimeNotice, #loginPanel, +#downloadPanel, #settingsPanel { display: block; position: absolute; @@ -172,18 +177,21 @@ a { font-family: system-ui; } +#downloadPanel, #settingsPanel { width: 35%; height: 80%; overflow: auto; } +#downloadTitle, #fullSettingsTitle { font-size: 1.5em; font-weight: bold; margin-bottom: 10px; } +#downloadPanelInner, #settingsPanelInner { display: flex; flex-direction: column; @@ -192,16 +200,23 @@ a { padding: 10px 10%; } +.downloadRow, .settingsRow { width: 100%; } +.downloadTitle, .settingTitle { font-size: 1.2em; font-weight: bold; margin-bottom: 10px; } +.downloadTitle { + margin: 6px; +} + +.downloadLabel, .settingLabel { display:inline-block; font-size: 1em; @@ -209,12 +224,14 @@ a { margin: 10px 0px; } +.downloadSubtitle, .settingSubtitle { color: rgb(165, 165, 165); font-size: 0.8em; font-weight: normal; } +.downloadSection, .settingSection { display: flex; flex-direction: row; @@ -222,10 +239,12 @@ a { justify-content: space-between; } +.downloadSection .smolBtn, .settingSection .smolBtn { height: 30px; } +#downloadTitleBar, #settingsTitleBar { display: flex; flex-direction: row; @@ -233,15 +252,18 @@ a { justify-content: space-between; } +#downloadClose, #settingsClose { display: inline-block; transition: filter 0.1s ease-in-out; } +#downloadClose img, #settingsClose img { height: 20px; } +#downloadClose:hover, #settingsClose:hover { filter: invert(85%) sepia(31%) saturate(560%) hue-rotate(329deg) brightness(100%) contrast(92%); cursor: pointer; diff --git a/scripts/gc_download.cmd b/scripts/gc_download.cmd new file mode 100644 index 0000000..6d822d2 --- /dev/null +++ b/scripts/gc_download.cmd @@ -0,0 +1,58 @@ +@echo off + +set KEYSTORE_URL=%1 +set ARTIFACT_URL=%2 +set BRANCH=%3 +set FOLDER_NAME=".\gc-%BRANCH%" +set FOLDER_NAME=%FOLDER_NAME:"=% + +title GC Download Script + +if not exist "%FOLDER_NAME%" mkdir "%FOLDER_NAME%" +if not exist ".\temp" mkdir ".\temp" + +echo Downloading Grasscutter prebuilt jar... + +:: Download the jar +powershell Invoke-WebRequest -Uri %KEYSTORE_URL% -OutFile "./temp/gcjar.zip" + +echo Extracting... + +:: Delete old file if there is one there +if exist "%FOLDER_NAME%\grasscutter.jar" del "%FOLDER_NAME%\grasscutter.jar" + +powershell Expand-Archive -Path "./temp/gcjar.zip" -DestinationPath "%FOLDER_NAME%" -Force + +:: Find the jar file name and rename it, just in case +for %%i in (%FOLDER_NAME%/*) do ( + :: If the extension is jar, rename the file + if %%~xi equ .jar rename "%FOLDER_NAME%\%%i" grasscutter.jar +) + +echo Downloading keystore.p12... + +:: Download the keystore.p12 file +powershell Invoke-WebRequest -Uri %ARTIFACT_URL% -OutFile "./%FOLDER_NAME%/keystore.p12" + +:: Check java version, this will automatically output some tuff +call .\scripts\javaver.cmd %BRANCH% + +:: Allow resource downloading to be optional, since it takes a while +set REPLY=y +set /p "REPLY=Download server resources? (This can take a while) [y|n]:" +if /i not "%reply%" == "y" goto :finish + +call .\scripts\resources_download.cmd %FOLDER_NAME% + +goto :finish + +:finish + :: Remove temp stuff + del /s /q "./temp" + + echo Done, latest Grasscutter %BRANCH% now downloaded in %FOLDER_NAME% + + pause + + taskkill /f /fi "WINDOWTITLE eq GC Download Script" + diff --git a/scripts/install.cmd b/scripts/install.cmd index 99e27c3..63fbd41 100644 --- a/scripts/install.cmd +++ b/scripts/install.cmd @@ -3,6 +3,8 @@ set ORIGIN=%1 set ORIGIN=%ORIGIN:"=% +title Grassclipper Installer + echo Downloading proxy server... :: Make sure we are in the right directory @@ -35,12 +37,12 @@ taskkill /f /im mitmdump.exe echo Adding ceritifcate... :: Ensure we are elevated for certs ->nul 2>&1 certutil -addstore root %USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer || ( +>nul 2>&1 certutil -addstore root "%USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer" || ( echo ============================================================================================================ echo !! Certificate install failed !! echo. echo Please manually run this command as Administrator: - echo certutil -addstore root %USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer + echo certutil -addstore root "%USERPROFILE%\.mitmproxy\mitmproxy-ca-cert.cer" echo ============================================================================================================ ) @@ -48,4 +50,4 @@ echo Done! You can now open GrassClipper.exe! pause -exit /b \ No newline at end of file +taskkill /f /fi "WINDOWTITLE eq Grassclipper Installer" \ No newline at end of file diff --git a/scripts/javaver.cmd b/scripts/javaver.cmd new file mode 100644 index 0000000..c22093a --- /dev/null +++ b/scripts/javaver.cmd @@ -0,0 +1,70 @@ +@echo off + +set BRANCH=%1 + +echo Checking java version... + +where java >nul 2>nul +if %errorlevel%==1 ( + echo ======================================================================================= + echo No version of Java was found! + + if %BRANCH% EQU stable ( + echo To launch the stable branch server, you must install Java 8 + ) + + if %BRANCH% EQU development ( + echo To launch the development branch server, you must install Java 17 + ) + + echo ======================================================================================= + + exit /b +) + +:: https://stackoverflow.com/questions/5675459/how-to-get-java-version-from-batch-script +for /f "tokens=3" %%g in ('java -version 2^>^&1 ^| findstr /i "version"') do ( + @echo Output: %%g + set JAVAVER=%%g +) +set JAVAVER=%JAVAVER:"=% + +for /f "delims=. tokens=1-3" %%v in ("%JAVAVER%") do ( + set MAJOR=%%v + set MINOR=%%w + set BUILD=%%x +) + +if %BRANCH% EQU stable ( + :: Ensure java 8 + if %MAJOR% EQU 1 ( + if %MINOR% LSS 8 ( + echo ======================================================================================= + echo !! Java version is less than 8 !! + echo Please download Java 8 to ensure %BRANCH% branch server launches correctly. + echo ======================================================================================= + exit /b + ) + ) + + if %MAJOR% NEQ 1 ( + echo ======================================================================================= + echo !! Java version is not 8 !! + echo Please download Java 8 to ensure %BRANCH% branch server launches correctly. + echo ======================================================================================= + exit /b + ) +) + +if %BRANCH% EQU development ( + :: Ensure java 17 + if %MAJOR% LSS 17 ( + echo ======================================================================================= + echo !! Java version is less than 17 !! + echo Please download Java 17 to ensure %BRANCH% branch server launches correctly. + echo ======================================================================================= + exit /b + ) +) + +echo Java version is compatible \ No newline at end of file diff --git a/scripts/local_server_launch.cmd b/scripts/local_server_launch.cmd index e052d1a..75dde1f 100644 --- a/scripts/local_server_launch.cmd +++ b/scripts/local_server_launch.cmd @@ -3,15 +3,23 @@ set GRASSCUTTER_JAR=%1 set GRASSCUTTER_JAR=%GRASSCUTTER_JAR:"=% +title Grasscutter + :: Get folder the jar is in set "X=%GRASSCUTTER_JAR%" :l -if "%X:~-1%"=="\" goto al -set "X=%X:~0,-1%" -goto l + set IS_SLASH=false + + if "%X:~-1%"=="\" set IS_SLASH=true + if "%X:~-1%"=="/" set IS_SLASH=true + + if %IS_SLASH% equ true goto al + + set "X=%X:~0,-1%" + goto l :al -set "X=%X:~0,-1%" -set "GRASSCUTTER_ROOT=%X%" + set "X=%X:~0,-1%" + set "GRASSCUTTER_ROOT=%X%" echo Starting local Grasscutter server... diff --git a/scripts/private_server_launch.cmd b/scripts/private_server_launch.cmd index 0cc7b12..5fcbf1e 100644 --- a/scripts/private_server_launch.cmd +++ b/scripts/private_server_launch.cmd @@ -27,7 +27,7 @@ if "%ENABLE_KILLSWITCH%" EQU "true" ( :: Restart in elevated if need be >nul 2>&1 reg query "HKU\S-1-5-19" || ( set params = %*:"="""% - cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && %~s0 %1 %2 %3 "%4" ""%cd%/../"" %6", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && taskkill /f /fi "WINDOWTITLE eq PS Launcher Script" && exit /b ) + cd /d "%~dp0" && ( if exist "%temp%\getadmin.vbs" del "%temp%\getadmin.vbs" ) && fsutil dirty query %systemdrive% 1>nul 2>nul || ( echo Set UAC = CreateObject^("Shell.Application"^) : UAC.ShellExecute "cmd.exe", "/k cd ""%~sdp0"" && %~s0 %1 %2 %3 "%4" ""%cd%"" %6", "", "runas", 1 >> "%temp%\getadmin.vbs" && "%temp%\getadmin.vbs" && taskkill /f /fi "WINDOWTITLE eq PS Launcher Script" && exit /b ) ) ) @@ -41,7 +41,7 @@ reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v Pr reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer /d "127.0.0.1:8080" /f >nul 2>nul :: Start proxy server -start "Proxy Server" "%ORIGIN%/ext/mitmdump.exe" -s "%ORIGIN%/proxy/proxy.py" -k --allow-hosts ".*\.yuanshen\.com|.*\.mihoyo\.com|.*\.hoyoverse\.com" --ssl-insecure --set ip=%IP% --set port=%PORT% --set use_https=%USE_HTTPS% +start "Proxy Server" "%ORIGIN%\ext\mitmdump.exe" -s "%ORIGIN%/proxy/proxy.py" -k --allow-hosts ".*\.yuanshen\.com|.*\.mihoyo\.com|.*\.hoyoverse\.com" --ssl-insecure --set ip=%IP% --set port=%PORT% --set use_https=%USE_HTTPS% echo Opening %GAME_PATH% diff --git a/scripts/resources_download.cmd b/scripts/resources_download.cmd new file mode 100644 index 0000000..f1e1574 --- /dev/null +++ b/scripts/resources_download.cmd @@ -0,0 +1,29 @@ +@echo off + +set FOLDER_NAME=%1 +set FOLDER_NAME=%FOLDER_NAME:"=% + +if not exist ".\temp" mkdir ".\temp" +if not exist ".\resources" mkdir ".\resources" + +echo Downloading resources, this can take a while... + +:: Grab the giant ass resource zip +powershell Invoke-WebRequest -Uri https://github.com/Koko-boya/Grasscutter_Resources/archive/refs/heads/main.zip -OutFile "./temp/resources.zip" + +echo Extracting... + +:: Extract resources to the folder +powershell Expand-Archive -Path "./temp/resources.zip" -DestinationPath "%FOLDER_NAME%" -Force + +:: Delete old resources folder if there is one there +del /s /q "%FOLDER_NAME%\resources">nul + +echo Moving resources to folder... + +robocopy "%FOLDER_NAME%\Grasscutter_Resources-main\Resources" "%FOLDER_NAME%\resources" /E /MOVE>nul + +:: Delete straggling files +del /s /q "%FOLDER_NAME%\Grasscutter_Resources-main" + +echo Done, resources should be properly extracted \ No newline at end of file