Compare commits

...

9 Commits

Author SHA1 Message Date
SpikeHD
b0c545d032 correct jar for 2.8 run 2022-07-15 19:12:52 -07:00
SpikeHD
88dd7b854f default to latest game version 2022-07-15 19:07:22 -07:00
SpikeHD
e159bc2cdb resource getting for downloads page 2022-07-15 18:29:13 -07:00
SpikeHD
cc4600ec77 create resources file on ifrst config write 2022-07-15 18:25:43 -07:00
SpikeHD
f37e44a88c meta download button 2022-07-15 18:15:10 -07:00
SpikeHD
4f806efc93 client download 2022-07-15 18:00:05 -07:00
SpikeHD
083de896b3 get latest client and metadata links 2022-07-15 17:54:56 -07:00
SpikeHD
9c64c1f282 client version setting 2022-07-15 17:25:14 -07:00
SpikeHD
aa10a908ad idk some wip shit 2022-07-15 16:58:27 -07:00
10 changed files with 223 additions and 37 deletions

View File

@@ -14,6 +14,8 @@
"enabled": "Enabled", "enabled": "Enabled",
"disabled": "Disabled", "disabled": "Disabled",
"game_exec": "Set Game Executable", "game_exec": "Set Game Executable",
"game_version": "Set Game Version",
"emergency_metadata": "Emergency Metadata Restore",
"grasscutter_jar": "Set Grasscutter JAR", "grasscutter_jar": "Set Grasscutter JAR",
"toggle_encryption": "Toggle Encryption", "toggle_encryption": "Toggle Encryption",
"java_path": "Set Custom Java Path", "java_path": "Set Custom Java Path",

View File

@@ -49,7 +49,8 @@ fn main() {
downloader::stop_download, downloader::stop_download,
lang::get_lang, lang::get_lang,
lang::get_languages, lang::get_languages,
web::valid_url web::valid_url,
web::web_get
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@@ -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(); let response = client.get(url).header(USER_AGENT, "cultivation").send().await.unwrap();
response.status().as_str() == "200" 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
} }

View File

@@ -115,9 +115,9 @@ export default class TopBar extends React.Component<IProps, IState> {
<div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}> <div id="downloadsBtn" className='TopButton' onClick={this.props.downFunc}>
<img src={downBtn} alt="downloads" /> <img src={downBtn} alt="downloads" />
</div> </div>
{/* <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}> <div id="gameBtn" className="TopButton" onClick={this.props.gameFunc}>
<img src={gameBtn} alt="game" /> <img src={gameBtn} alt="game" />
</div> */} </div>
</div> </div>
</div> </div>
) )

View File

@@ -12,12 +12,7 @@ import { getConfigOption, setConfigOption } from '../../../utils/configuration'
import { invoke } from '@tauri-apps/api' import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event' import { listen } from '@tauri-apps/api/event'
import HelpButton from '../common/HelpButton' import HelpButton from '../common/HelpButton'
import { getVersionCache, VersionData } from '../../../utils/resources'
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'
interface IProps { interface IProps {
closeFn: () => void; closeFn: () => void;
@@ -30,6 +25,7 @@ interface IState {
repo_downloading: boolean repo_downloading: boolean
grasscutter_set: boolean grasscutter_set: boolean
resources_exist: boolean resources_exist: boolean
version_data: VersionData | null
} }
export default class Downloads extends React.Component<IProps, IState> { 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(), resources_downloading: this.props.downloadManager.downloadingResources(),
repo_downloading: this.props.downloadManager.downloadingRepo(), repo_downloading: this.props.downloadManager.downloadingRepo(),
grasscutter_set: false, grasscutter_set: false,
resources_exist: false resources_exist: false,
version_data: null
} }
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this) this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
@@ -55,6 +52,11 @@ export default class Downloads extends React.Component<IProps, IState> {
async componentDidMount() { async componentDidMount() {
const gc_path = await getConfigOption('grasscutter_path') const gc_path = await getConfigOption('grasscutter_path')
const versionData = await getVersionCache()
this.setState({
version_data: versionData,
})
listen('jar_extracted', () => { listen('jar_extracted', () => {
this.setState({ grasscutter_set: true }, this.forceUpdate) this.setState({ grasscutter_set: true }, this.forceUpdate)
@@ -109,7 +111,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStableRepo() { async downloadGrasscutterStableRepo() {
const folder = await this.getGrasscutterFolder() 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) unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
}) })
@@ -118,7 +120,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterDevRepo() { async downloadGrasscutterDevRepo() {
const folder = await this.getGrasscutterFolder() 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) unzip(folder + '\\grasscutter_repo.zip', folder + '\\', this.toggleButtons)
}) })
@@ -127,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStable() { async downloadGrasscutterStable() {
const folder = await this.getGrasscutterFolder() 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) unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
}) })
@@ -139,7 +141,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterLatest() { async downloadGrasscutterLatest() {
const folder = await this.getGrasscutterFolder() 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) unzip(folder + '\\grasscutter.zip', folder + '\\', this.toggleButtons)
}) })
@@ -151,7 +153,7 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadResources() { async downloadResources() {
const folder = await this.getGrasscutterFolder() 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 // Delete the existing folder if it exists
if (await invoke('dir_exists', { if (await invoke('dir_exists', {
path: folder + '\\resources' path: folder + '\\resources'
@@ -200,7 +202,7 @@ export default class Downloads extends React.Component<IProps, IState> {
</HelpButton> </HelpButton>
</div> </div>
<div className='DownloadValue' id="downloadMenuButtonGCStable"> <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" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>
@@ -215,7 +217,7 @@ export default class Downloads extends React.Component<IProps, IState> {
</HelpButton> </HelpButton>
</div> </div>
<div className='DownloadValue' id="downloadMenuButtonGCDev"> <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" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>
@@ -233,7 +235,7 @@ export default class Downloads extends React.Component<IProps, IState> {
</HelpButton> </HelpButton>
</div> </div>
<div className='DownloadValue' id="downloadMenuButtonGCStableData"> <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" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>
@@ -248,7 +250,7 @@ export default class Downloads extends React.Component<IProps, IState> {
</HelpButton> </HelpButton>
</div> </div>
<div className='DownloadValue' id="downloadMenuButtonGCDevData"> <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" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>
@@ -264,7 +266,11 @@ export default class Downloads extends React.Component<IProps, IState> {
</HelpButton> </HelpButton>
</div> </div>
<div className='DownloadValue' id="downloadMenuButtonResources"> <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" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>

View File

@@ -8,8 +8,7 @@ import DirInput from '../common/DirInput'
import BigButton from '../common/BigButton' import BigButton from '../common/BigButton'
import HelpButton from '../common/HelpButton' import HelpButton from '../common/HelpButton'
import { unzip } from '../../../utils/zipUtils' import { unzip } from '../../../utils/zipUtils'
import { getVersionCache } from '../../../utils/resources'
const GAME_DOWNLOAD = ''
interface IProps { interface IProps {
closeFn: () => void; closeFn: () => void;
@@ -20,6 +19,7 @@ interface IState {
gameDownloading: boolean; gameDownloading: boolean;
gameDownloadFolder: string; gameDownloadFolder: string;
dirPlaceholder: string; dirPlaceholder: string;
clientDownloadLink: string | null | undefined;
} }
export default class Downloads extends React.Component<IProps, IState> { export default class Downloads extends React.Component<IProps, IState> {
@@ -29,23 +29,25 @@ export default class Downloads extends React.Component<IProps, IState> {
this.state = { this.state = {
gameDownloading: false, gameDownloading: false,
gameDownloadFolder: '', gameDownloadFolder: '',
dirPlaceholder: '' dirPlaceholder: '',
clientDownloadLink: ''
} }
this.downloadGame = this.downloadGame.bind(this) this.downloadGame = this.downloadGame.bind(this)
} }
async componentDidMount() { async componentDidMount() {
this.setState({ const versionCache = await getVersionCache()
dirPlaceholder: await translate('components.select_folder')
})
console.log(this.state) this.setState({
dirPlaceholder: await translate('components.select_folder'),
clientDownloadLink: versionCache?.client_download_link
})
} }
async downloadGame() { async downloadGame() {
const folder = this.state.gameDownloadFolder 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 + '\\', () => { unzip(folder + '\\game.zip', folder + '\\', () => {
this.setState({ this.setState({
gameDownloading: false 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"> <Menu heading='Download Game' closeFn={this.props.closeFn} className="GameDownloadMenu">
<div className="GameDownload"> <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="downloadGameBtn" onClick={this.downloadGame}>Download Game</BigButton>
: <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton> : <BigButton id="disabledGameBtn" onClick={() => null} disabled>Download Game</BigButton>
} }

View File

@@ -12,6 +12,7 @@ import * as server from '../../../utils/server'
import './Options.css' import './Options.css'
import BigButton from '../common/BigButton' import BigButton from '../common/BigButton'
import { cacheLauncherResources, getVersionCache, getVersions } from '../../../utils/resources'
interface IProps { interface IProps {
closeFn: () => void; closeFn: () => void;
@@ -20,6 +21,8 @@ interface IProps {
interface IState { interface IState {
game_install_path: string game_install_path: string
grasscutter_path: string grasscutter_path: string
client_version: string
meta_download: string | null | undefined
java_path: string java_path: string
grasscutter_with_game: boolean grasscutter_with_game: boolean
language_options: { [key: string]: string }[], language_options: { [key: string]: string }[],
@@ -41,6 +44,8 @@ export default class Options extends React.Component<IProps, IState> {
this.state = { this.state = {
game_install_path: '', game_install_path: '',
grasscutter_path: '', grasscutter_path: '',
client_version: '',
meta_download: '',
java_path: '', java_path: '',
grasscutter_with_game: false, grasscutter_with_game: false,
language_options: [], language_options: [],
@@ -58,6 +63,7 @@ export default class Options extends React.Component<IProps, IState> {
this.setGameExec = this.setGameExec.bind(this) this.setGameExec = this.setGameExec.bind(this)
this.setGrasscutterJar = this.setGrasscutterJar.bind(this) this.setGrasscutterJar = this.setGrasscutterJar.bind(this)
this.setJavaPath = this.setJavaPath.bind(this) this.setJavaPath = this.setJavaPath.bind(this)
this.setClientVersion = this.setClientVersion.bind(this)
this.setAkebi = this.setAkebi.bind(this) this.setAkebi = this.setAkebi.bind(this)
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this) this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
this.setCustomBackground = this.setCustomBackground.bind(this) this.setCustomBackground = this.setCustomBackground.bind(this)
@@ -77,6 +83,8 @@ export default class Options extends React.Component<IProps, IState> {
game_install_path: config.game_install_path || '', game_install_path: config.game_install_path || '',
grasscutter_path: config.grasscutter_path || '', grasscutter_path: config.grasscutter_path || '',
java_path: config.java_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, grasscutter_with_game: config.grasscutter_with_game || false,
language_options: languages, language_options: languages,
current_language: config.language || 'en', current_language: config.language || 'en',
@@ -101,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) { setGrasscutterJar(value: string) {
setConfigOption('grasscutter_path', value) setConfigOption('grasscutter_path', value)
@@ -199,16 +218,49 @@ export default class Options extends React.Component<IProps, IState> {
<DirInput onChange={this.setGameExec} value={this.state?.game_install_path} extensions={['exe']} /> <DirInput onChange={this.setGameExec} value={this.state?.game_install_path} extensions={['exe']} />
</div> </div>
</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 && ( this.state.swag && (
<div className='OptionSection' id="menuOptionsContainerAkebi"> <>
<div className='OptionLabel' id="menuOptionsLabelAkebi"> <Divider />
<Tr text="swag.akebi" /> <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> </div>
<div className='OptionValue' id="menuOptionsDirAkebi"> <Divider />
<DirInput onChange={this.setAkebi} value={this.state?.akebi_path} extensions={['exe']} /> </>
</div>
</div>
) )
} }
<div className='OptionSection' id="menuOptionsContainerGCJar"> <div className='OptionSection' id="menuOptionsContainerGCJar">

View File

@@ -1,5 +1,6 @@
import { fs } from '@tauri-apps/api' import { fs } from '@tauri-apps/api'
import { dataDir } from '@tauri-apps/api/path' import { dataDir } from '@tauri-apps/api/path'
import { cacheLauncherResources } from './resources'
let configFilePath: string let configFilePath: string
let defaultConfig: Configuration let defaultConfig: Configuration
@@ -17,6 +18,7 @@ let defaultConfig: Configuration
last_port: '443', last_port: '443',
language: 'en', language: 'en',
customBackground: '', customBackground: '',
client_version: '2.7.0',
cert_generated: false, cert_generated: false,
theme: 'default', theme: 'default',
https_enabled: false, https_enabled: false,
@@ -39,6 +41,7 @@ export interface Configuration {
last_port: string last_port: string
language: string language: string
customBackground: string customBackground: string
client_version: string
cert_generated: boolean cert_generated: boolean
theme: string theme: string
https_enabled: boolean https_enabled: boolean
@@ -116,6 +119,12 @@ async function readConfigFile() {
contents: JSON.stringify(defaultConfig) 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) await fs.writeFile(file)
} }

View File

@@ -107,7 +107,9 @@ export default class DownloadHandler {
return this.downloads.some(d => d.path.includes('grasscutter_repo.zip')) 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 // Begin download from rust backend, don't add if the download addition fails
invoke('download_file', { url, path }) invoke('download_file', { url, path })
const obj = { const obj = {

106
src/utils/resources.ts Normal file
View 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)
}