Compare commits
	
		
			19 Commits
		
	
	
		
			2.1.1
			...
			f06e63f7f8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f06e63f7f8 | |||
| 638f1e852e | |||
| 8b30b7c1fa | |||
| f41ca92711 | |||
| 65a3e86261 | |||
| c74bc57453 | |||
| 528af70446 | |||
| 6f2764a047 | |||
| 1c4a40c495 | |||
| a9de377456 | |||
| c17cbc48d8 | |||
| 513692c2d5 | |||
| 90717ed960 | |||
| 6bcce04b72 | |||
| 8d2024aa0a | |||
| 22815e74b6 | |||
| c4d9862860 | |||
| d56d4875e0 | |||
| da8e237679 | 
| @@ -12,6 +12,7 @@ The Launcher is a "plug and play thing", download it, place it on the desktop an | ||||
|  | ||||
| - Automatic osu! client updating before Launch | ||||
| - Custom osu! Logo in MainMenu | ||||
| - Relax misses and much more | ||||
| - Account saving | ||||
|  | ||||
| ## Build from source | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| const appName = "EZPPLauncher"; | ||||
| const appVersion = "2.1.0"; | ||||
| const appVersion = "2.1.4"; | ||||
|  | ||||
| module.exports = { appName, appVersion }; | ||||
|   | ||||
| @@ -1,20 +1,20 @@ | ||||
| const childProcess = require("child_process"); | ||||
|  | ||||
| const runFile = (folder, file, args, onExit) => { | ||||
|     childProcess.execFile(file, args, { | ||||
|         cwd: folder | ||||
|     }, (_err, _stdout, _stdin) => { | ||||
|         if (onExit) onExit(); | ||||
|     }) | ||||
| } | ||||
|   childProcess.execFile(file, args, { | ||||
|     cwd: folder, | ||||
|   }, (_err, _stdout, _stdin) => { | ||||
|     if (onExit) onExit(); | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const runFileDetached = (folder, file, args) => { | ||||
|     const subProcess = childProcess.spawn(file + (args ? " " + args : ''), { | ||||
|         cwd: folder, | ||||
|         detached: true, | ||||
|         stdio: 'ignore' | ||||
|     }); | ||||
|     subProcess.unref(); | ||||
| } | ||||
|   const subProcess = childProcess.spawn(file + (args ? " " + args : ""), { | ||||
|     cwd: folder, | ||||
|     detached: true, | ||||
|     stdio: "ignore", | ||||
|   }); | ||||
|   subProcess.unref(); | ||||
| }; | ||||
|  | ||||
| module.exports = { runFile, runFileDetached }; | ||||
| module.exports = { runFile, runFileDetached }; | ||||
|   | ||||
							
								
								
									
										15
									
								
								electron/fileUtil.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								electron/fileUtil.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| const fs = require("fs"); | ||||
|  | ||||
| function isWritable(filePath) { | ||||
|   let fileAccess = false; | ||||
|   try { | ||||
|     fs.closeSync(fs.openSync(filePath, "r+")); | ||||
|     fileAccess = true; | ||||
|   } catch { | ||||
|   } | ||||
|   return fileAccess; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   isWritable, | ||||
| }; | ||||
| @@ -1,13 +1,13 @@ | ||||
| function formatBytes(bytes, decimals = 2) { | ||||
|     if (!+bytes) return '0 Bytes' | ||||
|   if (!+bytes) return "0 B"; | ||||
|  | ||||
|     const k = 1024 | ||||
|     const dm = decimals < 0 ? 0 : decimals | ||||
|     const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] | ||||
|   const k = 1024; | ||||
|   const dm = decimals < 0 ? 0 : decimals; | ||||
|   const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; | ||||
|  | ||||
|     const i = Math.floor(Math.log(bytes) / Math.log(k)) | ||||
|   const i = Math.floor(Math.log(bytes) / Math.log(k)); | ||||
|  | ||||
|     return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}` | ||||
|   return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`; | ||||
| } | ||||
|  | ||||
| module.exports = { formatBytes }; | ||||
| module.exports = { formatBytes }; | ||||
|   | ||||
							
								
								
									
										15
									
								
								electron/imageUtil.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								electron/imageUtil.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| async function checkImageExists(url) { | ||||
|   try { | ||||
|     const response = await fetch(url, { method: "HEAD" }); | ||||
|     if (!response.ok) { | ||||
|       return false; | ||||
|     } | ||||
|     const contentType = response.headers.get("content-type"); | ||||
|     if (!contentType) return false; | ||||
|     return contentType.startsWith("image/"); | ||||
|   } catch (error) { | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = { checkImageExists }; | ||||
							
								
								
									
										44
									
								
								electron/logging.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								electron/logging.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| const fs = require("fs"); | ||||
| const path = require("path"); | ||||
|  | ||||
| class Logger { | ||||
|   constructor(directory) { | ||||
|     this.directory = directory; | ||||
|     this.enabled = false; | ||||
|   } | ||||
|  | ||||
|   async init() { | ||||
|     const filename = `${new Date().toISOString().replace(/:/g, "-")}.log`; | ||||
|     this.logPath = path.join(this.directory, filename); | ||||
|   } | ||||
|  | ||||
|   async log(message) { | ||||
|     if (this.logPath === undefined || this.enabled == false) { | ||||
|       return; | ||||
|     } | ||||
|     if (!fs.existsSync(this.logPath)) { | ||||
|       await fs.promises.mkdir(this.directory, { recursive: true }); | ||||
|       await fs.promises.writeFile(this.logPath, ""); | ||||
|     } | ||||
|     const logMessage = `[${new Date().toISOString()}] LOG: ${message}`; | ||||
|     await fs.promises.appendFile(this.logPath, `${logMessage}\n`); | ||||
|     console.log(logMessage); | ||||
|   } | ||||
|  | ||||
|   async error(message, error) { | ||||
|     if (this.logPath === undefined || this.enabled == false) { | ||||
|       return; | ||||
|     } | ||||
|     if (!fs.existsSync(this.logPath)) { | ||||
|       await fs.promises.mkdir(this.directory, { recursive: true }); | ||||
|       await fs.promises.writeFile(this.logPath, ""); | ||||
|     } | ||||
|     const errorMessage = `[${ | ||||
|       new Date().toISOString() | ||||
|     }] ERROR: ${message}\n${error.stack}`; | ||||
|     await fs.promises.appendFile(this.logPath, `${errorMessage}\n`); | ||||
|     console.error(errorMessage); | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = Logger; | ||||
							
								
								
									
										26
									
								
								electron/netUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								electron/netUtils.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| const { exec } = require("child_process"); | ||||
|  | ||||
| async function isNet8Installed() { | ||||
|     return new Promise((resolve) => { | ||||
|         exec("dotnet --list-runtimes", (error, stdout, stderr) => { | ||||
|             if (error) { | ||||
|                 resolve(false); | ||||
|                 return; | ||||
|             } | ||||
|             if (stderr) { | ||||
|                 resolve(false); | ||||
|                 return; | ||||
|             } | ||||
|             const version = stdout.trim(); | ||||
|             for (const line of version.split('\n')) { | ||||
|                 if (line.startsWith("Microsoft.WindowsDesktop.App 8.")) { | ||||
|                     resolve(true); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             resolve(false); | ||||
|         }) | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = { isNet8Installed }; | ||||
| @@ -10,6 +10,16 @@ const checkUpdateURL = | ||||
| const ignoredOsuEntities = [ | ||||
|   "osu!auth.dll", | ||||
| ]; | ||||
| const gamemodes = { | ||||
|   0: "osu!", | ||||
|   1: "taiko", | ||||
|   2: "catch", | ||||
|   3: "mania", | ||||
|   4: "osu!(rx)", | ||||
|   5: "taiko(rx)", | ||||
|   6: "catch(rx)", | ||||
|   8: "osu!(ap)", | ||||
| }; | ||||
| const osuEntities = [ | ||||
|   "avcodec-51.dll", | ||||
|   "avformat-52.dll", | ||||
| @@ -34,26 +44,7 @@ const osuEntities = [ | ||||
|   "scores.db", | ||||
| ]; | ||||
|  | ||||
| const patcherFiles = [ | ||||
|   { | ||||
|     name: "patcher.exe", | ||||
|     url_download: "https://ez-pp.farm/assets/patcher.exe", | ||||
|     url_hash: "https://ez-pp.farm/assets/patcher.md5", | ||||
|   }, | ||||
|   { | ||||
|     name: "patch.hook.dll", | ||||
|     url_download: "https://ez-pp.farm/assets/patch.hook.dll", | ||||
|     url_hash: "https://ez-pp.farm/assets/patch.hook.md5", | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| const uiFiles = [ | ||||
|   { | ||||
|     name: "ezpp!ui.dll", | ||||
|     url_download: "https://ez-pp.farm/assets/ezpp!ui.dll", | ||||
|     url_hash: "https://ez-pp.farm/assets/ezpp!ui.md5", | ||||
|   }, | ||||
| ]; | ||||
| const ezppLauncherUpdateList = "https://ez-pp.farm/ezpplauncher"; | ||||
|  | ||||
| async function isValidOsuFolder(path) { | ||||
|   const allFiles = await fs.promises.readdir(path); | ||||
| @@ -193,31 +184,24 @@ function downloadUpdateFiles(osuPath, updateFiles) { | ||||
|  | ||||
|   const startDownload = async () => { | ||||
|     for (const updatePatch of updateFiles) { | ||||
|       const fileName = updatePatch.filename; | ||||
|       const fileSize = updatePatch.filesize; | ||||
|       const fileURL = updatePatch.url_full; | ||||
|  | ||||
|       const axiosDownloadWithProgress = await axios.get(fileURL, { | ||||
|         responseType: "stream", | ||||
|         onDownloadProgress: (progressEvent) => { | ||||
|           const { loaded, total } = progressEvent; | ||||
|           eventEmitter.emit("data", { | ||||
|             fileName, | ||||
|             loaded, | ||||
|             total, | ||||
|             progress: Math.floor((loaded / total) * 100), | ||||
|           }); | ||||
|         }, | ||||
|       }); | ||||
|       axiosDownloadWithProgress.data.on("end", () => { | ||||
|         eventEmitter.emit("data", { | ||||
|           fileName, | ||||
|           loaded: fileSize, | ||||
|           total: fileSize, | ||||
|           progress: 100, | ||||
|         }); | ||||
|       }); | ||||
|       try { | ||||
|         const fileName = updatePatch.filename; | ||||
|         const fileSize = updatePatch.filesize; | ||||
|         const fileURL = updatePatch.url_full; | ||||
|  | ||||
|         const axiosDownloadWithProgress = await axios.get(fileURL, { | ||||
|           responseType: "stream", | ||||
|           onDownloadProgress: (progressEvent) => { | ||||
|             const { loaded, total } = progressEvent; | ||||
|             eventEmitter.emit("data", { | ||||
|               fileName, | ||||
|               loaded, | ||||
|               total, | ||||
|               progress: Math.floor((loaded / total) * 100), | ||||
|             }); | ||||
|           }, | ||||
|         }); | ||||
|  | ||||
|         if (fs.existsSync(path.join(osuPath, fileName))) { | ||||
|           await fs.promises.rm(path.join(osuPath, fileName), { | ||||
|             force: true, | ||||
| @@ -231,6 +215,7 @@ function downloadUpdateFiles(osuPath, updateFiles) { | ||||
|         console.log(err); | ||||
|         eventEmitter.emit("error", { | ||||
|           fileName, | ||||
|           error: err, | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
| @@ -255,66 +240,76 @@ function runOsuWithDevServer(osuPath, serverDomain, onExit) { | ||||
|   runFile(osuPath, osuExecuteable, ["-devserver", serverDomain], onExit); | ||||
| } | ||||
|  | ||||
| async function getPatcherUpdates(osuPath) { | ||||
| async function getEZPPLauncherUpdateFiles(osuPath) { | ||||
|   const filesToDownload = []; | ||||
|  | ||||
|   const patcherDir = path.join(osuPath, "EZPPLauncher"); | ||||
|   if (!fs.existsSync(patcherDir)) fs.mkdirSync(patcherDir); | ||||
|  | ||||
|   for (const patcherFile of patcherFiles) { | ||||
|     if (fs.existsSync(path.join(patcherDir, patcherFile.name))) { | ||||
|       const latestPatchFileHash = await (await fetch(patcherFile.url_hash)) | ||||
|         .text(); | ||||
|       const localPatchFileHash = crypto.createHash("md5").update( | ||||
|         fs.readFileSync(path.join(patcherDir, patcherFile.name)), | ||||
|       ).digest("hex"); | ||||
|       if ( | ||||
|         latestPatchFileHash.trim().toLowerCase() != | ||||
|           localPatchFileHash.trim().toLowerCase() | ||||
|       ) filesToDownload.push(patcherFile); | ||||
|     } else filesToDownload.push(patcherFile); | ||||
|   const updateFilesRequest = await fetch(ezppLauncherUpdateList, { | ||||
|     method: "PATCH", | ||||
|   }); | ||||
|   const updateFiles = await updateFilesRequest.json(); | ||||
|   for (const updateFile of updateFiles) { | ||||
|     const filePath = path.join( | ||||
|       osuPath, | ||||
|       ...updateFile.folder.split("/"), | ||||
|       updateFile.name, | ||||
|     ); | ||||
|     if (fs.existsSync(filePath)) { | ||||
|       const fileHash = updateFile.md5.toLowerCase(); | ||||
|       const localFileHash = crypto.createHash("md5").update( | ||||
|         fs.readFileSync(filePath), | ||||
|       ).digest("hex").toLowerCase(); | ||||
|       if (fileHash !== localFileHash) { | ||||
|         filesToDownload.push(updateFile); | ||||
|       } | ||||
|     } else { | ||||
|       filesToDownload.push(updateFile); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return filesToDownload; | ||||
| } | ||||
|  | ||||
| function downloadPatcherUpdates(osuPath, patcherUpdates) { | ||||
| async function downloadEZPPLauncherUpdateFiles(osuPath, updateFiles) { | ||||
|   const eventEmitter = new EventEmitter(); | ||||
|  | ||||
|   const startDownload = async () => { | ||||
|     const patcherDir = path.join(osuPath, "EZPPLauncher"); | ||||
|     if (!fs.existsSync(patcherDir)) fs.mkdirSync(patcherDir); | ||||
|  | ||||
|     for (const patcherFile of patcherUpdates) { | ||||
|       const fileName = patcherFile.name; | ||||
|       const fileURL = patcherFile.url_download; | ||||
|       const axiosDownloadWithProgress = await axios.get(fileURL, { | ||||
|         responseType: "stream", | ||||
|         onDownloadProgress: (progressEvent) => { | ||||
|           const { loaded, total } = progressEvent; | ||||
|           eventEmitter.emit("data", { | ||||
|             fileName, | ||||
|             loaded, | ||||
|             total, | ||||
|             progress: Math.floor((loaded / total) * 100), | ||||
|           }); | ||||
|         }, | ||||
|       }); | ||||
|  | ||||
|     for (const updateFile of updateFiles) { | ||||
|       try { | ||||
|         if (fs.existsSync(path.join(osuPath, "EZPPLauncher", fileName))) { | ||||
|           await fs.promises.rm(path.join(osuPath, "EZPPLauncher", fileName), { | ||||
|         const filePath = path.join( | ||||
|           osuPath, | ||||
|           ...updateFile.folder.split("/"), | ||||
|           updateFile.name, | ||||
|         ); | ||||
|         const folder = path.dirname(filePath); | ||||
|         if (!fs.existsSync(folder)) { | ||||
|           await fs.promises.mkdir(folder, { recursive: true }); | ||||
|         } | ||||
|         const axiosDownloadWithProgress = await axios.get(updateFile.url, { | ||||
|           responseType: "stream", | ||||
|           onDownloadProgress: (progressEvent) => { | ||||
|             const fileSize = updateFile.size; | ||||
|             const { loaded } = progressEvent; | ||||
|             eventEmitter.emit("data", { | ||||
|               fileName: path.basename(filePath), | ||||
|               loaded, | ||||
|               total: fileSize, | ||||
|               progress: Math.floor((loaded / fileSize) * 100), | ||||
|             }); | ||||
|           }, | ||||
|         }); | ||||
|  | ||||
|         if (fs.existsSync(filePath)) { | ||||
|           await fs.promises.rm(filePath, { | ||||
|             force: true, | ||||
|           }); | ||||
|         } | ||||
|         await fs.promises.writeFile( | ||||
|           path.join(osuPath, "EZPPLauncher", fileName), | ||||
|           filePath, | ||||
|           axiosDownloadWithProgress.data, | ||||
|         ); | ||||
|       } catch (err) { | ||||
|         console.log(err); | ||||
|         eventEmitter.emit("error", { | ||||
|           fileName, | ||||
|           fileName: path.basename(filePath), | ||||
|           error: err, | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
| @@ -326,93 +321,49 @@ function downloadPatcherUpdates(osuPath, patcherUpdates) { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| async function getUIFiles(osuPath) { | ||||
|   const filesToDownload = []; | ||||
|  | ||||
|   const ezppLauncherDir = path.join(osuPath, "EZPPLauncher"); | ||||
|   if (!fs.existsSync(ezppLauncherDir)) fs.mkdirSync(ezppLauncherDir); | ||||
|  | ||||
|   for (const uiFile of uiFiles) { | ||||
|     if (fs.existsSync(path.join(ezppLauncherDir, uiFile.name))) { | ||||
|       const latestPatchFileHash = await (await fetch(uiFile.url_hash)).text(); | ||||
|       const localPatchFileHash = crypto.createHash("md5").update( | ||||
|         fs.readFileSync(path.join(ezppLauncherDir, uiFile.name)), | ||||
|       ).digest("hex"); | ||||
|       if ( | ||||
|         latestPatchFileHash.trim().toLowerCase() != | ||||
|           localPatchFileHash.trim().toLowerCase() | ||||
|       ) filesToDownload.push(uiFile); | ||||
|     } else filesToDownload.push(uiFile); | ||||
|   } | ||||
|  | ||||
|   return filesToDownload; | ||||
| } | ||||
|  | ||||
| function downloadUIFiles(osuPath, uiFiles) { | ||||
|   const eventEmitter = new EventEmitter(); | ||||
|  | ||||
|   const startDownload = async () => { | ||||
|     const ezpplauncherDir = path.join(osuPath, "EZPPLauncher"); | ||||
|     if (!fs.existsSync(ezpplauncherDir)) fs.mkdirSync(ezpplauncherDir); | ||||
|  | ||||
|     for (const uiFile of uiFiles) { | ||||
|       const fileName = uiFile.name; | ||||
|       const fileURL = uiFile.url_download; | ||||
|       const axiosDownloadWithProgress = await axios.get(fileURL, { | ||||
|         responseType: "stream", | ||||
|         onDownloadProgress: (progressEvent) => { | ||||
|           const { loaded, total } = progressEvent; | ||||
|           eventEmitter.emit("data", { | ||||
|             fileName, | ||||
|             loaded, | ||||
|             total, | ||||
|             progress: Math.floor((loaded / total) * 100), | ||||
|           }); | ||||
|         }, | ||||
|       }); | ||||
|       try { | ||||
|         if (fs.existsSync(path.join(osuPath, "EZPPLauncher", fileName))) { | ||||
|           await fs.promises.rm(path.join(osuPath, "EZPPLauncher", fileName), { | ||||
|             force: true, | ||||
|           }); | ||||
|         } | ||||
|  | ||||
|         await fs.promises.writeFile( | ||||
|           path.join(osuPath, "EZPPLauncher", fileName), | ||||
|           axiosDownloadWithProgress.data, | ||||
|         ); | ||||
|       } catch (err) { | ||||
|         console.log(err); | ||||
|         eventEmitter.emit("error", { | ||||
|           fileName, | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return { | ||||
|     eventEmitter, | ||||
|     startDownload, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| async function replaceUIFile(osuPath, revert) { | ||||
| async function replaceUIFiles(osuPath, revert) { | ||||
|   if (!revert) { | ||||
|     const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll"); | ||||
|     const oldOsuUIFile = path.join(osuPath, "osu!ui.dll"); | ||||
|     const ezppGameplayFile = path.join( | ||||
|       osuPath, | ||||
|       "EZPPLauncher", | ||||
|       "ezpp!gameplay.dll", | ||||
|     ); | ||||
|     const oldOsuGameplayFile = path.join(osuPath, "osu!gameplay.dll"); | ||||
|  | ||||
|     await fs.promises.rename( | ||||
|       oldOsuUIFile, | ||||
|       path.join(osuPath, "osu!ui.dll.bak"), | ||||
|     ); | ||||
|     await fs.promises.rename(ezppUIFile, oldOsuUIFile); | ||||
|  | ||||
|     await fs.promises.rename( | ||||
|       oldOsuGameplayFile, | ||||
|       path.join(osuPath, "osu!gameplay.dll.bak"), | ||||
|     ); | ||||
|     await fs.promises.rename(ezppGameplayFile, oldOsuGameplayFile); | ||||
|   } else { | ||||
|     const oldOsuUIFile = path.join(osuPath, "osu!ui.dll"); | ||||
|     const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll"); | ||||
|     const oldOsuGameplayFile = path.join(osuPath, "osu!gameplay.dll"); | ||||
|     const ezppGameplayFile = path.join( | ||||
|       osuPath, | ||||
|       "EZPPLauncher", | ||||
|       "ezpp!gameplay.dll", | ||||
|     ); | ||||
|  | ||||
|     await fs.promises.rename(oldOsuUIFile, ezppUIFile); | ||||
|     await fs.promises.rename( | ||||
|       path.join(osuPath, "osu!ui.dll.bak"), | ||||
|       oldOsuUIFile, | ||||
|     ); | ||||
|  | ||||
|     await fs.promises.rename(oldOsuGameplayFile, ezppGameplayFile); | ||||
|     await fs.promises.rename( | ||||
|       path.join(osuPath, "osu!gameplay.dll.bak"), | ||||
|       oldOsuGameplayFile, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -473,12 +424,11 @@ module.exports = { | ||||
|   getFilesThatNeedUpdate, | ||||
|   downloadUpdateFiles, | ||||
|   runOsuWithDevServer, | ||||
|   getPatcherUpdates, | ||||
|   downloadPatcherUpdates, | ||||
|   downloadUIFiles, | ||||
|   getUIFiles, | ||||
|   replaceUIFile, | ||||
|   replaceUIFiles, | ||||
|   findOsuInstallation, | ||||
|   updateOsuConfigHashes, | ||||
|   runOsuUpdater, | ||||
|   getEZPPLauncherUpdateFiles, | ||||
|   downloadEZPPLauncherUpdateFiles, | ||||
|   gamemodes, | ||||
| }; | ||||
|   | ||||
| @@ -2,7 +2,10 @@ const DiscordRPC = require("discord-auto-rpc"); | ||||
| const { appName, appVersion } = require("./appInfo.js"); | ||||
|  | ||||
| const clientId = "1032772293220384808"; | ||||
|  | ||||
| /** @type {DiscordRPC.AutoClient} */ | ||||
| let richPresence; | ||||
|  | ||||
| let intervalId; | ||||
|  | ||||
| let currentStatus = { | ||||
| @@ -32,6 +35,9 @@ module.exports = { | ||||
|       richPresence = new DiscordRPC.AutoClient({ transport: "ipc" }); | ||||
|       richPresence.endlessLogin({ clientId }); | ||||
|       richPresence.once("ready", () => { | ||||
|         console.log( | ||||
|           "connected presence with user " + richPresence.user.username, | ||||
|         ); | ||||
|         richPresence.setActivity(currentStatus); | ||||
|         intervalId = setInterval(() => { | ||||
|           richPresence.setActivity(currentStatus); | ||||
| @@ -47,16 +53,17 @@ module.exports = { | ||||
|       richPresence = null; | ||||
|     } | ||||
|   }, | ||||
|   updateStatus: ({ state, details }) => { | ||||
|   updateStatus: ({ state, details, largeImageKey }) => { | ||||
|     currentStatus.state = state ?? "  "; | ||||
|     currentStatus.details = details ?? "  "; | ||||
|     currentStatus.largeImageKey = largeImageKey ?? "ezppfarm"; | ||||
|   }, | ||||
|   updateVersion: (osuVersion) => { | ||||
|     currentStatus.smallImageKey = osuVersion ? "osu" : "  "; | ||||
|     currentStatus.smallImageText = osuVersion ? `osu! ${osuVersion}` : "  "; | ||||
|   updateUser: ({ username, id }) => { | ||||
|     currentStatus.smallImageKey = id ? `https://a.ez-pp.farm/${id}` : "  "; | ||||
|     currentStatus.smallImageText = username ?? "  "; | ||||
|   }, | ||||
|   update: () => { | ||||
|     if (richPresence) { | ||||
|     if (richPresence && richPresence.user) { | ||||
|       richPresence.setActivity(currentStatus); | ||||
|     } | ||||
|   }, | ||||
|   | ||||
							
								
								
									
										591
									
								
								main.js
									
									
									
									
									
								
							
							
						
						
									
										591
									
								
								main.js
									
									
									
									
									
								
							| @@ -3,15 +3,6 @@ const { app, BrowserWindow, Menu, ipcMain, dialog, shell } = require( | ||||
|   "electron", | ||||
| ); | ||||
|  | ||||
| /* const unhandled = require("electron-unhandled"); | ||||
| unhandled({ | ||||
|   logger: console.error, | ||||
|   showDialog: true, | ||||
|   reportButton: () => { | ||||
|     shell.openExternal("https://ez-pp.farm/discord"); | ||||
|   }, | ||||
| }); */ | ||||
|  | ||||
| const path = require("path"); | ||||
| const serve = require("electron-serve"); | ||||
| const loadURL = serve({ directory: "public" }); | ||||
| @@ -27,14 +18,12 @@ const { | ||||
|   downloadUpdateFiles, | ||||
|   getUserConfig, | ||||
|   runOsuWithDevServer, | ||||
|   getPatcherUpdates, | ||||
|   downloadPatcherUpdates, | ||||
|   getUIFiles, | ||||
|   downloadUIFiles, | ||||
|   replaceUIFile, | ||||
|   replaceUIFiles, | ||||
|   findOsuInstallation, | ||||
|   updateOsuConfigHashes, | ||||
|   runOsuUpdater, | ||||
|   gamemodes, | ||||
|   getEZPPLauncherUpdateFiles, | ||||
|   downloadEZPPLauncherUpdateFiles, | ||||
| } = require("./electron/osuUtil"); | ||||
| const { formatBytes } = require("./electron/formattingUtil"); | ||||
| const windowName = require("get-window-by-name"); | ||||
| @@ -46,6 +35,10 @@ const { getHwId } = require("./electron/hwidUtil"); | ||||
| const { appName, appVersion } = require("./electron/appInfo"); | ||||
| const { updateAvailable, releasesUrl } = require("./electron/updateCheck"); | ||||
| const fkill = require("fkill"); | ||||
| const { checkImageExists } = require("./electron/imageUtil"); | ||||
| const { isNet8Installed } = require("./electron/netUtils"); | ||||
| const Logger = require("./electron/logging"); | ||||
| const { isWritable } = require("./electron/fileUtil"); | ||||
|  | ||||
| // Keep a global reference of the window object, if you don't, the window will | ||||
| // be closed automatically when the JavaScript object is garbage collected. | ||||
| @@ -54,6 +47,13 @@ let osuCheckInterval; | ||||
| let userOsuPath; | ||||
| let osuLoaded = false; | ||||
| let patch = false; | ||||
| let logger = new Logger(path.join( | ||||
|   process.platform == "win32" | ||||
|     ? process.env["LOCALAPPDATA"] | ||||
|     : process.env["HOME"], | ||||
|   "EZPPLauncher", | ||||
|   "logs", | ||||
| )); | ||||
|  | ||||
| let currentUser = undefined; | ||||
|  | ||||
| @@ -72,12 +72,39 @@ function startOsuStatus() { | ||||
|     if (firstInstance) { | ||||
|       if (!osuLoaded) { | ||||
|         osuLoaded = true; | ||||
|  | ||||
|         try { | ||||
|           const currentUserInfo = await fetch( | ||||
|             `https://api.ez-pp.farm/get_player_info?name=${currentUser.username}&scope=info`, | ||||
|           ); | ||||
|           const currentUserInfoJson = await currentUserInfo.json(); | ||||
|           if ( | ||||
|             "player" in currentUserInfoJson && | ||||
|             currentUserInfoJson.player != null | ||||
|           ) { | ||||
|             if ( | ||||
|               "info" in currentUserInfoJson.player && | ||||
|               currentUserInfoJson.player.info != null | ||||
|             ) { | ||||
|               const id = currentUserInfoJson.player.info.id; | ||||
|               const username = currentUserInfoJson.player.info.name; | ||||
|               richPresence.updateUser({ | ||||
|                 id, | ||||
|                 username, | ||||
|               }); | ||||
|               richPresence.update(); | ||||
|             } | ||||
|           } | ||||
|         } catch { | ||||
|         } | ||||
|  | ||||
|         setTimeout(() => { | ||||
|           if (patch) { | ||||
|             const patcherExecuteable = path.join( | ||||
|               userOsuPath, | ||||
|               "EZPPLauncher", | ||||
|               "patcher.exe", | ||||
|               "patcher", | ||||
|               "osu!.patcher.exe", | ||||
|             ); | ||||
|             if (fs.existsSync(patcherExecuteable)) { | ||||
|               runFileDetached(userOsuPath, patcherExecuteable); | ||||
| @@ -96,15 +123,46 @@ function startOsuStatus() { | ||||
|       if (!("player_status" in currentStatus)) return; | ||||
|       if (!("status" in currentStatus.player_status)) return; | ||||
|  | ||||
|       const currentMode = currentStatus.player_status.status.mode; | ||||
|       const currentModeString = gamemodes[currentMode]; | ||||
|  | ||||
|       const currentInfoRequest = await fetch( | ||||
|         "https://api.ez-pp.farm/get_player_info?name=" + currentUser.username + | ||||
|           "&scope=all", | ||||
|       ); | ||||
|       const currentInfo = await currentInfoRequest.json(); | ||||
|       let currentUsername = currentInfo.player.info.name; | ||||
|       const currentId = currentInfo.player.info.id; | ||||
|       const currentStats = currentInfo.player.stats[currentMode]; | ||||
|  | ||||
|       currentUsername += ` (#${currentStats.rank})`; | ||||
|  | ||||
|       let largeImageKey = "ezppfarm"; | ||||
|       let details = "Idle..."; | ||||
|       let infoText = currentStatus.player_status.status.info_text.length > 0 | ||||
|         ? currentStatus.player_status.status.info_text | ||||
|         : "  "; | ||||
|       if ( | ||||
|         "beatmap" in currentStatus.player_status.status && | ||||
|         currentStatus.player_status.status.beatmap !== null | ||||
|       ) { | ||||
|         const setId = currentStatus.player_status.status.beatmap.set_id; | ||||
|         if (setId) { | ||||
|           const coverImage = | ||||
|             `https://assets.ppy.sh/beatmaps/${setId}/covers/list@2x.jpg`; | ||||
|           if ( | ||||
|             checkImageExists(coverImage) | ||||
|           ) { | ||||
|             largeImageKey = coverImage; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       switch (currentStatus.player_status.status.action) { | ||||
|         case 1: | ||||
|           details = "AFK..."; | ||||
|           infoText = "  "; | ||||
|           largeImageKey = "ezppfarm"; | ||||
|           break; | ||||
|         case 2: | ||||
|           details = "Playing..."; | ||||
| @@ -118,6 +176,7 @@ function startOsuStatus() { | ||||
|         case 5: | ||||
|           details = "Multiplayer: Selecting a Beatmap..."; | ||||
|           infoText = "  "; | ||||
|           largeImageKey = "ezppfarm"; | ||||
|           break; | ||||
|         case 6: | ||||
|           details = "Watching..."; | ||||
| @@ -127,10 +186,12 @@ function startOsuStatus() { | ||||
|           break; | ||||
|         case 9: | ||||
|           details = "Submitting..."; | ||||
|           largeImageKey = "ezppfarm"; | ||||
|           break; | ||||
|         case 11: | ||||
|           details = "Multiplayer: Idle..."; | ||||
|           infoText = "  "; | ||||
|           largeImageKey = "ezppfarm"; | ||||
|           break; | ||||
|         case 12: | ||||
|           details = "Multiplayer: Playing..."; | ||||
| @@ -138,12 +199,21 @@ function startOsuStatus() { | ||||
|         case 13: | ||||
|           details = "Browsing osu!direct..."; | ||||
|           infoText = "  "; | ||||
|           largeImageKey = "ezppfarm"; | ||||
|           break; | ||||
|       } | ||||
|  | ||||
|       details = `[${currentModeString}] ${details}`; | ||||
|  | ||||
|       richPresence.updateUser({ | ||||
|         username: currentUsername, | ||||
|         id: currentId, | ||||
|       }); | ||||
|  | ||||
|       richPresence.updateStatus({ | ||||
|         details, | ||||
|         state: infoText, | ||||
|         largeImageKey, | ||||
|       }); | ||||
|  | ||||
|       richPresence.update(); | ||||
| @@ -157,9 +227,19 @@ function stopOsuStatus() { | ||||
|  | ||||
| function registerIPCPipes() { | ||||
|   ipcMain.handle("ezpplauncher:login", async (e, args) => { | ||||
|     const hwid = getHwId(); | ||||
|     let hwid = ""; | ||||
|     try { | ||||
|       hwid = getHwId(); | ||||
|     } catch (err) { | ||||
|       logger.error(`Failed to get HWID.`, err); | ||||
|       return { | ||||
|         code: 500, | ||||
|         message: "Failed to get HWID.", | ||||
|       }; | ||||
|     } | ||||
|     const timeout = new AbortController(); | ||||
|     const timeoutId = setTimeout(() => timeout.abort(), 8000); | ||||
|     logger.log(`Logging in with user ${args.username}...`); | ||||
|     try { | ||||
|       const fetchResult = await fetch("https://ez-pp.farm/login/check", { | ||||
|         signal: timeout.signal, | ||||
| @@ -184,14 +264,20 @@ function registerIPCPipes() { | ||||
|           } | ||||
|           currentUser = args; | ||||
|           config.remove("guest"); | ||||
|         } | ||||
|           logger.log(`Logged in as user ${args.username}!`); | ||||
|         } else logger.log(`Login failed for user ${args.username}.`); | ||||
|         return result; | ||||
|       } | ||||
|       logger.log( | ||||
|         `Login failed for user ${username}.\nResponse:\n${await fetchResult | ||||
|           .text()}`, | ||||
|       ); | ||||
|       return { | ||||
|         code: 500, | ||||
|         message: "Something went wrong while logging you in.", | ||||
|       }; | ||||
|     } catch (err) { | ||||
|       logger.error("Error while logging in:", err); | ||||
|       return { | ||||
|         code: 500, | ||||
|         message: "Something went wrong while logging you in.", | ||||
| @@ -221,6 +307,7 @@ function registerIPCPipes() { | ||||
|     } | ||||
|     const timeout = new AbortController(); | ||||
|     const timeoutId = setTimeout(() => timeout.abort(), 8000); | ||||
|     logger.log(`Logging in with user ${username}...`); | ||||
|     try { | ||||
|       const fetchResult = await fetch("https://ez-pp.farm/login/check", { | ||||
|         signal: timeout.signal, | ||||
| @@ -243,16 +330,23 @@ function registerIPCPipes() { | ||||
|             username: username, | ||||
|             password: password, | ||||
|           }; | ||||
|         } | ||||
|           logger.log(`Logged in as user ${username}!`); | ||||
|         } else logger.log(`Login failed for user ${username}.`); | ||||
|  | ||||
|         return result; | ||||
|       } else { | ||||
|         config.remove("password"); | ||||
|       } | ||||
|       logger.log( | ||||
|         `Login failed for user ${username}.\nResponse:\n${await fetchResult | ||||
|           .text()}`, | ||||
|       ); | ||||
|       return { | ||||
|         code: 500, | ||||
|         message: "Something went wrong while logging you in.", | ||||
|       }; | ||||
|     } catch (err) { | ||||
|       logger.error("Error while logging in:", err); | ||||
|       return { | ||||
|         code: 500, | ||||
|         message: "Something went wrong while logging you in.", | ||||
| @@ -265,6 +359,7 @@ function registerIPCPipes() { | ||||
|     config.remove("password"); | ||||
|     config.set("guest", "1"); | ||||
|     currentUser = undefined; | ||||
|     logger.log("Logged in as guest user."); | ||||
|   }); | ||||
|  | ||||
|   ipcMain.handle("ezpplauncher:logout", (e) => { | ||||
| @@ -272,6 +367,7 @@ function registerIPCPipes() { | ||||
|     config.remove("password"); | ||||
|     config.remove("guest"); | ||||
|     currentUser = undefined; | ||||
|     logger.log("Loging out."); | ||||
|     return true; | ||||
|   }); | ||||
|  | ||||
| @@ -288,6 +384,10 @@ function registerIPCPipes() { | ||||
|         else richPresence.connect(); | ||||
|       } | ||||
|  | ||||
|       if (key == "logging") { | ||||
|         logger.enabled = logging; | ||||
|       } | ||||
|  | ||||
|       if (typeof value == "boolean") { | ||||
|         config.set(key, value ? "true" : "false"); | ||||
|       } else { | ||||
| @@ -337,121 +437,78 @@ function registerIPCPipes() { | ||||
|   }); | ||||
|  | ||||
|   ipcMain.handle("ezpplauncher:launch", async (e) => { | ||||
|     const configPatch = config.get("patch"); | ||||
|     patch = configPatch != undefined ? configPatch == "true" : true; | ||||
|     mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|       status: "Checking osu! directory...", | ||||
|     }); | ||||
|     await new Promise((res) => setTimeout(res, 1000)); | ||||
|     const osuPath = config.get("osuPath"); | ||||
|     userOsuPath = osuPath; | ||||
|     if (osuPath == undefined) { | ||||
|       mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|       mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|         type: "error", | ||||
|         message: "osu! path not set!", | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     if (!(await isValidOsuFolder(osuPath))) { | ||||
|       mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|       mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|         type: "error", | ||||
|         message: "invalid osu! path!", | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|       status: "Checking for osu! updates...", | ||||
|     }); | ||||
|     await new Promise((res) => setTimeout(res, 1000)); | ||||
|     const releaseStream = await getGlobalConfig(osuPath).get("_ReleaseStream"); | ||||
|     const latestFiles = await getUpdateFiles(releaseStream); | ||||
|     const uiFiles = await getUIFiles(osuPath); | ||||
|     const updateFiles = await getFilesThatNeedUpdate(osuPath, latestFiles); | ||||
|     if (uiFiles.length > 0) { | ||||
|       const uiDownloader = downloadUIFiles(osuPath, uiFiles); | ||||
|       let errored = false; | ||||
|       uiDownloader.eventEmitter.on("error", (data) => { | ||||
|         const filename = data.fileName; | ||||
|         errored = true; | ||||
|     try { | ||||
|       const osuWindowTitle = windowName.getWindowText("osu!.exe"); | ||||
|       if (osuWindowTitle.length > 0) { | ||||
|         mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|           type: "error", | ||||
|           message: | ||||
|             `Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`, | ||||
|           message: "osu! is running, please exit.", | ||||
|         }); | ||||
|       }); | ||||
|       uiDownloader.eventEmitter.on("data", (data) => { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|           progress: Math.ceil(data.progress), | ||||
|         }); | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: `Downloading ${data.fileName}(${formatBytes(data.loaded)}/${ | ||||
|             formatBytes(data.total) | ||||
|           })...`, | ||||
|         }); | ||||
|       }); | ||||
|       await uiDownloader.startDownload(); | ||||
|       mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|         progress: -1, | ||||
|       }); | ||||
|       if (errored) { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     if (updateFiles.length > 0) { | ||||
|       const updateDownloader = downloadUpdateFiles(osuPath, updateFiles); | ||||
|       let errored = false; | ||||
|       updateDownloader.eventEmitter.on("error", (data) => { | ||||
|         const filename = data.fileName; | ||||
|         errored = true; | ||||
|         mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|           type: "error", | ||||
|           message: | ||||
|             `Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`, | ||||
|         }); | ||||
|       }); | ||||
|       updateDownloader.eventEmitter.on("data", (data) => { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|           progress: Math.ceil(data.progress), | ||||
|         }); | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: `Downloading ${data.fileName}(${formatBytes(data.loaded)}/${ | ||||
|             formatBytes(data.total) | ||||
|           })...`, | ||||
|         }); | ||||
|       }); | ||||
|       await updateDownloader.startDownload(); | ||||
|       mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|         progress: -1, | ||||
|       }); | ||||
|       if (errored) { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|         return; | ||||
|       } | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "osu! is now up to date!", | ||||
|       }); | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|     } else { | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "osu! is up to date!", | ||||
|       }); | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|     } | ||||
|  | ||||
|     if (patch) { | ||||
|       logger.log("Preparing launch..."); | ||||
|       const configPatch = config.get("patch"); | ||||
|       patch = configPatch != undefined ? configPatch == "true" : true; | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "Looking for patcher updates...", | ||||
|         status: "Checking osu! directory...", | ||||
|       }); | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|       const patchFiles = await getPatcherUpdates(osuPath); | ||||
|       if (patchFiles.length > 0) { | ||||
|         const patcherDownloader = downloadPatcherUpdates(osuPath, patchFiles); | ||||
|       const osuPath = config.get("osuPath"); | ||||
|       userOsuPath = osuPath; | ||||
|       if (osuPath == undefined) { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|         mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|           type: "error", | ||||
|           message: "osu! path not set!", | ||||
|         }); | ||||
|         mainWindow.webContents.send("ezpplauncher:open-settings"); | ||||
|         logger.log("osu! path is not set."); | ||||
|         return; | ||||
|       } | ||||
|       if (!(await isValidOsuFolder(osuPath))) { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|         mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|           type: "error", | ||||
|           message: "invalid osu! path!", | ||||
|         }); | ||||
|         logger.log("osu! path is invalid."); | ||||
|         return; | ||||
|       } | ||||
|       if (patch) { | ||||
|         if (!(await isNet8Installed())) { | ||||
|           mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|           mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|             type: "error", | ||||
|             message: ".NET 8 is not installed.", | ||||
|           }); | ||||
|           //open .net 8 download in browser | ||||
|           shell.openExternal( | ||||
|             "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.4-windows-x64-installer", | ||||
|           ); | ||||
|           logger.log(".NET 8 is not installed."); | ||||
|         } | ||||
|       } | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "Checking for osu! updates...", | ||||
|       }); | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|       const releaseStream = await getGlobalConfig(osuPath).get( | ||||
|         "_ReleaseStream", | ||||
|       ); | ||||
|       const latestFiles = await getUpdateFiles(releaseStream); | ||||
|       const updateFiles = await getFilesThatNeedUpdate(osuPath, latestFiles); | ||||
|       if (updateFiles.length > 0) { | ||||
|         logger.log("osu! updates found."); | ||||
|         const updateDownloader = downloadUpdateFiles(osuPath, updateFiles); | ||||
|         let errored = false; | ||||
|         patcherDownloader.eventEmitter.on("error", (data) => { | ||||
|         updateDownloader.eventEmitter.on("error", (data) => { | ||||
|           const filename = data.fileName; | ||||
|           logger.error( | ||||
|             `Failed to download/replace ${filename}!`, | ||||
|             data.error, | ||||
|           ); | ||||
|           errored = true; | ||||
|           mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|             type: "error", | ||||
| @@ -459,7 +516,10 @@ function registerIPCPipes() { | ||||
|               `Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`, | ||||
|           }); | ||||
|         }); | ||||
|         patcherDownloader.eventEmitter.on("data", (data) => { | ||||
|         updateDownloader.eventEmitter.on("data", (data) => { | ||||
|           if (data.progress >= 100) { | ||||
|             logger.log(`Downloaded ${data.fileName} successfully.`); | ||||
|           } | ||||
|           mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|             progress: Math.ceil(data.progress), | ||||
|           }); | ||||
| @@ -469,7 +529,7 @@ function registerIPCPipes() { | ||||
|             })...`, | ||||
|           }); | ||||
|         }); | ||||
|         await patcherDownloader.startDownload(); | ||||
|         await updateDownloader.startDownload(); | ||||
|         mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|           progress: -1, | ||||
|         }); | ||||
| @@ -478,118 +538,206 @@ function registerIPCPipes() { | ||||
|           return; | ||||
|         } | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: "Patcher is now up to date!", | ||||
|           status: "osu! is now up to date!", | ||||
|         }); | ||||
|         await new Promise((res) => setTimeout(res, 1000)); | ||||
|       } else { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: "Patcher is up to date!", | ||||
|           status: "osu! is up to date!", | ||||
|         }); | ||||
|         await new Promise((res) => setTimeout(res, 1000)); | ||||
|       } | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|     } | ||||
|     if (updateFiles.length > 0) { | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "Launching osu! updater to verify updates...", | ||||
|       }); | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|  | ||||
|       await new Promise((res) => { | ||||
|         runOsuUpdater(osuPath, async () => { | ||||
|           await new Promise((res) => setTimeout(res, 500)); | ||||
|           const terminationThread = setInterval(async () => { | ||||
|             const osuWindowTitle = windowName.getWindowText("osu!.exe"); | ||||
|             if (osuWindowTitle.length < 0) { | ||||
|               return; | ||||
|             } | ||||
|             const firstInstance = osuWindowTitle[0]; | ||||
|             if (firstInstance) { | ||||
|               const processId = firstInstance.processId; | ||||
|               await fkill(processId, { force: true, silent: true }); | ||||
|               clearInterval(terminationThread); | ||||
|               res(); | ||||
|             } | ||||
|           }, 500); | ||||
|       if (patch) { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: "Looking for patcher updates...", | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     await new Promise((res) => setTimeout(res, 1000)); | ||||
|  | ||||
|     mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|       status: "Preparing launch...", | ||||
|     }); | ||||
|  | ||||
|     await updateOsuConfigHashes(osuPath); | ||||
|     await replaceUIFile(osuPath, false); | ||||
|  | ||||
|     const forceUpdateFiles = [ | ||||
|       ".require_update", | ||||
|       "help.txt", | ||||
|       "_pending", | ||||
|     ]; | ||||
|     //TODO: needs testing | ||||
|     try { | ||||
|       for (const updateFileName of forceUpdateFiles) { | ||||
|         const updateFile = path.join(osuPath, updateFileName); | ||||
|         if (fs.existsSync(updateFile)) { | ||||
|           await fs.promises.rm(updateFile, { | ||||
|             force: true, | ||||
|             recursive: (await fs.promises.lstat(updateFile)).isDirectory, | ||||
|         await new Promise((res) => setTimeout(res, 1000)); | ||||
|         const patchFiles = await getEZPPLauncherUpdateFiles(osuPath); | ||||
|         if (patchFiles.length > 0) { | ||||
|           logger.log("EZPPLauncher updates found."); | ||||
|           const patcherDownloader = await downloadEZPPLauncherUpdateFiles( | ||||
|             osuPath, | ||||
|             patchFiles, | ||||
|           ); | ||||
|           let errored = false; | ||||
|           patcherDownloader.eventEmitter.on("error", (data) => { | ||||
|             const filename = data.fileName; | ||||
|             logger.error(`Failed to download/replace ${filename}!`, data.error); | ||||
|             errored = true; | ||||
|             mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|               type: "error", | ||||
|               message: | ||||
|                 `Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`, | ||||
|             }); | ||||
|           }); | ||||
|           patcherDownloader.eventEmitter.on("data", (data) => { | ||||
|             if (data.progress >= 100) { | ||||
|               logger.log(`Downloaded ${data.fileName} successfully.`); | ||||
|             } | ||||
|             mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|               progress: Math.ceil(data.progress), | ||||
|             }); | ||||
|             mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|               status: `Downloading ${data.fileName}(${ | ||||
|                 formatBytes(data.loaded) | ||||
|               }/${formatBytes(data.total)})...`, | ||||
|             }); | ||||
|           }); | ||||
|           await patcherDownloader.startDownload(); | ||||
|           mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|             progress: -1, | ||||
|           }); | ||||
|           if (errored) { | ||||
|             mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|             return; | ||||
|           } | ||||
|           mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|             status: "Patcher is now up to date!", | ||||
|           }); | ||||
|         } else { | ||||
|           mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|             status: "Patcher is up to date!", | ||||
|           }); | ||||
|         } | ||||
|         await new Promise((res) => setTimeout(res, 1000)); | ||||
|       } | ||||
|     } catch {} | ||||
|       if (updateFiles.length > 0) { | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: "Launching osu! updater to verify updates...", | ||||
|         }); | ||||
|         await new Promise((res) => setTimeout(res, 1000)); | ||||
|  | ||||
|     const userConfig = getUserConfig(osuPath); | ||||
|     richPresence.updateVersion(await userConfig.get("LastVersion")); | ||||
|     richPresence.update(); | ||||
|     if (richPresence.hasPresence) { | ||||
|       await userConfig.set("DiscordRichPresence", "0"); | ||||
|     } | ||||
|     await userConfig.set("ShowInterfaceDuringRelax", "1"); | ||||
|     if (currentUser) { | ||||
|       await userConfig.set("CredentialEndpoint", "ez-pp.farm"); | ||||
|       await userConfig.set("SavePassword", "1"); | ||||
|       await userConfig.set("SaveUsername", "1"); | ||||
|       await userConfig.set("Username", currentUser.username); | ||||
|       await userConfig.set("Password", currentUser.password); | ||||
|     } | ||||
|         await new Promise((res) => { | ||||
|           runOsuUpdater(osuPath, async () => { | ||||
|             await new Promise((res) => setTimeout(res, 500)); | ||||
|             const terminationThread = setInterval(async () => { | ||||
|               const osuWindowTitle = windowName.getWindowText("osu!.exe"); | ||||
|               if (osuWindowTitle.length < 0) { | ||||
|                 return; | ||||
|               } | ||||
|               const firstInstance = osuWindowTitle[0]; | ||||
|               if (firstInstance) { | ||||
|                 const processId = firstInstance.processId; | ||||
|                 await fkill(processId, { force: true, silent: true }); | ||||
|                 clearInterval(terminationThread); | ||||
|                 res(); | ||||
|               } | ||||
|             }, 500); | ||||
|           }); | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|     mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|       status: "Launching osu!...", | ||||
|     }); | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|  | ||||
|     const onExitHook = () => { | ||||
|       mainWindow.show(); | ||||
|       mainWindow.focus(); | ||||
|       stopOsuStatus(); | ||||
|       richPresence.updateVersion(); | ||||
|       richPresence.updateStatus({ | ||||
|         state: "Idle in Launcher...", | ||||
|         details: undefined, | ||||
|       }); | ||||
|       richPresence.update(); | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "Waiting for cleanup...", | ||||
|         status: "Preparing launch...", | ||||
|       }); | ||||
|  | ||||
|       setTimeout(async () => { | ||||
|         await replaceUIFile(osuPath, true); | ||||
|       /* await updateOsuConfigHashes(osuPath); */ | ||||
|       logger.log("Replacing UI files..."); | ||||
|       try { | ||||
|         await replaceUIFiles(osuPath, false); | ||||
|         logger.log("UI files replaced successfully."); | ||||
|       } catch (err) { | ||||
|         logger.error("Failed to replace UI files:", err); | ||||
|         mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|           type: "error", | ||||
|           message: "Failed to replace UI files. try restarting EZPPLauncher.", | ||||
|         }); | ||||
|         mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|         osuLoaded = false; | ||||
|       }, 5000); | ||||
|     }; | ||||
|     runOsuWithDevServer(osuPath, "ez-pp.farm", onExitHook); | ||||
|     mainWindow.hide(); | ||||
|     startOsuStatus(); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|     /* mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|       progress: 0, | ||||
|     }); | ||||
|     mainWindow.webContents.send("ezpplauncher:launchprogress", { | ||||
|       progress: 100, | ||||
|     }); */ | ||||
|     return true; | ||||
|       const forceUpdateFiles = [ | ||||
|         ".require_update", | ||||
|         "help.txt", | ||||
|         "_pending", | ||||
|       ]; | ||||
|  | ||||
|       try { | ||||
|         for (const updateFileName of forceUpdateFiles) { | ||||
|           const updateFile = path.join(osuPath, updateFileName); | ||||
|           if (fs.existsSync(updateFile)) { | ||||
|             await fs.promises.rm(updateFile, { | ||||
|               force: true, | ||||
|               recursive: (await fs.promises.lstat(updateFile)).isDirectory, | ||||
|             }); | ||||
|           } | ||||
|         } | ||||
|       } catch (err) { | ||||
|         logger.error("Failed to remove force update files:", err); | ||||
|       } | ||||
|  | ||||
|       const userConfig = getUserConfig(osuPath); | ||||
|       if (richPresence.hasPresence) { | ||||
|         await userConfig.set("DiscordRichPresence", "0"); | ||||
|       } | ||||
|       await userConfig.set("ShowInterfaceDuringRelax", "1"); | ||||
|       if (currentUser) { | ||||
|         await userConfig.set("CredentialEndpoint", "ez-pp.farm"); | ||||
|         await userConfig.set("SavePassword", "1"); | ||||
|         await userConfig.set("SaveUsername", "1"); | ||||
|         await userConfig.set("Username", currentUser.username); | ||||
|         await userConfig.set("Password", currentUser.password); | ||||
|       } | ||||
|  | ||||
|       mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|         status: "Launching osu!...", | ||||
|       }); | ||||
|  | ||||
|       await new Promise((res) => setTimeout(res, 1000)); | ||||
|  | ||||
|       logger.log("Launching osu!..."); | ||||
|  | ||||
|       const onExitHook = () => { | ||||
|         logger.log("osu! has exited."); | ||||
|         mainWindow.show(); | ||||
|         mainWindow.focus(); | ||||
|         stopOsuStatus(); | ||||
|         richPresence.updateUser({ | ||||
|           username: "  ", | ||||
|           id: undefined, | ||||
|         }); | ||||
|         richPresence.updateStatus({ | ||||
|           state: "Idle in Launcher...", | ||||
|           details: undefined, | ||||
|         }); | ||||
|         richPresence.update(); | ||||
|         mainWindow.webContents.send("ezpplauncher:launchstatus", { | ||||
|           status: "Waiting for cleanup...", | ||||
|         }); | ||||
|         const timeStart = performance.now(); | ||||
|         logger.log("Waiting for cleanup..."); | ||||
|  | ||||
|         const cleanup = setInterval(async () => { | ||||
|           const osuUIFile = path.join(osuPath, "osu!ui.dll"); | ||||
|           const osuGameplayFile = path.join(osuPath, "osu!gameplay.dll"); | ||||
|           if (isWritable(osuUIFile) && isWritable(osuGameplayFile)) { | ||||
|             logger.log( | ||||
|               `Cleanup complete, took ${ | ||||
|                 ((performance.now() - timeStart) / 1000).toFixed(3) | ||||
|               } seconds.`, | ||||
|             ); | ||||
|             clearInterval(cleanup); | ||||
|             await replaceUIFiles(osuPath, true); | ||||
|             mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|             osuLoaded = false; | ||||
|           } | ||||
|         }, 1000); | ||||
|       }; | ||||
|       runOsuWithDevServer(osuPath, "ez-pp.farm", onExitHook); | ||||
|       mainWindow.hide(); | ||||
|       startOsuStatus(); | ||||
|       return true; | ||||
|     } catch (err) { | ||||
|       logger.error("Failed to launch", err); | ||||
|       mainWindow.webContents.send("ezpplauncher:alert", { | ||||
|         type: "error", | ||||
|         message: "Failed to launch osu!. Please try again.", | ||||
|       }); | ||||
|       mainWindow.webContents.send("ezpplauncher:launchabort"); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @@ -645,6 +793,13 @@ function createWindow() { | ||||
|       richPresence.connect(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   logger.init(); | ||||
|  | ||||
|   const loggingEnabled = config.get("logging"); | ||||
|   if (loggingEnabled && loggingEnabled == "true") { | ||||
|     logger.enabled = true; | ||||
|   } | ||||
|   // Uncomment the following line of code when app is ready to be packaged. | ||||
|   // loadURL(mainWindow); | ||||
|  | ||||
|   | ||||
							
								
								
									
										1113
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1113
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "ezpplauncher-next", | ||||
|   "version": "2.1.1", | ||||
|   "version": "2.1.4", | ||||
|   "description": "EZPPLauncher rewritten with Svelte.", | ||||
|   "private": false, | ||||
|   "license": "MIT", | ||||
| @@ -61,7 +61,6 @@ | ||||
|     "@rollup/plugin-node-resolve": "^15.2.3", | ||||
|     "@rollup/plugin-terser": "^0.4.4", | ||||
|     "@rollup/plugin-typescript": "^11.1.5", | ||||
|     "@sveltejs/vite-plugin-svelte": "^3.0.1", | ||||
|     "@tsconfig/svelte": "^5.0.2", | ||||
|     "autoprefixer": "^10.4.16", | ||||
|     "concurrently": "^8.2.2", | ||||
|   | ||||
| @@ -115,3 +115,9 @@ ipcRenderer.addListener("ezpplauncher:update", (e, args) => { | ||||
|     new CustomEvent("update", { detail: args }), | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| ipcRenderer.addListener("ezpplauncher:open-settings", (e, args) => { | ||||
|   window.dispatchEvent( | ||||
|     new CustomEvent("open-settings"), | ||||
|   ); | ||||
| }); | ||||
|   | ||||
| @@ -66,6 +66,10 @@ | ||||
|     window.dispatchEvent(new CustomEvent("updateExit")); | ||||
|   }); | ||||
|  | ||||
|   window.addEventListener("open-settings", (e) => { | ||||
|     currentPage.set(Page.Settings); | ||||
|   }); | ||||
|  | ||||
|   window.addEventListener("launchStatusUpdate", (e) => { | ||||
|     const status = (e as CustomEvent).detail.status; | ||||
|     launchStatus.set(status); | ||||
|   | ||||
| @@ -26,12 +26,12 @@ | ||||
|     green: "bg-green-600 dark:bg-green-500", | ||||
|     yellow: "bg-yellow-400", | ||||
|     purple: "bg-purple-600 dark:bg-purple-500", | ||||
|     indigo: "bg-indigo-600 dark:bg-indigo-500", | ||||
|     indigo: "bg-indigo-600 dark:bg-indigo-500" | ||||
|   }; | ||||
|  | ||||
|   let _progress = tweened(0, { | ||||
|     duration: tweenDuration, | ||||
|     easing, | ||||
|     easing | ||||
|   }); | ||||
|  | ||||
|   $: { | ||||
| @@ -50,8 +50,12 @@ | ||||
|     > | ||||
|     <span class="text-sm font-medium text-blue-700 dark:text-white" | ||||
|       >{animate | ||||
|         ? $_progress.toFixed(precision) | ||||
|         : progress.toFixed(precision)}%</span | ||||
|         ? isNaN($_progress) | ||||
|           ? parseInt("100").toFixed(precision) | ||||
|           : $_progress.toFixed(precision) | ||||
|         : isNaN(progress) | ||||
|           ? parseInt("100").toFixed(precision) | ||||
|           : progress.toFixed(precision)}%</span | ||||
|     > | ||||
|   </div> | ||||
| {/if} | ||||
| @@ -63,7 +67,13 @@ | ||||
|         class={twJoin(labelInsideClass, barColors[color])} | ||||
|         style="width: {animate ? $_progress : progress}%" | ||||
|       > | ||||
|         {animate ? $_progress.toFixed(precision) : progress.toFixed(precision)}% | ||||
|         {animate | ||||
|           ? isNaN($_progress) | ||||
|             ? parseInt("100").toFixed(precision) | ||||
|             : $_progress.toFixed(precision) | ||||
|           : isNaN(progress) | ||||
|             ? parseInt("100").toFixed(precision) | ||||
|             : progress.toFixed(precision)}% | ||||
|       </div> | ||||
|     {:else} | ||||
|       <div | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| <script lang="ts"> | ||||
|   import { Button, Checkbox } from "flowbite-svelte"; | ||||
|   import { Button } from "flowbite-svelte"; | ||||
|   import Progressbar from "../lib/Progressbar.svelte"; | ||||
|   import { | ||||
|     launching, | ||||
|     patch, | ||||
|     launchStatus, | ||||
|     launchPercentage, | ||||
|     launchPercentage | ||||
|   } from "./../storage/localStore"; | ||||
|   let progressbarFix = true; | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| <script lang="ts"> | ||||
|   import { Input, Button, Spinner, Checkbox } from "flowbite-svelte"; | ||||
|   import type { User } from "../types/user"; | ||||
|   import type { Error } from "../types/error"; | ||||
|   import { type User } from "../types/user"; | ||||
|   import { currentPage, currentUser, startup } from "../storage/localStore"; | ||||
|   import toast from "svelte-french-toast"; | ||||
|   import { Page } from "../consts/pages"; | ||||
| @@ -43,14 +42,14 @@ | ||||
|             position: "bottom-center", | ||||
|             className: | ||||
|               "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", | ||||
|             duration: 3000, | ||||
|             duration: 3000 | ||||
|           }); | ||||
|         }, | ||||
|         { once: true } | ||||
|       ); | ||||
|       window.dispatchEvent( | ||||
|         new CustomEvent("login-attempt", { | ||||
|           detail: { username, password, saveCredentials }, | ||||
|           detail: { username, password, saveCredentials } | ||||
|         }) | ||||
|       ); | ||||
|     }); | ||||
| @@ -59,13 +58,13 @@ | ||||
|       { | ||||
|         loading: "Logging in...", | ||||
|         success: "Successfully logged in!", | ||||
|         error: "Invalid Username or Password!", | ||||
|         error: "Invalid Username or Password!" | ||||
|       }, | ||||
|       { | ||||
|         position: "bottom-center", | ||||
|         className: | ||||
|           "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", | ||||
|         duration: 3000, | ||||
|         duration: 3000 | ||||
|       } | ||||
|     ); | ||||
|   }; | ||||
| @@ -87,7 +86,7 @@ | ||||
|               position: "bottom-center", | ||||
|               className: | ||||
|                 "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", | ||||
|               duration: 3000, | ||||
|               duration: 3000 | ||||
|             }); | ||||
|             return; | ||||
|           } | ||||
| @@ -104,7 +103,7 @@ | ||||
|             position: "bottom-center", | ||||
|             className: | ||||
|               "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", | ||||
|             duration: 3000, | ||||
|             duration: 3000 | ||||
|           }); | ||||
|           loading = false; | ||||
|         }, | ||||
| @@ -117,13 +116,13 @@ | ||||
|       { | ||||
|         loading: "Logging in...", | ||||
|         success: "Successfully logged in!", | ||||
|         error: "Failed to login.", | ||||
|         error: "Failed to login." | ||||
|       }, | ||||
|       { | ||||
|         position: "bottom-center", | ||||
|         className: | ||||
|           "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", | ||||
|         duration: 3000, | ||||
|         duration: 3000 | ||||
|       } | ||||
|     ); | ||||
|   }; | ||||
| @@ -135,7 +134,7 @@ | ||||
|       position: "bottom-center", | ||||
|       className: | ||||
|         "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", | ||||
|       duration: 3000, | ||||
|       duration: 3000 | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <script lang="ts"> | ||||
|   import { Button, ButtonGroup, Input, Toggle } from "flowbite-svelte"; | ||||
|   import { FileSearchSolid, FolderSolid } from "flowbite-svelte-icons"; | ||||
|   import { patch, presence } from "./../storage/localStore"; | ||||
|   import { patch, presence, logging } from "./../storage/localStore"; | ||||
|  | ||||
|   let folderPath: string = ""; | ||||
|  | ||||
| @@ -12,8 +12,10 @@ | ||||
|     const settingPresence = settings.find( | ||||
|       (setting) => setting.key == "presence" | ||||
|     ); | ||||
|     const settingLogging = settings.find((setting) => setting.key == "logging"); | ||||
|     patch.set(settingPatch ? settingPatch.val == "true" : true); | ||||
|     presence.set(settingPresence ? settingPresence.val == "true" : true); | ||||
|     logging.set(settingLogging ? settingLogging.val == "true" : false); | ||||
|     folderPath = osuPath ? osuPath.val : ""; | ||||
|   }); | ||||
|   window.dispatchEvent(new CustomEvent("settings-get")); | ||||
| @@ -39,19 +41,18 @@ | ||||
|       new CustomEvent("setting-update", { detail: { presence: $presence } }) | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   const toggleLogging = () => { | ||||
|     logging.set(!$logging); | ||||
|     window.dispatchEvent( | ||||
|       new CustomEvent("setting-update", { detail: { logging: $logging } }) | ||||
|     ); | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <main | ||||
|   class="h-[265px] flex flex-col justify-start p-3 animate-fadeIn opacity-0" | ||||
| > | ||||
|   <div class="flex flex-col gap-2 p-3"> | ||||
|     <Toggle class="w-fit" bind:checked={$presence} on:click={togglePresence} | ||||
|       >Discord Presence</Toggle | ||||
|     > | ||||
|     <Toggle class="w-fit" bind:checked={$patch} on:click={togglePatching} | ||||
|       >Patching</Toggle | ||||
|     > | ||||
|   </div> | ||||
|   <div | ||||
|     class="container flex flex-col items-center justify-center gap-5 rounded-lg p-3" | ||||
|   > | ||||
| @@ -77,4 +78,15 @@ | ||||
|       </Button> | ||||
|     </ButtonGroup> | ||||
|   </div> | ||||
|   <div class="flex flex-col gap-2 p-3"> | ||||
|     <Toggle class="w-fit" bind:checked={$presence} on:click={togglePresence} | ||||
|       >Discord Presence</Toggle | ||||
|     > | ||||
|     <Toggle class="w-fit" bind:checked={$patch} on:click={togglePatching} | ||||
|       >Patching</Toggle | ||||
|     > | ||||
|     <Toggle class="w-fit" bind:checked={$logging} on:click={toggleLogging} | ||||
|       >Debug Logging</Toggle | ||||
|     > | ||||
|   </div> | ||||
| </main> | ||||
|   | ||||
| @@ -7,8 +7,11 @@ export const updateAvailable = writable(false); | ||||
| export const launching = writable(false); | ||||
| export const launchStatus = writable("Waiting..."); | ||||
| export const launchPercentage = writable(-1); | ||||
|  | ||||
| export const currentUser: Writable<undefined | User> = writable(undefined); | ||||
| export const currentPage = writable(Page.Login); | ||||
|  | ||||
| export const osuPath: Writable<undefined | string> = writable(undefined); | ||||
| export const patch = writable(true); | ||||
| export const presence = writable(true); | ||||
| export const currentUser: Writable<undefined | User> = writable(undefined); | ||||
| export const currentPage = writable(Page.Login); | ||||
| export const logging = writable(false); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| const { vitePreprocess } = require("@sveltejs/vite-plugin-svelte"); | ||||
| const preprocess = require("svelte-preprocess"); | ||||
|  | ||||
| const config = { | ||||
|   preprocess: [vitePreprocess({})], | ||||
|   preprocess: [preprocess()], | ||||
| }; | ||||
|  | ||||
| module.exports = config; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user