mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-13 07:34:36 +01:00
Compare commits
22 Commits
v1.0.2-alp
...
resource_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0c545d032 | ||
|
|
88dd7b854f | ||
|
|
e159bc2cdb | ||
|
|
cc4600ec77 | ||
|
|
f37e44a88c | ||
|
|
4f806efc93 | ||
|
|
083de896b3 | ||
|
|
9c64c1f282 | ||
|
|
aa10a908ad | ||
|
|
bae193050f | ||
|
|
fc5ffae1e2 | ||
|
|
ab0e05ffe1 | ||
|
|
88a1740b91 | ||
|
|
f24f3af377 | ||
|
|
abafc94379 | ||
|
|
b2453e7c4d | ||
|
|
4fc90ee333 | ||
|
|
0ec8782f48 | ||
|
|
33c67eef06 | ||
|
|
b3585927ca | ||
|
|
3008f50e1f | ||
|
|
99293ad7cf |
@@ -14,6 +14,8 @@
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"game_exec": "Set Game Executable",
|
||||
"game_version": "Set Game Version",
|
||||
"emergency_metadata": "Emergency Metadata Restore",
|
||||
"grasscutter_jar": "Set Grasscutter JAR",
|
||||
"toggle_encryption": "Toggle Encryption",
|
||||
"java_path": "Set Custom Java Path",
|
||||
@@ -57,5 +59,8 @@
|
||||
"gc_stable_data": "Download the current stable Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||
"gc_dev_data": "Download the latest development Grasscutter data files, which does not come with a jar file. This is useful for updating.",
|
||||
"resources": "These are also required to run a Grasscutter server. This button will be grey if you have an existing resources folder with contents inside"
|
||||
},
|
||||
"swag": {
|
||||
"akebi": "Set Akebi Executable"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ windows_subsystem = "windows"
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{sync::Mutex, collections::HashMap};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use std::thread;
|
||||
use sysinfo::{System, SystemExt};
|
||||
@@ -23,9 +22,6 @@ mod web;
|
||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
|
||||
fn main() {
|
||||
// Start the game process watcher.
|
||||
process_watcher();
|
||||
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
@@ -53,22 +49,37 @@ fn main() {
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url
|
||||
web::valid_url,
|
||||
web::web_get
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
fn process_watcher() {
|
||||
// Every 5 seconds, see if the game process is still running.
|
||||
// If it is not, then we assume the game has closed and disable the proxy
|
||||
// to prevent any requests from being sent to the game.
|
||||
#[tauri::command]
|
||||
fn is_game_running() -> bool {
|
||||
// Grab the game process name
|
||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
// Start a thread so as to not block the main thread.
|
||||
thread::spawn(|| {
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(window: tauri::Window,process: String) {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||
|
||||
window.listen("disable_process_watcher", |_e| {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
});
|
||||
|
||||
println!("Starting process watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut system = System::new_all();
|
||||
|
||||
loop {
|
||||
thread::sleep(std::time::Duration::from_secs(5));
|
||||
|
||||
// Refresh system info
|
||||
system.refresh_all();
|
||||
|
||||
@@ -81,28 +92,18 @@ fn process_watcher() {
|
||||
|
||||
// If the game process closes, disable the proxy.
|
||||
if !exists {
|
||||
println!("Game closed");
|
||||
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||
disconnect();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
thread::sleep(std::time::Duration::from_secs(5));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_game_running() -> bool {
|
||||
// Grab the game process name
|
||||
let proc = WATCH_GAME_PROCESS.lock().unwrap().to_string();
|
||||
|
||||
!proc.is_empty()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn enable_process_watcher(process: String) {
|
||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn connect(port: u16, certificate_path: String) {
|
||||
// Log message to console.
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use duct::cmd;
|
||||
|
||||
use crate::file_helpers;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_program(path: String) {
|
||||
// Open the program from the specified path.
|
||||
open::that(&path).unwrap();
|
||||
// Open in new thread to prevent blocking.
|
||||
std::thread::spawn(move || {
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(&path).unwrap_or(());
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -15,4 +15,10 @@ pub(crate) async fn valid_url(url: String) -> bool {
|
||||
let response = client.get(url).header(USER_AGENT, "cultivation").send().await.unwrap();
|
||||
|
||||
response.status().as_str() == "200"
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn web_get(url: String) -> String {
|
||||
// Send a GET request to the specified URL and send the response body back to the client.
|
||||
query(&url).await
|
||||
}
|
||||
67
src/resources/icons/akebi.svg
Normal file
67
src/resources/icons/akebi.svg
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M7045 10204 c-301 -50 -596 -277 -731 -564 -96 -204 -111 -463 -40
|
||||
-692 16 -52 14 -58 -26 -58 -62 0 -220 -85 -331 -178 -24 -20 -105 -91 -178
|
||||
-157 -74 -66 -177 -156 -229 -200 -208 -175 -629 -573 -1001 -944 -152 -152
|
||||
-286 -284 -297 -294 -18 -17 -27 -17 -75 -8 -96 21 -249 29 -361 20 -321 -26
|
||||
-613 -162 -836 -389 -341 -346 -457 -846 -303 -1301 l36 -106 -66 -100 c-102
|
||||
-155 -212 -343 -303 -518 -353 -683 -556 -1455 -531 -2025 6 -136 29 -341 47
|
||||
-415 36 -156 58 -234 92 -335 47 -137 52 -147 134 -315 90 -182 113 -220 223
|
||||
-375 376 -528 945 -931 1571 -1113 261 -76 439 -94 503 -52 27 17 30 17 105
|
||||
-4 125 -35 209 -50 345 -62 107 -9 152 -8 279 6 118 13 180 15 277 8 214 -14
|
||||
475 -9 566 11 144 31 312 80 395 114 14 6 68 27 120 48 91 36 302 135 365 172
|
||||
180 104 373 265 507 422 346 407 518 977 520 1725 0 287 -10 448 -48 755 -43
|
||||
357 -95 631 -208 1100 -24 102 -47 199 -51 217 -5 26 -1 41 25 78 48 70 108
|
||||
204 132 297 32 118 31 366 0 482 -75 272 -258 503 -507 639 l-77 42 -68 202
|
||||
c-37 112 -70 213 -73 225 -3 13 -14 57 -26 98 -12 41 -33 134 -47 205 -23 115
|
||||
-26 154 -27 345 0 223 1 232 62 580 44 245 54 349 47 490 -10 234 -52 334
|
||||
-206 495 -98 103 -128 150 -167 261 -24 70 -27 94 -28 209 0 135 10 185 58
|
||||
280 49 99 182 235 282 288 179 96 373 112 464 39 62 -49 101 -117 101 -174 0
|
||||
-81 -49 -152 -117 -172 -13 -3 -32 -8 -42 -11 -13 -4 -34 7 -65 34 -26 22 -63
|
||||
45 -84 52 -107 32 -219 -73 -198 -185 7 -37 89 -141 129 -163 137 -76 279 -77
|
||||
428 -3 102 52 190 149 236 263 36 89 42 239 14 326 -36 108 -112 215 -206 286
|
||||
-80 62 -158 92 -265 105 -105 11 -142 11 -245 -6z m-574 -1640 c138 -41 189
|
||||
-186 160 -459 -13 -115 -45 -328 -62 -405 -19 -86 -49 -386 -49 -493 0 -163
|
||||
17 -299 71 -552 7 -36 35 -132 102 -350 l24 -80 -31 -2 c-114 -9 -233 -28
|
||||
-305 -50 -193 -55 -383 -185 -509 -347 -12 -16 -24 -27 -27 -24 -6 5 -22 125
|
||||
-30 223 -3 39 -10 113 -15 165 -10 105 -26 365 -35 595 -5 120 -11 158 -26
|
||||
192 -34 70 -110 104 -187 84 -44 -12 -77 -47 -217 -230 -38 -51 -90 -118 -115
|
||||
-150 -25 -32 -70 -91 -100 -131 -30 -40 -64 -84 -76 -98 l-21 -25 -37 67 c-89
|
||||
159 -229 312 -386 419 l-81 55 213 214 c371 372 775 751 1063 997 94 79 193
|
||||
166 220 192 78 75 185 157 239 184 60 30 136 33 217 9z m-2317 -1783 c351 -93
|
||||
611 -365 682 -712 22 -109 15 -291 -16 -403 -107 -396 -472 -688 -900 -722
|
||||
l-85 -6 70 50 c180 127 282 307 293 513 16 326 -203 611 -533 695 -90 22 -256
|
||||
23 -347 1 -136 -33 -284 -119 -366 -212 -22 -25 -42 -45 -44 -45 -9 0 11 102
|
||||
33 175 104 343 411 610 782 681 104 20 329 12 431 -15z m1310 -406 c21 -268
|
||||
29 -351 51 -545 31 -262 72 -520 120 -765 26 -130 66 -320 75 -360 5 -22 28
|
||||
-128 50 -235 23 -107 45 -213 50 -235 41 -178 113 -614 145 -880 48 -401 57
|
||||
-943 20 -1215 -55 -407 -186 -801 -368 -1102 -107 -178 -168 -262 -258 -355
|
||||
-146 -150 -290 -212 -494 -213 -252 0 -520 107 -800 319 -274 207 -539 609
|
||||
-674 1021 -112 339 -154 603 -154 960 0 278 18 425 82 671 92 348 301 725 615
|
||||
1106 l50 61 87 16 c472 86 871 430 1017 876 42 128 55 211 58 383 l4 168 120
|
||||
157 c169 220 194 252 196 249 1 -1 5 -38 8 -82z m1043 -511 c-144 -112 -216
|
||||
-335 -168 -519 27 -101 69 -171 148 -245 74 -69 141 -104 246 -127 67 -14 90
|
||||
-15 159 -5 145 21 277 102 353 218 60 90 86 178 86 287 0 51 -3 102 -6 112
|
||||
-13 40 26 -17 61 -89 95 -196 84 -447 -28 -633 -183 -303 -569 -423 -917 -284
|
||||
-171 69 -330 226 -398 394 -152 373 46 791 432 913 73 23 82 17 32 -22z
|
||||
m-3393 -1044 c104 -70 239 -133 356 -166 52 -15 97 -28 98 -30 4 -2 -15 -29
|
||||
-83 -124 -60 -83 -171 -254 -217 -335 -185 -323 -305 -691 -349 -1070 -19
|
||||
-158 -16 -519 5 -690 34 -284 98 -554 186 -790 44 -120 179 -400 231 -480 171
|
||||
-266 299 -420 509 -610 50 -45 61 -59 47 -63 -47 -10 -352 117 -535 224 -700
|
||||
409 -1152 1060 -1253 1804 -19 136 -16 466 5 625 78 591 314 1222 678 1813
|
||||
l70 113 87 -84 c48 -46 122 -107 165 -137z m3105 -469 c175 -88 288 -116 481
|
||||
-116 190 0 316 30 470 111 30 16 58 30 61 32 15 7 131 -523 183 -833 64 -382
|
||||
88 -661 88 -1015 -1 -346 -24 -553 -94 -820 -112 -430 -342 -768 -675 -993
|
||||
-139 -94 -386 -208 -643 -297 -67 -24 -174 -50 -255 -64 -94 -17 -350 -22
|
||||
-359 -8 -2 4 16 26 40 47 42 37 171 184 204 231 176 256 269 425 360 654 118
|
||||
295 181 554 221 907 19 168 21 222 16 503 -4 173 -11 369 -16 434 -30 341
|
||||
-103 828 -177 1168 -15 73 -18 108 -7 108 4 0 50 -22 102 -49z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -62,20 +62,27 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
|
||||
height: 100%;
|
||||
max-height: 60px;
|
||||
}
|
||||
|
||||
#officialPlay {
|
||||
width: 60%
|
||||
.ServerLaunchButtons .BigButton {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#officialPlay {
|
||||
max-width: 60%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#akebiLaunch,
|
||||
#serverLaunch {
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.AkebiIcon,
|
||||
.ServerIcon {
|
||||
height: 20px;
|
||||
filter: invert(28%) sepia(28%) saturate(1141%) hue-rotate(352deg) brightness(96%) contrast(88%);
|
||||
|
||||
@@ -8,6 +8,8 @@ import { translate } from '../../utils/language'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
import Server from '../../resources/icons/server.svg'
|
||||
import Akebi from '../../resources/icons/akebi.svg'
|
||||
|
||||
import './ServerLaunchSection.css'
|
||||
import {dataDir} from '@tauri-apps/api/path'
|
||||
|
||||
@@ -29,6 +31,8 @@ interface IState {
|
||||
|
||||
httpsLabel: string;
|
||||
httpsEnabled: boolean;
|
||||
|
||||
swag: boolean;
|
||||
}
|
||||
|
||||
export default class ServerLaunchSection extends React.Component<IProps, IState> {
|
||||
@@ -45,11 +49,13 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
portPlaceholder: '',
|
||||
portHelpText: '',
|
||||
httpsLabel: '',
|
||||
httpsEnabled: false
|
||||
httpsEnabled: false,
|
||||
swag: false
|
||||
}
|
||||
|
||||
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
|
||||
this.playGame = this.playGame.bind(this)
|
||||
this.launchAkebi = this.launchAkebi.bind(this)
|
||||
this.setIp = this.setIp.bind(this)
|
||||
this.setPort = this.setPort.bind(this)
|
||||
this.toggleHttps = this.toggleHttps.bind(this)
|
||||
@@ -69,6 +75,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
portHelpText: await translate('help.port_help_text'),
|
||||
httpsLabel: await translate('main.https_enable'),
|
||||
httpsEnabled: config.https_enabled || false,
|
||||
swag: config.swag_mode || false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,7 +92,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
await saveConfig(config)
|
||||
}
|
||||
|
||||
async playGame() {
|
||||
async playGame(exe?: string, proc_name?: string) {
|
||||
const config = await getConfig()
|
||||
|
||||
if (!config.game_install_path) return alert('Game path not set!')
|
||||
@@ -107,7 +114,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
// Set IP
|
||||
await invoke('set_proxy_addr', { addr: (this.state.httpsEnabled ? 'https':'http') + '://' + this.state.ip + ':' + this.state.port })
|
||||
await invoke('enable_process_watcher', {
|
||||
process: game_exe
|
||||
process: proc_name || game_exe
|
||||
})
|
||||
|
||||
// Connect to proxy
|
||||
@@ -133,11 +140,11 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
|
||||
// Launch the program
|
||||
const gameExists = await invoke('dir_exists', {
|
||||
path: config.game_install_path
|
||||
path: exe || config.game_install_path
|
||||
})
|
||||
|
||||
if (gameExists) await invoke('run_program', { path: config.game_install_path })
|
||||
else alert('Game not found! At: ' + config.game_install_path)
|
||||
if (gameExists) await invoke('run_program', { path: exe || config.game_install_path })
|
||||
else alert('Game not found! At: ' + (exe || config.game_install_path))
|
||||
}
|
||||
|
||||
async launchServer() {
|
||||
@@ -161,6 +168,16 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
})
|
||||
}
|
||||
|
||||
async launchAkebi() {
|
||||
const config = await getConfig()
|
||||
|
||||
// Get game exe from game path, so we can watch it
|
||||
const pathArr = config.game_install_path.replace(/\\/g, '/').split('/')
|
||||
const gameExec = pathArr[pathArr.length - 1]
|
||||
|
||||
await this.playGame(config.akebi_path, gameExec)
|
||||
}
|
||||
|
||||
setIp(text: string) {
|
||||
this.setState({
|
||||
ip: text
|
||||
@@ -205,13 +222,19 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
<Checkbox id="httpsEnable" label={this.state.httpsLabel} onChange={this.toggleHttps} checked={this.state.httpsEnabled} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
||||
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
|
||||
{
|
||||
this.state.swag && (
|
||||
<BigButton onClick={this.launchAkebi} id="akebiLaunch">
|
||||
<img className="AkebiIcon" id="akebiIcon" src={Akebi} />
|
||||
</BigButton>
|
||||
)
|
||||
}
|
||||
<BigButton onClick={this.launchServer} id="serverLaunch">
|
||||
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
||||
</BigButton>
|
||||
|
||||
@@ -25,4 +25,30 @@
|
||||
#version {
|
||||
margin: 0px 6px;
|
||||
color: #434343;
|
||||
}
|
||||
|
||||
#unassumingButton {
|
||||
font-weight: bold;
|
||||
margin: 0px 6px;
|
||||
color: #141414;
|
||||
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#unassumingButton:hover {
|
||||
color: #434343;
|
||||
}
|
||||
|
||||
#unassumingButton.spin {
|
||||
color: #fff;
|
||||
animation: spin 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import gameBtn from '../../resources/icons/game.svg'
|
||||
import Tr from '../../utils/language'
|
||||
|
||||
import './TopBar.css'
|
||||
import { getConfig, setConfigOption } from '../../utils/configuration'
|
||||
|
||||
interface IProps {
|
||||
optFunc: () => void;
|
||||
@@ -19,13 +20,21 @@ interface IProps {
|
||||
|
||||
interface IState {
|
||||
version: string;
|
||||
clicks: number;
|
||||
intv: NodeJS.Timeout | null;
|
||||
}
|
||||
|
||||
export default class TopBar extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props)
|
||||
|
||||
this.state = { version: '0.0.0' }
|
||||
this.state = {
|
||||
version: '0.0.0',
|
||||
clicks: 0,
|
||||
intv: null
|
||||
}
|
||||
|
||||
this.activateClick = this.activateClick.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -41,6 +50,39 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
appWindow.minimize()
|
||||
}
|
||||
|
||||
async activateClick() {
|
||||
const config = await getConfig()
|
||||
|
||||
// They already got it, no need to reactivate
|
||||
if (config.swag_mode) return
|
||||
|
||||
if (this.state.clicks === 2) {
|
||||
setTimeout(() => {
|
||||
// Gotta clear it so it goes back to regular colors
|
||||
this.setState({
|
||||
clicks: 0
|
||||
})
|
||||
}, 600)
|
||||
|
||||
// Activate... SWAG MODE
|
||||
await setConfigOption('swag_mode', true)
|
||||
|
||||
// Reload the window
|
||||
window.location.reload()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.state.clicks < 3) {
|
||||
this.setState({
|
||||
clicks: this.state.clicks + 1,
|
||||
intv: setTimeout(() => this.setState({ clicks: 0 }), 1500)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="TopBar" id="topBarContainer" data-tauri-drag-region>
|
||||
@@ -50,6 +92,16 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
</span>
|
||||
<span data-tauri-drag-region id="version">{this.state?.version}</span>
|
||||
</div>
|
||||
{
|
||||
/**
|
||||
* HEY YOU
|
||||
*
|
||||
* If you're looking at the source code to find the swag mode thing, that's okay! If you're not, move along...
|
||||
* Just do me a favor and don't go telling everyone about how you found it. If you are just helping someone who
|
||||
* for some reason needs it, that's fine, but not EVERYONE needs it, which is why it exists in the first place.
|
||||
*/
|
||||
}
|
||||
<div id="unassumingButton" className={this.state.clicks === 2 ? 'spin' : ''} onClick={this.activateClick}>?</div>
|
||||
<div className="TopBtns" id="topBarButtonContainer">
|
||||
<div id="closeBtn" onClick={this.handleClose} className='TopButton'>
|
||||
<img src={closeIcon} alt="close" />
|
||||
@@ -63,9 +115,9 @@ export default class TopBar extends React.Component<IProps, IState> {
|
||||
<div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}>
|
||||
<img src={downBtn} alt="downloads" />
|
||||
</div>
|
||||
{/* <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
|
||||
<div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
|
||||
<img src={gameBtn} alt="game" />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -12,12 +12,7 @@ import { getConfigOption, setConfigOption } from '../../../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
|
||||
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||
const STABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip'
|
||||
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://github.com/Koko-boya/Grasscutter_Resources/archive/refs/heads/main.zip'
|
||||
import { getVersionCache, VersionData } from '../../../utils/resources'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
@@ -30,6 +25,7 @@ interface IState {
|
||||
repo_downloading: boolean
|
||||
grasscutter_set: boolean
|
||||
resources_exist: boolean
|
||||
version_data: VersionData | null
|
||||
}
|
||||
|
||||
export default class Downloads extends React.Component<IProps, IState> {
|
||||
@@ -41,7 +37,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
resources_downloading: this.props.downloadManager.downloadingResources(),
|
||||
repo_downloading: this.props.downloadManager.downloadingRepo(),
|
||||
grasscutter_set: false,
|
||||
resources_exist: false
|
||||
resources_exist: false,
|
||||
version_data: null
|
||||
}
|
||||
|
||||
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
|
||||
@@ -55,6 +52,11 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async componentDidMount() {
|
||||
const gc_path = await getConfigOption('grasscutter_path')
|
||||
const versionData = await getVersionCache()
|
||||
|
||||
this.setState({
|
||||
version_data: versionData,
|
||||
})
|
||||
|
||||
listen('jar_extracted', () => {
|
||||
this.setState({ grasscutter_set: true }, this.forceUpdate)
|
||||
@@ -109,7 +111,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterStableRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
this.props.downloadManager.addDownload(this.state.version_data?.stable, folder + '\\grasscutter_repo.zip', () =>{
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@@ -118,7 +120,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterDevRepo() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', () =>{
|
||||
this.props.downloadManager.addDownload(this.state.version_data?.dev, folder + '\\grasscutter_repo.zip', () =>{
|
||||
unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@@ -127,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterStable() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(STABLE_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
this.props.downloadManager.addDownload(this.state.version_data?.stableJar, folder + '\\grasscutter.zip', () =>{
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@@ -139,7 +141,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterLatest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', () =>{
|
||||
this.props.downloadManager.addDownload(this.state.version_data?.devJar, folder + '\\grasscutter.zip', () =>{
|
||||
unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
|
||||
})
|
||||
|
||||
@@ -151,7 +153,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadResources() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||
this.props.downloadManager.addDownload(this.state.version_data?.resources, folder + '\\resources.zip', async () => {
|
||||
// Delete the existing folder if it exists
|
||||
if (await invoke('dir_exists', {
|
||||
path: folder + '\\resources'
|
||||
@@ -200,7 +202,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCStable">
|
||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
||||
<BigButton disabled={this.state.grasscutter_downloading || !this.state.version_data?.stableJar} onClick={this.downloadGrasscutterStable} id="grasscutterStableBtn" >
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@@ -215,7 +217,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCDev">
|
||||
<BigButton disabled={this.state.grasscutter_downloading} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
||||
<BigButton disabled={this.state.grasscutter_downloading || !this.state.version_data?.devJar} onClick={this.downloadGrasscutterLatest} id="grasscutterLatestBtn" >
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@@ -233,7 +235,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCStableData">
|
||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
||||
<BigButton disabled={this.state.repo_downloading || !this.state.version_data?.stable} onClick={this.downloadGrasscutterStableRepo} id="grasscutterStableRepo" >
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@@ -248,7 +250,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonGCDevData">
|
||||
<BigButton disabled={this.state.repo_downloading} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
||||
<BigButton disabled={this.state.repo_downloading || !this.state.version_data?.dev} onClick={this.downloadGrasscutterStableRepo} id="grasscutterDevRepo" >
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
@@ -264,7 +266,11 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</HelpButton>
|
||||
</div>
|
||||
<div className='DownloadValue' id="downloadMenuButtonResources">
|
||||
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
||||
<BigButton disabled={
|
||||
this.state.resources_downloading
|
||||
|| !this.state.grasscutter_set
|
||||
|| this.state.resources_exist
|
||||
|| !this.state.version_data?.resources} onClick={this.downloadResources} id="resourcesBtn" >
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,7 @@ import DirInput from '../common/DirInput'
|
||||
import BigButton from '../common/BigButton'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import { unzip } from '../../../utils/zipUtils'
|
||||
|
||||
const GAME_DOWNLOAD = ''
|
||||
import { getVersionCache } from '../../../utils/resources'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
@@ -20,6 +19,7 @@ interface IState {
|
||||
gameDownloading: boolean;
|
||||
gameDownloadFolder: string;
|
||||
dirPlaceholder: string;
|
||||
clientDownloadLink: string | null | undefined;
|
||||
}
|
||||
|
||||
export default class Downloads extends React.Component<IProps, IState> {
|
||||
@@ -29,23 +29,25 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
gameDownloading: false,
|
||||
gameDownloadFolder: '',
|
||||
dirPlaceholder: ''
|
||||
dirPlaceholder: '',
|
||||
clientDownloadLink: ''
|
||||
}
|
||||
|
||||
this.downloadGame = this.downloadGame.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.setState({
|
||||
dirPlaceholder: await translate('components.select_folder')
|
||||
})
|
||||
const versionCache = await getVersionCache()
|
||||
|
||||
console.log(this.state)
|
||||
this.setState({
|
||||
dirPlaceholder: await translate('components.select_folder'),
|
||||
clientDownloadLink: versionCache?.client_download_link
|
||||
})
|
||||
}
|
||||
|
||||
async downloadGame() {
|
||||
const folder = this.state.gameDownloadFolder
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', () =>{
|
||||
this.props.downloadManager.addDownload(this.state.clientDownloadLink, folder + '\\game.zip', () =>{
|
||||
unzip(folder + '\\game.zip', folder + '\\', () => {
|
||||
this.setState({
|
||||
gameDownloading: false
|
||||
@@ -63,7 +65,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
<Menu heading='Download Game' closeFn={this.props.closeFn} className="GameDownloadMenu">
|
||||
<div className="GameDownload">
|
||||
{
|
||||
this.state.gameDownloadFolder !== '' && !this.state.gameDownloading ?
|
||||
this.state.gameDownloadFolder !== '' && !this.state.gameDownloading && this.state.clientDownloadLink ?
|
||||
<BigButton id="downloadGameBtn" onClick={this.downloadGame}>Download Game</BigButton>
|
||||
: <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
border-radius: 10px;
|
||||
|
||||
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.2);
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.Menu::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MenuInner {
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as server from '../../../utils/server'
|
||||
|
||||
import './Options.css'
|
||||
import BigButton from '../common/BigButton'
|
||||
import { cacheLauncherResources, getVersionCache, getVersions } from '../../../utils/resources'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void;
|
||||
@@ -20,6 +21,8 @@ interface IProps {
|
||||
interface IState {
|
||||
game_install_path: string
|
||||
grasscutter_path: string
|
||||
client_version: string
|
||||
meta_download: string | null | undefined
|
||||
java_path: string
|
||||
grasscutter_with_game: boolean
|
||||
language_options: { [key: string]: string }[],
|
||||
@@ -28,6 +31,10 @@ interface IState {
|
||||
themes: string[]
|
||||
theme: string
|
||||
encryption: boolean
|
||||
swag: boolean
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: string
|
||||
}
|
||||
|
||||
export default class Options extends React.Component<IProps, IState> {
|
||||
@@ -37,6 +44,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.state = {
|
||||
game_install_path: '',
|
||||
grasscutter_path: '',
|
||||
client_version: '',
|
||||
meta_download: '',
|
||||
java_path: '',
|
||||
grasscutter_with_game: false,
|
||||
language_options: [],
|
||||
@@ -44,12 +53,18 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
bg_url_or_path: '',
|
||||
themes: ['default'],
|
||||
theme: '',
|
||||
encryption: false
|
||||
encryption: false,
|
||||
swag: false,
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: '',
|
||||
}
|
||||
|
||||
this.setGameExec = this.setGameExec.bind(this)
|
||||
this.setGrasscutterJar = this.setGrasscutterJar.bind(this)
|
||||
this.setJavaPath = this.setJavaPath.bind(this)
|
||||
this.setClientVersion = this.setClientVersion.bind(this)
|
||||
this.setAkebi = this.setAkebi.bind(this)
|
||||
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
|
||||
this.setCustomBackground = this.setCustomBackground.bind(this)
|
||||
this.toggleEncryption = this.toggleEncryption.bind(this)
|
||||
@@ -68,13 +83,19 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
game_install_path: config.game_install_path || '',
|
||||
grasscutter_path: config.grasscutter_path || '',
|
||||
java_path: config.java_path || '',
|
||||
client_version: config.client_version || '',
|
||||
meta_download: (await getVersionCache())?.metadata_backup_link || '',
|
||||
grasscutter_with_game: config.grasscutter_with_game || false,
|
||||
language_options: languages,
|
||||
current_language: config.language || 'en',
|
||||
bg_url_or_path: config.customBackground || '',
|
||||
themes: (await getThemeList()).map(t => t.name),
|
||||
theme: config.theme || 'default',
|
||||
encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled')
|
||||
encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled'),
|
||||
swag: config.swag_mode || false,
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: config.akebi_path || '',
|
||||
})
|
||||
|
||||
this.forceUpdate()
|
||||
@@ -88,6 +109,17 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
async setClientVersion(value: string) {
|
||||
await setConfigOption('client_version', value)
|
||||
|
||||
const newCache = await cacheLauncherResources()
|
||||
|
||||
this.setState({
|
||||
client_version: value,
|
||||
meta_download: newCache?.metadata_backup_link
|
||||
})
|
||||
}
|
||||
|
||||
setGrasscutterJar(value: string) {
|
||||
setConfigOption('grasscutter_path', value)
|
||||
|
||||
@@ -104,6 +136,14 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
setAkebi(value: string) {
|
||||
setConfigOption('akebi_path', value)
|
||||
|
||||
this.setState({
|
||||
akebi_path: value
|
||||
})
|
||||
}
|
||||
|
||||
async setLanguage(value: string) {
|
||||
await setConfigOption('language', value)
|
||||
window.location.reload()
|
||||
@@ -178,6 +218,51 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<DirInput onChange={this.setGameExec} value={this.state?.game_install_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='OptionSection' id="menuOptionsContainerClientVersion">
|
||||
<div className='OptionLabel' id="menuOptionsLabelClientVersion">
|
||||
<Tr text="options.game_version" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirClientVersion">
|
||||
<select value={this.state.client_version} id="menuOptionsSelectMenuThemes" onChange={(event) => {
|
||||
this.setClientVersion(event.target.value)
|
||||
}}>
|
||||
{getVersions().map(t => (
|
||||
<option
|
||||
key={t}
|
||||
value={t}>
|
||||
|
||||
{t}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className='OptionSection' id="menuOptionsContainerMetadataDownload">
|
||||
<div className='OptionLabel' id="menuOptionsLabelMetadataDownload">
|
||||
<Tr text="options.emergency_metadata" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsButtonMetadataDownload">
|
||||
<BigButton disabled={this.state.meta_download === ''} onClick={this.toggleEncryption} id="toggleEnc">
|
||||
<Tr text='components.download' />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
this.state.swag && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className='OptionSection' id="menuOptionsContainerAkebi">
|
||||
<div className='OptionLabel' id="menuOptionsLabelAkebi">
|
||||
<Tr text="swag.akebi" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsDirAkebi">
|
||||
<DirInput onChange={this.setAkebi} value={this.state?.akebi_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div className='OptionSection' id="menuOptionsContainerGCJar">
|
||||
<div className='OptionLabel' id="menuOptionsLabelGCJar">
|
||||
<Tr text="options.grasscutter_jar" />
|
||||
@@ -191,7 +276,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<Tr text="options.toggle_encryption" />
|
||||
</div>
|
||||
<div className='OptionValue' id="menuOptionsButtonToggleEnc">
|
||||
<BigButton onClick={this.toggleEncryption} id="toggleEnc">
|
||||
<BigButton disabled={this.state.grasscutter_path === ''} onClick={this.toggleEncryption} id="toggleEnc">
|
||||
{
|
||||
this.state.encryption
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { fs } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { cacheLauncherResources } from './resources'
|
||||
|
||||
let configFilePath: string
|
||||
let defaultConfig: Configuration
|
||||
@@ -17,6 +18,7 @@ let defaultConfig: Configuration
|
||||
last_port: '443',
|
||||
language: 'en',
|
||||
customBackground: '',
|
||||
client_version: '2.7.0',
|
||||
cert_generated: false,
|
||||
theme: 'default',
|
||||
https_enabled: false,
|
||||
@@ -39,10 +41,15 @@ export interface Configuration {
|
||||
last_port: string
|
||||
language: string
|
||||
customBackground: string
|
||||
client_version: string
|
||||
cert_generated: boolean
|
||||
theme: string
|
||||
https_enabled: boolean
|
||||
debug_enabled: boolean
|
||||
swag_mode?: boolean
|
||||
|
||||
// Swag stuff
|
||||
akebi_path?: string
|
||||
}
|
||||
|
||||
export async function setConfigOption(key: string, value: any): Promise<void> {
|
||||
@@ -112,6 +119,12 @@ async function readConfigFile() {
|
||||
contents: JSON.stringify(defaultConfig)
|
||||
}
|
||||
|
||||
// Also just shoe-horning this in, cache resources on first launch
|
||||
const versionData = await cacheLauncherResources()
|
||||
|
||||
defaultConfig.client_version = versionData?.game || ''
|
||||
|
||||
// Write config
|
||||
await fs.writeFile(file)
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,9 @@ export default class DownloadHandler {
|
||||
return this.downloads.some(d => d.path.includes('grasscutter_repo.zip'))
|
||||
}
|
||||
|
||||
addDownload(url: string, path: string, onFinish?: () => void) {
|
||||
addDownload(url: string | null | undefined, path: string, onFinish?: () => void) {
|
||||
if (!url) return
|
||||
|
||||
// Begin download from rust backend, don't add if the download addition fails
|
||||
invoke('download_file', { url, path })
|
||||
const obj = {
|
||||
|
||||
106
src/utils/resources.ts
Normal file
106
src/utils/resources.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { fs, invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { getConfig } from './configuration'
|
||||
|
||||
export interface VersionData {
|
||||
game: string
|
||||
metadata: string | null
|
||||
metadata_backup_link: string | null
|
||||
client_download_link: string | null
|
||||
resources: string
|
||||
stableJar: string | null
|
||||
devJar: string | null
|
||||
stable: string | null
|
||||
dev: string | null
|
||||
}
|
||||
|
||||
const globals: {
|
||||
[key: string]: VersionData
|
||||
} = {
|
||||
'2.8.0': {
|
||||
game: '2.8.0',
|
||||
metadata: '2.8.0',
|
||||
metadata_backup_link: null,
|
||||
client_download_link: null,
|
||||
resources: 'https://gitlab.com/yukiz/GrasscutterResources/-/archive/2.8/GrasscutterResources-2.8.zip',
|
||||
stableJar: null,
|
||||
devJar: 'https://nightly.link/Grasscutters/Grasscutter/actions/runs/2661955213/Grasscutter.zip',
|
||||
stable: null,
|
||||
dev: 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/2.8.zip'
|
||||
},
|
||||
'2.7.0': {
|
||||
game: '2.7.0',
|
||||
metadata: null,
|
||||
metadata_backup_link: null,
|
||||
client_download_link: null,
|
||||
resources: 'https://github.com/Koko-boya/Grasscutter_Resources/archive/refs/heads/main.zip',
|
||||
stableJar: 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip',
|
||||
devJar: 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip',
|
||||
stable: 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip',
|
||||
dev: 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||
},
|
||||
'2.6.0': {
|
||||
game: '2.6.0',
|
||||
metadata: null,
|
||||
metadata_backup_link: null,
|
||||
client_download_link: null,
|
||||
resources: 'https://github.com/Koko-boya/Grasscutter_Resources/archive/0e99a59218a346c2d56c54953f99077882de4a6d.zip',
|
||||
stableJar: 'https://github.com/Grasscutters/Grasscutter/releases/download/v1.1.0/grasscutter-1.1.0.jar',
|
||||
devJar: null,
|
||||
stable: 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/2.6.zip',
|
||||
dev: null
|
||||
}
|
||||
}
|
||||
|
||||
export async function cacheLauncherResources() {
|
||||
const config = await getConfig()
|
||||
const versionAPIUrl = 'https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10&sub_channel_id=0'
|
||||
|
||||
// Get versions from API
|
||||
const versions = JSON.parse(await invoke('web_get', {
|
||||
url: versionAPIUrl
|
||||
}))
|
||||
|
||||
if (!versions || versions.retcode !== 0) {
|
||||
console.log('Failed to get versions from API')
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedVersion = config.client_version || versions.data.game.latest.version
|
||||
const selectedVersionData = globals[selectedVersion]
|
||||
|
||||
if (!selectedVersionData) {
|
||||
console.log('Failed to get version for selected version')
|
||||
return null
|
||||
}
|
||||
|
||||
const latest = versions.data.game.latest
|
||||
const latestData = globals[latest.version]
|
||||
|
||||
if (latestData) {
|
||||
latestData.metadata_backup_link = latest.decompressed_path + '/GenshinImpact_Data/Managed/Metadata/global-metadata.dat'
|
||||
latestData.client_download_link = latest.path
|
||||
}
|
||||
|
||||
// Write
|
||||
fs.writeFile({
|
||||
path: await dataDir() + 'cultivation/resources.json',
|
||||
contents: JSON.stringify(selectedVersionData)
|
||||
})
|
||||
|
||||
// In case we want to get it right away too
|
||||
return selectedVersionData
|
||||
}
|
||||
|
||||
export async function getVersionCache() {
|
||||
const raw = await fs.readTextFile(await dataDir() + 'cultivation/resources.json').catch(e => {
|
||||
console.log(e)
|
||||
return null
|
||||
})
|
||||
|
||||
return raw ? JSON.parse(raw) as VersionData : null
|
||||
}
|
||||
|
||||
export function getVersions() {
|
||||
return Object.keys(globals)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ export async function toggleEncryption(path: string) {
|
||||
// Write file
|
||||
await fs.writeFile({
|
||||
path,
|
||||
contents: JSON.stringify(serverConf)
|
||||
contents: JSON.stringify(serverConf, null, 2),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user