mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-13 07:34:36 +01:00
Changes from 1.2.1 - 1.5.1
Contains slightly modified commits from between 1.2.1 and 1.5.1.
This commit is contained in:
@@ -17,7 +17,7 @@ interface IState {
|
||||
}
|
||||
|
||||
const downloadHandler = new DownloadHandler()
|
||||
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
|
||||
const WEB_BG = 'https://api.grasscutter.io/cultivation/bgfile'
|
||||
|
||||
class App extends React.Component<Readonly<unknown>, IState> {
|
||||
constructor(props: Readonly<unknown>) {
|
||||
@@ -25,7 +25,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
|
||||
this.state = {
|
||||
page: 'main',
|
||||
bgFile: DEFAULT_BG,
|
||||
bgFile: FALLBACK_BG,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,9 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
|
||||
// Get custom bg AFTER theme is loaded !! important !!
|
||||
const custom_bg = await getConfigOption('custom_background')
|
||||
const offline_mode = await getConfigOption('offline_mode')
|
||||
|
||||
if (custom_bg) {
|
||||
if (custom_bg || offline_mode) {
|
||||
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
|
||||
|
||||
if (!isUrl) {
|
||||
@@ -50,7 +51,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
|
||||
bgFile: isValid ? convertFileSrc(custom_bg) : FALLBACK_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
@@ -62,7 +63,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isValid ? custom_bg : DEFAULT_BG,
|
||||
bgFile: isValid ? custom_bg : FALLBACK_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
@@ -70,12 +71,12 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
} else {
|
||||
// Check if api bg is accessible
|
||||
const isDefaultValid = await invoke('valid_url', {
|
||||
url: DEFAULT_BG,
|
||||
url: WEB_BG,
|
||||
})
|
||||
|
||||
this.setState(
|
||||
{
|
||||
bgFile: isDefaultValid ? DEFAULT_BG : FALLBACK_BG,
|
||||
bgFile: isDefaultValid ? WEB_BG : FALLBACK_BG,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
@@ -86,6 +87,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
// @ts-expect-error - TS doesn't like our custom event
|
||||
page: e.detail,
|
||||
})
|
||||
this.forceUpdate
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
|
||||
}
|
||||
|
||||
async function startProxy() {
|
||||
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + 'cultivation/ca' })
|
||||
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ async function stopProxy() {
|
||||
}
|
||||
|
||||
async function generateCertificates() {
|
||||
await invoke('generate_ca_files', { path: (await dataDir()) + 'cultivation' })
|
||||
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
|
||||
}
|
||||
|
||||
async function generateInfo() {
|
||||
console.log({
|
||||
certificatePath: (await dataDir()) + 'cultivation/ca',
|
||||
certificatePath: (await dataDir()) + '\\cultivation\\ca',
|
||||
isAdmin: await invoke('is_elevated'),
|
||||
connectingTo: proxyAddress,
|
||||
})
|
||||
|
||||
@@ -76,8 +76,10 @@ export class Main extends React.Component<IProps, IState> {
|
||||
setConfigOption('grasscutter_path', payload)
|
||||
})
|
||||
|
||||
listen('migoto_extracted', ({ payload }: { payload: string }) => {
|
||||
setConfigOption('migoto_path', payload)
|
||||
listen('migoto_extracted', async ({ payload }: { payload: string }) => {
|
||||
await setConfigOption('migoto_path', payload)
|
||||
this.setState({ migotoSet: true })
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
// Emitted for rsa replacing-purposes
|
||||
@@ -93,14 +95,6 @@ export class Main extends React.Component<IProps, IState> {
|
||||
}
|
||||
})
|
||||
|
||||
listen('migoto_set', async () => {
|
||||
this.setState({
|
||||
migotoSet: !!(await getConfigOption('migoto_path')),
|
||||
})
|
||||
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
// Emitted for automatic processes
|
||||
listen('grasscutter_closed', async () => {
|
||||
const autoService = await getConfigOption('auto_mongodb')
|
||||
@@ -182,33 +176,35 @@ export class Main extends React.Component<IProps, IState> {
|
||||
// Update launch args to allow launching when updating from old versions
|
||||
await setConfigOption('launch_args', await getConfigOption('launch_args'))
|
||||
|
||||
// Get latest version and compare to this version
|
||||
const latestVersion: {
|
||||
tag_name: string
|
||||
link: string
|
||||
} = await invoke('get_latest_release')
|
||||
const tagName = latestVersion?.tag_name.replace(/[^\d.]/g, '')
|
||||
if (!(await getConfigOption('offline_mode'))) {
|
||||
// Get latest version and compare to this version
|
||||
const latestVersion: {
|
||||
tag_name: string
|
||||
link: string
|
||||
} = await invoke('get_latest_release')
|
||||
const tagName = latestVersion?.tag_name.replace(/[^\d.]/g, '')
|
||||
|
||||
// Check if tagName is different than current version
|
||||
if (tagName && tagName !== (await getVersion())) {
|
||||
// Display notification of new release
|
||||
this.setState({
|
||||
notification: (
|
||||
<>
|
||||
Cultivation{' '}
|
||||
<a href="#" onClick={() => invoke('open_in_browser', { url: latestVersion.link })}>
|
||||
{latestVersion?.tag_name}
|
||||
</a>{' '}
|
||||
is now available!
|
||||
</>
|
||||
),
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
// Check if tagName is different than current version
|
||||
if (tagName && tagName !== (await getVersion())) {
|
||||
// Display notification of new release
|
||||
this.setState({
|
||||
notification: null,
|
||||
notification: (
|
||||
<>
|
||||
Cultivation{' '}
|
||||
<a href="#" onClick={() => invoke('open_in_browser', { url: latestVersion.link })}>
|
||||
{latestVersion?.tag_name}
|
||||
</a>{' '}
|
||||
is now available!
|
||||
</>
|
||||
),
|
||||
})
|
||||
}, 6000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
notification: null,
|
||||
})
|
||||
}, 6000)
|
||||
}
|
||||
}
|
||||
|
||||
// Period check to only show progress bar when downloading files
|
||||
@@ -355,7 +351,7 @@ export class Main extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
<div className="BottomSection" id="bottomSectionContainer">
|
||||
<ServerLaunchSection openExtras={this.openExtrasMenu} />
|
||||
<ServerLaunchSection openExtras={this.openExtrasMenu} downloadHandler={this.props.downloadHandler} />
|
||||
|
||||
<div
|
||||
id="DownloadProgress"
|
||||
|
||||
@@ -151,7 +151,7 @@ export class Mods extends React.Component<IProps, IState> {
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}, 500)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -211,7 +211,7 @@ export class Mods extends React.Component<IProps, IState> {
|
||||
<TextInput
|
||||
id="search"
|
||||
key="search"
|
||||
placeholder={this.state.page.toString()}
|
||||
placeholder={'Search Mods - Page ' + this.state.page.toString()}
|
||||
onChange={(text: string) => {
|
||||
this.setSearch(text)
|
||||
}}
|
||||
|
||||
@@ -17,9 +17,11 @@ import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../util
|
||||
import { patchGame, unpatchGame } from '../../utils/rsa'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
import DownloadHandler from '../../utils/download'
|
||||
|
||||
interface IProps {
|
||||
openExtras: (playGame: () => void) => void
|
||||
downloadHandler: DownloadHandler
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -72,10 +74,15 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
this.setPort = this.setPort.bind(this)
|
||||
this.toggleHttps = this.toggleHttps.bind(this)
|
||||
this.launchServer = this.launchServer.bind(this)
|
||||
this.setButtonLabel = this.setButtonLabel.bind(this)
|
||||
|
||||
listen('start_grasscutter', async () => {
|
||||
this.launchServer()
|
||||
})
|
||||
|
||||
listen('set_game', async () => {
|
||||
this.setButtonLabel()
|
||||
})
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -96,6 +103,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
migotoSet: config.migoto_path !== '',
|
||||
unElevated: config.un_elevated || false,
|
||||
})
|
||||
|
||||
this.setButtonLabel()
|
||||
}
|
||||
|
||||
async toggleGrasscutter() {
|
||||
@@ -140,8 +149,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
// Connect to proxy
|
||||
if (config.toggle_grasscutter) {
|
||||
const game_exe = await getGameExecutable()
|
||||
let newerGame = false
|
||||
|
||||
const patchable = game_exe?.toLowerCase().includes('genshin' || 'yuanshen')
|
||||
const patchable = game_exe?.toLowerCase().includes('yuanshen') || game_exe?.toLowerCase().includes('genshin')
|
||||
|
||||
if (config.patch_rsa && patchable) {
|
||||
const gameVersion = await getGameVersion()
|
||||
@@ -164,10 +174,58 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
return
|
||||
}
|
||||
|
||||
const patched = await patchGame()
|
||||
if (gameVersion?.major == 4 && gameVersion?.minor == 5) {
|
||||
await confirm(
|
||||
'Please use Cultivation version 1.4.0 for game version 4.5. You can find that here: https://github.com/NotThorny/Cultivation/releases/tag/1.4.0'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const versionString = gameVersion?.major.toString() + gameVersion?.minor.toString()
|
||||
|
||||
if ((gameVersion?.major == 4 && gameVersion?.minor > 5) || config.newer_game) {
|
||||
newerGame = true
|
||||
|
||||
const path = (await invoke('install_location')) as string
|
||||
|
||||
const patchstring = '\\altpatch\\'
|
||||
const altPatch = path + patchstring
|
||||
|
||||
const ALT_PATCH =
|
||||
'https://autopatchhk.yuanshen.com/client_app/download/pc_zip/20231030132335_iOEfPMcbrXpiA8Ca/ScatteredFiles/GenshinImpact_Data/Plugins/mihoyonet.dll'
|
||||
const pExists = (await invoke('dir_exists', {
|
||||
path: altPatch,
|
||||
})) as boolean
|
||||
|
||||
if (!pExists) {
|
||||
await invoke('dir_create', {
|
||||
path: altPatch,
|
||||
})
|
||||
this.props.downloadHandler.addDownload(ALT_PATCH, path + '/altpatch/mihoyonet.dll')
|
||||
await confirm('Please wait for the download in the bottom left to disappear, then click yes')
|
||||
}
|
||||
|
||||
/* For custom address patch only, used in 4.5 */
|
||||
// let httpString = 'http://'
|
||||
// if (this.state.httpsEnabled) {
|
||||
// httpString = 'https://'
|
||||
// }
|
||||
// config.launch_args = '-server=' + httpString + this.state.ip + ':' + this.state.port
|
||||
}
|
||||
|
||||
const patched = await patchGame(newerGame, versionString)
|
||||
|
||||
if (!patched) {
|
||||
alert('Could not patch! Try launching again, or patching manually.')
|
||||
alert(
|
||||
"Could not patch! You're trying to launch a version that you don't have a patch for!" +
|
||||
"\nEnsure you're using a valid game version, and have the patch for this version in your Cultivation install folder." +
|
||||
'\n\nIf this means nothing to you, YOU HAVE THE WRONG GAME VERSION.' +
|
||||
// Add game version due to overwhelming number of people saying they are using a version they are not using
|
||||
'\n\nYOUR GAME VERSION: ' +
|
||||
gameVersion?.major +
|
||||
'.' +
|
||||
gameVersion?.minor
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -186,7 +244,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
|
||||
})
|
||||
// Connect to proxy
|
||||
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + 'cultivation/ca' })
|
||||
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
|
||||
}
|
||||
|
||||
// Open server as well if the options are set
|
||||
@@ -314,6 +372,19 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
await saveConfig(config)
|
||||
}
|
||||
|
||||
async setButtonLabel() {
|
||||
const ver = await getGameVersion()
|
||||
if (ver != null) {
|
||||
this.setState({
|
||||
buttonLabel: (await translate('main.launch_button')) + ' ' + ver?.major + '.' + ver?.minor,
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
buttonLabel: await translate('main.launch_button'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="playButton">
|
||||
|
||||
@@ -8,19 +8,23 @@ import { dataDir } from '@tauri-apps/api/path'
|
||||
|
||||
import './Downloads.css'
|
||||
import Divider from './Divider'
|
||||
import { getConfigOption } from '../../../utils/configuration'
|
||||
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import { ask } from '@tauri-apps/api/dialog'
|
||||
|
||||
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip'
|
||||
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip' // Change to link that can be updated without modifying here
|
||||
const FULL_QUEST_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterQuests.zip'
|
||||
const FULL_50_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterLunaGC50.zip' // https://github.com/Kei-Luna/LunaGC_5.0.0
|
||||
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 UNSTABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/unstable/Grasscutter.zip'
|
||||
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://gitlab.com/api/v4/projects/35984297/repository/archive.zip' // Use Yuuki res as grasscutter crepe res are broken
|
||||
const MIGOTO_DOWNLOAD =
|
||||
'https://github.com/SilentNightSound/GI-Model-Importer/releases/download/V7.0/3dmigoto-GIMI-for-playing-mods.zip'
|
||||
'https://github.com/SilentNightSound/GI-Model-Importer/releases/download/v7.0/3dmigoto-GIMI-for-playing-mods.zip'
|
||||
const MIGOTO_FALLBACK = 'https://cdn.discordapp.com/attachments/615655311960965130/1177724469847003268/GIMI7.zip' // Since main dl fails for a few too many users
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void
|
||||
@@ -56,8 +60,10 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
|
||||
this.downloadGrasscutterFullBuild = this.downloadGrasscutterFullBuild.bind(this)
|
||||
this.downloadGrasscutterFullQuest = this.downloadGrasscutterFullQuest.bind(this)
|
||||
this.downloadGrasscutterFull50 = this.downloadGrasscutterFull50.bind(this)
|
||||
this.downloadGrasscutterStableRepo = this.downloadGrasscutterStableRepo.bind(this)
|
||||
this.downloadGrasscutterDevRepo = this.downloadGrasscutterDevRepo.bind(this)
|
||||
this.downloadGrasscutterUnstable = this.downloadGrasscutterUnstable.bind(this)
|
||||
this.downloadGrasscutterLatest = this.downloadGrasscutterLatest.bind(this)
|
||||
this.downloadResources = this.downloadResources.bind(this)
|
||||
this.downloadMigoto = this.downloadMigoto.bind(this)
|
||||
@@ -76,6 +82,19 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
this.setState({ grasscutter_set: true }, this.forceUpdate)
|
||||
})
|
||||
|
||||
// Listen for GIMI failure to initiate fallback
|
||||
listen('download_error', ({ payload }) => {
|
||||
// @ts-expect-error shut up typescript
|
||||
const errorData: {
|
||||
path: string
|
||||
error: string
|
||||
} = payload
|
||||
|
||||
if (errorData.path.includes('GIMI.zip')) {
|
||||
this.downloadMigotoFallback()
|
||||
}
|
||||
})
|
||||
|
||||
if (!gc_path || gc_path === '') {
|
||||
this.setState({
|
||||
grasscutter_set: false,
|
||||
@@ -85,15 +104,15 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
return
|
||||
}
|
||||
|
||||
const path = gc_path.substring(0, gc_path.lastIndexOf('/'))
|
||||
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
|
||||
|
||||
if (gc_path) {
|
||||
const resources_exist: boolean =
|
||||
((await invoke('dir_exists', {
|
||||
path: path + '/resources',
|
||||
path: path + '\\resources',
|
||||
})) as boolean) &&
|
||||
(!(await invoke('dir_is_empty', {
|
||||
path: path + '/resources',
|
||||
path: path + '\\resources',
|
||||
})) as boolean)
|
||||
|
||||
this.setState({
|
||||
@@ -110,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
// Set to default if not set
|
||||
if (!path || path === '') {
|
||||
const appdata = await dataDir()
|
||||
folderPath = appdata + 'cultivation/grasscutter'
|
||||
folderPath = appdata + 'cultivation\\grasscutter'
|
||||
|
||||
// Early return since its formatted properly
|
||||
return folderPath
|
||||
@@ -133,8 +152,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterFullBuild() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '/GrasscutterCulti.zip', async () => {
|
||||
await unzip(folder + '/GrasscutterCulti.zip', folder + '/', true)
|
||||
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '\\GrasscutterCulti.zip', async () => {
|
||||
await unzip(folder + '\\GrasscutterCulti.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -143,8 +162,18 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGrasscutterFullQuest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '/GrasscutterQuests.zip', async () => {
|
||||
await unzip(folder + '/GrasscutterQuests.zip', folder + '/', true)
|
||||
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '\\GrasscutterQuests.zip', async () => {
|
||||
await unzip(folder + '\\GrasscutterQuests.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterFull50() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(FULL_50_DOWNLOAD, folder + '\\Grasscutter50.zip', async () => {
|
||||
await unzip(folder + '\\Grasscutter50.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -153,8 +182,8 @@ 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', async () => {
|
||||
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
|
||||
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -163,18 +192,28 @@ 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', async () => {
|
||||
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
|
||||
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterUnstable() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(UNSTABLE_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||
this.toggleButtons
|
||||
})
|
||||
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadGrasscutterLatest() {
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '/grasscutter.zip', async () => {
|
||||
await unzip(folder + '/grasscutter.zip', folder + '/', true)
|
||||
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
|
||||
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -185,27 +224,38 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async downloadResources() {
|
||||
// Tell the user this is not needed in most cases
|
||||
if (
|
||||
!(await ask(
|
||||
'These are not needed if you have already downloaded the All-in-One!! \nAre you sure you want to continue this download?'
|
||||
))
|
||||
) {
|
||||
// If refusing confirmation
|
||||
return
|
||||
}
|
||||
|
||||
// Tell the user this takes some time
|
||||
alert(
|
||||
'Extracting resources can take time! If your resources appear to be "stuck" extracting for less than 15-20 mins, they likely still are extracting.'
|
||||
)
|
||||
|
||||
const folder = await this.getGrasscutterFolder()
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '/resources.zip', async () => {
|
||||
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
|
||||
// Delete the existing folder if it exists
|
||||
if (
|
||||
await invoke('dir_exists', {
|
||||
path: folder + '/resources',
|
||||
path: folder + '\\resources',
|
||||
})
|
||||
) {
|
||||
await invoke('dir_delete', {
|
||||
path: folder + '/resources',
|
||||
path: folder + '\\resources',
|
||||
})
|
||||
}
|
||||
|
||||
await unzip(folder + '/resources.zip', folder + '/', true)
|
||||
await unzip(folder + '\\resources.zip', folder + '\\', true)
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folder + '/Resources',
|
||||
path: folder + '\\Resources',
|
||||
newName: 'resources',
|
||||
})
|
||||
|
||||
@@ -216,13 +266,27 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async downloadMigoto() {
|
||||
const folder = (await this.getCultivationFolder()) + '/3dmigoto'
|
||||
await invoke('dir_create', {
|
||||
path: folder,
|
||||
if (!this.state.swag) {
|
||||
await setConfigOption('swag_mode', true)
|
||||
this.setState({ swag: true })
|
||||
await setConfigOption('last_extras', { migoto: true, akebi: false, reshade: false })
|
||||
}
|
||||
|
||||
const folder = await this.getCultivationFolder()
|
||||
|
||||
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '\\GIMI.zip', async () => {
|
||||
await unzip(folder + '\\GIMI.zip', folder + '\\', true, true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '/GIMI-3dmigoto.zip', async () => {
|
||||
await unzip(folder + '/GIMI-3dmigoto.zip', folder + '/', true)
|
||||
this.toggleButtons()
|
||||
}
|
||||
|
||||
async downloadMigotoFallback() {
|
||||
const folder = await this.getCultivationFolder()
|
||||
|
||||
this.props.downloadManager.addDownload(MIGOTO_FALLBACK, folder + '\\GIMI7.zip', async () => {
|
||||
await unzip(folder + '\\GIMI7.zip', folder + '\\', true, true)
|
||||
this.toggleButtons()
|
||||
})
|
||||
|
||||
@@ -256,7 +320,6 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
<Tr text={'downloads.grasscutter_fullbuild'} />
|
||||
<HelpButton contents="help.gc_fullbuild" />
|
||||
</div>
|
||||
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCFullBuild">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
@@ -267,7 +330,6 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCFullQuest">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCFullQuest">
|
||||
<Tr text={'downloads.grasscutter_fullquest'} />
|
||||
@@ -276,18 +338,38 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCFullQuest">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterFullQuest}
|
||||
onClick={this.downloadGrasscutterFull50}
|
||||
id="grasscutterFullBuildBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className="HeaderText" id="downloadMenuIndividualHeader">
|
||||
<Tr text="downloads.individual_header" />
|
||||
</div>
|
||||
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCUnstable">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCUnstable">
|
||||
<Tr
|
||||
text={
|
||||
this.state.grasscutter_set ? 'downloads.grasscutter_unstable' : 'downloads.grasscutter_unstable_update'
|
||||
}
|
||||
/>
|
||||
<HelpButton contents="help.gc_unstable_jar" />
|
||||
</div>
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCUnstable">
|
||||
<BigButton
|
||||
disabled={this.state.grasscutter_downloading}
|
||||
onClick={this.downloadGrasscutterUnstable}
|
||||
id="grasscutterUnstableBtn"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCDev">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCDev">
|
||||
<Tr
|
||||
@@ -306,7 +388,28 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
|
||||
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCStableData">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCStableData">
|
||||
<Tr
|
||||
text={
|
||||
this.state.grasscutter_set
|
||||
? 'downloads.grasscutter_stable_data'
|
||||
: 'downloads.grasscutter_stable_data_update'
|
||||
}
|
||||
/>
|
||||
<HelpButton contents="help.gc_stable_data" />
|
||||
</div>
|
||||
<div className="DownloadValue" id="downloadMenuButtonGCStableData">
|
||||
<BigButton
|
||||
disabled={this.state.repo_downloading}
|
||||
onClick={this.downloadGrasscutterStableRepo}
|
||||
id="grasscutterStableRepo"
|
||||
>
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div> */}
|
||||
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelGCDevData">
|
||||
<Tr
|
||||
text={
|
||||
@@ -326,7 +429,7 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerResources">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelResources">
|
||||
@@ -344,25 +447,23 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.swag && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="HeaderText" id="downloadMenuModsHeader">
|
||||
<Tr text="downloads.mods_header" />
|
||||
<>
|
||||
<Divider />
|
||||
<div className="HeaderText" id="downloadMenuModsHeader">
|
||||
<Tr text="downloads.mods_header" />
|
||||
</div>
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelMigoto">
|
||||
<Tr text={'downloads.migoto'} />
|
||||
<HelpButton contents="help.migoto" />
|
||||
</div>
|
||||
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto">
|
||||
<div className="DownloadLabel" id="downloadMenuLabelMigoto">
|
||||
<Tr text={'downloads.migoto'} />
|
||||
<HelpButton contents="help.migoto" />
|
||||
</div>
|
||||
<div className="DownloadValue" id="downloadMenuButtonMigoto">
|
||||
<BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
<div className="DownloadValue" id="downloadMenuButtonMigoto">
|
||||
<BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ export default class Downloads extends React.Component<IProps, IState> {
|
||||
|
||||
async downloadGame() {
|
||||
const folder = this.state.gameDownloadFolder
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '/game.zip', async () => {
|
||||
await unzip(folder + '/game.zip', folder + '/', true)
|
||||
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', async () => {
|
||||
await unzip(folder + '\\game.zip', folder + '\\', true)
|
||||
this.setState({
|
||||
gameDownloading: false,
|
||||
})
|
||||
|
||||
@@ -16,8 +16,11 @@ import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/rsa'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import SmallButton from '../common/SmallButton'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
import { ask, confirm } from '@tauri-apps/api/dialog'
|
||||
import TextInput from '../common/TextInput'
|
||||
import { unzip } from '../../../utils/zipUtils'
|
||||
import { getGameExecutable } from '../../../utils/game'
|
||||
import { emit } from '@tauri-apps/api/event'
|
||||
|
||||
export enum GrasscutterElevation {
|
||||
None = 'None',
|
||||
@@ -52,6 +55,8 @@ interface IState {
|
||||
un_elevated: boolean
|
||||
redirect_more: boolean
|
||||
launch_args: string
|
||||
offline_mode: boolean
|
||||
newer_game: boolean
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: string
|
||||
@@ -88,6 +93,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
un_elevated: false,
|
||||
redirect_more: false,
|
||||
launch_args: '',
|
||||
offline_mode: false,
|
||||
newer_game: false,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: GrasscutterElevation.None,
|
||||
@@ -118,13 +125,10 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
const languages = await getLanguages()
|
||||
const platform: string = await invoke('get_platform')
|
||||
|
||||
let encEnabled
|
||||
if (config.grasscutter_path) {
|
||||
// Remove jar from path
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
const folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
|
||||
}
|
||||
// Remove jar from path
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
const folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
|
||||
|
||||
console.log(platform)
|
||||
|
||||
@@ -150,6 +154,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
un_elevated: config.un_elevated || false,
|
||||
redirect_more: config.redirect_more || false,
|
||||
launch_args: config.launch_args,
|
||||
offline_mode: config.offline_mode || false,
|
||||
newer_game: config.newer_game || false,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
|
||||
@@ -163,22 +169,28 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
setGameExecutable(value: string) {
|
||||
setConfigOption('game_install_path', value)
|
||||
async setGameExecutable(value: string) {
|
||||
await setConfigOption('game_install_path', value)
|
||||
|
||||
// I hope this stops people setting launcher.exe because oml it's annoying
|
||||
if (value.endsWith('launcher.exe')) {
|
||||
if (value.endsWith('launcher.exe') || value.endsWith('.lnk')) {
|
||||
const pathArr = value.replace(/\\/g, '/').split('/')
|
||||
pathArr.pop()
|
||||
const path = pathArr.join('/') + '/Genshin Impact Game/'
|
||||
|
||||
alert(
|
||||
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}`
|
||||
)
|
||||
if (value.endsWith('.lnk')) {
|
||||
alert(
|
||||
'You have set your game executable to a shortcut. You should not do this. Your patching will not work, and your proxy may shut off unexpectedly.'
|
||||
)
|
||||
} else {
|
||||
alert(
|
||||
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// If setting any other game, automatically set to redirect more
|
||||
if (!value.toLowerCase().includes('genshin' || 'yuanshen')) {
|
||||
if (!value.toLowerCase().includes('genshin') || !value.toLowerCase().includes('yuanshen')) {
|
||||
if (!this.state.redirect_more) {
|
||||
this.toggleOption('redirect_more')
|
||||
}
|
||||
@@ -187,26 +199,24 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.setState({
|
||||
game_install_path: value,
|
||||
})
|
||||
|
||||
emit('set_game', { game_path: value })
|
||||
}
|
||||
|
||||
async setGrasscutterJar(value: string) {
|
||||
setConfigOption('grasscutter_path', value)
|
||||
|
||||
this.setState({
|
||||
grasscutter_path: value,
|
||||
})
|
||||
|
||||
const config = await getConfig()
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
const folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
|
||||
|
||||
// Update encryption button when setting new jar
|
||||
this.setState({
|
||||
grasscutter_path: value,
|
||||
encryption: encEnabled,
|
||||
})
|
||||
|
||||
window.location.reload()
|
||||
// Encryption refuses to re-render w/o reload unless updated twice
|
||||
this.forceUpdateEncyption()
|
||||
}
|
||||
|
||||
setJavaPath(value: string) {
|
||||
@@ -267,7 +277,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
|
||||
if (!isUrl) {
|
||||
const filename = value.replace(/\\/g, '/').split('/').pop()
|
||||
const localBgPath = (await dataDir()).replace(/\\/g, '/')
|
||||
const localBgPath = ((await dataDir()) as string).replace(/\\/g, '/')
|
||||
|
||||
await setConfigOption('custom_background', `${localBgPath}/cultivation/bg/${filename}`)
|
||||
|
||||
@@ -284,6 +294,16 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
}
|
||||
}
|
||||
|
||||
async forceUpdateEncyption() {
|
||||
const config = await getConfig()
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
const folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
|
||||
this.setState({
|
||||
encryption: await server.encryptionEnabled(folderPath + '/config.json'),
|
||||
})
|
||||
}
|
||||
|
||||
async toggleEncryption() {
|
||||
const config = await getConfig()
|
||||
|
||||
@@ -343,6 +363,15 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async addMigotoDelay() {
|
||||
if (
|
||||
!(await ask(
|
||||
'Set delay for 3dmigoto loader? This is specifically made for GIMI v6 and earlier. Using it on latest GIMI or SRMI will cause issues!!! \n\nWould you like to continue?',
|
||||
{ title: 'GIMI Delay', type: 'warning' }
|
||||
))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
invoke('set_migoto_delay', {
|
||||
migotoPath: this.state.migoto_path,
|
||||
})
|
||||
@@ -355,6 +384,15 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async deleteWebCache() {
|
||||
if (await ask('Would you like to clear login cache? Yes to clear login cache. No to clear web cache.')) {
|
||||
await invoke('wipe_registry', {
|
||||
// The exe is always PascalCase so we can get the dir using regex
|
||||
execName: (await getGameExecutable())?.split('.exe')[0].replace(/([a-z\d])([A-Z])/g, '$1 $2'),
|
||||
})
|
||||
alert('Cleared login cache!')
|
||||
return
|
||||
}
|
||||
|
||||
alert('Cultivation may freeze for a moment while this occurs!')
|
||||
|
||||
// Get webCaches folder path
|
||||
@@ -364,8 +402,70 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches'
|
||||
|
||||
// Delete the folder
|
||||
await invoke('dir_delete', { path: path })
|
||||
await invoke('dir_delete', { path: path2 })
|
||||
if (await invoke('dir_exists', { path: path })) {
|
||||
await invoke('dir_delete', { path: path })
|
||||
}
|
||||
if (await invoke('dir_exists', { path: path2 })) {
|
||||
await invoke('dir_delete', { path: path2 })
|
||||
}
|
||||
}
|
||||
|
||||
async fixRes() {
|
||||
const config = await getConfig()
|
||||
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
let folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
|
||||
// Set to default if not set
|
||||
if (!path || path === '') {
|
||||
const appdata = await dataDir()
|
||||
folderPath = appdata + 'cultivation\\grasscutter'
|
||||
}
|
||||
|
||||
if (path.includes('/')) {
|
||||
folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
} else {
|
||||
folderPath = path.substring(0, path.lastIndexOf('\\'))
|
||||
}
|
||||
|
||||
// Check if Grasscutter path exists
|
||||
if (folderPath.length < 1) {
|
||||
alert('Grasscutter not installed or not set! This option can only work when it is installed.')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if resources zip exists
|
||||
if (
|
||||
!(await invoke('dir_exists', {
|
||||
path: folderPath + '\\GC-Resources-4.0.zip',
|
||||
}))
|
||||
) {
|
||||
alert('Resources are already unzipped or do not exist! Ensure your resources zip is named "GC-Resources-4.0.zip"')
|
||||
return
|
||||
}
|
||||
|
||||
alert(
|
||||
'This may fix white screen issues on login! Please be patient while extraction occurs, it may take some time (5-10 minutes). \n\n !! You will be alerted when it is done !!'
|
||||
)
|
||||
|
||||
// Unzip resources
|
||||
await unzip(folderPath + '\\GC-Resources-4.0.zip', folderPath + '\\', true)
|
||||
// Rename folder to resources
|
||||
invoke('rename', {
|
||||
path: folderPath + '\\Resources',
|
||||
newName: 'resources',
|
||||
})
|
||||
|
||||
// Update config.json to read from folder
|
||||
await server.changeResourcePath(folderPath + '/config.json')
|
||||
|
||||
// Check if Grasscutter is running, and restart if so to apply changes
|
||||
if (await invoke('is_grasscutter_running')) {
|
||||
alert('Automatically restarting Grasscutter for changes to apply!')
|
||||
await invoke('restart_grasscutter')
|
||||
}
|
||||
|
||||
alert('Resource fixing finished! Please launch the server again and try playing.')
|
||||
}
|
||||
|
||||
async toggleOption(opt: keyof Configuration) {
|
||||
@@ -542,7 +642,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<Tr text="swag.migoto" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsDirMigoto">
|
||||
<SmallButton onClick={this.addMigotoDelay} id="migotoDelay" contents="help.add_delay"></SmallButton>
|
||||
<SmallButton onClick={this.addMigotoDelay} id="migotoDelay"></SmallButton>
|
||||
<DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -599,6 +699,31 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="OptionSection" id="menuOptionsContainerOffline">
|
||||
<div className="OptionLabel" id="menuOptionsLabelOffline">
|
||||
<Tr text="options.offline_mode" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxOffline">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('offline_mode')}
|
||||
checked={this.state?.offline_mode}
|
||||
id="offlineMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="OptionSection" id="menuOptionsContainerNewerGame">
|
||||
<div className="OptionLabel" id="menuOptionsLabelNewerGame">
|
||||
<Tr text="Patch Mihoyonet" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxNewerGame">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('newer_game')}
|
||||
checked={this.state?.newer_game}
|
||||
id="newerGame"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
@@ -709,6 +834,15 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
value={this.state.launch_args}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="OptionLabel" id="menuOptionsLabelFixRes">
|
||||
<Tr text="options.fix_res" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsButtonfixRes">
|
||||
<BigButton onClick={this.fixRes} id="fixRes">
|
||||
<Tr text="components.fix" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import React from 'react'
|
||||
import Tr from '../../../utils/language'
|
||||
import { getConfig, getConfigOption } from '../../../utils/configuration'
|
||||
|
||||
import './NewsSection.css'
|
||||
|
||||
@@ -10,6 +11,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
offline: boolean
|
||||
selected: string
|
||||
news?: JSX.Element
|
||||
commitList?: JSX.Element[]
|
||||
@@ -40,6 +42,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
offline: false,
|
||||
selected: props.selected || 'commits',
|
||||
}
|
||||
|
||||
@@ -47,7 +50,16 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
this.showNews = this.showNews.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
async componentDidMount() {
|
||||
const config = await getConfig()
|
||||
this.setState({
|
||||
offline: config.offline_mode || false,
|
||||
})
|
||||
|
||||
// If offline, don't call news
|
||||
if (this.state.offline) {
|
||||
return
|
||||
}
|
||||
// Call showNews off the bat
|
||||
this.showNews()
|
||||
this.setSelected('commits')
|
||||
@@ -110,6 +122,11 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
async showNews() {
|
||||
const offline_mode = await getConfigOption('offline_mode')
|
||||
if (offline_mode) {
|
||||
return
|
||||
}
|
||||
|
||||
let news: JSX.Element | JSX.Element[] = <tr></tr>
|
||||
|
||||
switch (this.state.selected) {
|
||||
@@ -124,7 +141,10 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
case 'latest_version':
|
||||
news = (
|
||||
<tr>
|
||||
<td>Latest version: Grasscutter 1.7.0 - Cultivation 1.2.0</td>
|
||||
<td>
|
||||
Work in progress area! These numbers may be outdated, so please do not use them as reference. Latest
|
||||
version: Grasscutter 1.7.4 - Cultivation 1.5.1
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
break
|
||||
@@ -144,6 +164,10 @@ export default class NewsSection extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.offline) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="NewsSection" id="newsContainer">
|
||||
<div className="NewsTabs" id="newsTabsContainer">
|
||||
|
||||
@@ -29,6 +29,8 @@ let defaultConfig: Configuration
|
||||
un_elevated: false,
|
||||
redirect_more: false,
|
||||
launch_args: '',
|
||||
offline_mode: false,
|
||||
newer_game: false,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: 'None',
|
||||
@@ -64,6 +66,8 @@ export interface Configuration {
|
||||
un_elevated: boolean
|
||||
redirect_more: boolean
|
||||
launch_args: string
|
||||
offline_mode: boolean
|
||||
newer_game: boolean
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: string
|
||||
@@ -128,7 +132,7 @@ async function readConfigFile() {
|
||||
await fs.createDir(local + 'cultivation').catch((e) => console.log(e))
|
||||
}
|
||||
|
||||
const innerDirs = await fs.readDir(local + 'cultivation')
|
||||
const innerDirs = await fs.readDir(local + '/cultivation')
|
||||
|
||||
// Create grasscutter dir for potential installation
|
||||
if (!innerDirs.find((fileOrDir) => fileOrDir?.name === 'grasscutter')) {
|
||||
|
||||
@@ -73,6 +73,11 @@ export default class DownloadHandler {
|
||||
const index = this.downloads.findIndex((download) => download.path === errorData.path)
|
||||
this.downloads[index].status = 'error'
|
||||
this.downloads[index].error = errorData.error
|
||||
|
||||
// Remove GIMI from list as fallback will replace it
|
||||
if (errorData.path.includes('GIMI.zip')) {
|
||||
this.downloads.splice(index, 1)
|
||||
}
|
||||
})
|
||||
|
||||
// Extraction events
|
||||
@@ -92,6 +97,9 @@ export default class DownloadHandler {
|
||||
// Find the download that is not extracting and set it's status as such
|
||||
const index = this.downloads.findIndex((download) => download.path === obj.file)
|
||||
this.downloads[index].status = 'finished'
|
||||
|
||||
// Remove completed extraction from list
|
||||
this.downloads.splice(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function getGameDataFolder() {
|
||||
return null
|
||||
}
|
||||
|
||||
return (await getGameFolder()) + '/' + gameExec.replace('.exe', '_Data')
|
||||
return (await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data')
|
||||
}
|
||||
|
||||
export async function getGameVersion() {
|
||||
@@ -55,9 +55,33 @@ export async function getGameVersion() {
|
||||
return null
|
||||
}
|
||||
|
||||
const hasAsb = await invoke('dir_exists', {
|
||||
path: GameData + '\\StreamingAssets\\asb_settings.json',
|
||||
})
|
||||
|
||||
if (!hasAsb) {
|
||||
// For games that cannot determine game version
|
||||
const otherGameVer: string = await invoke('read_file', {
|
||||
path: GameData + '\\StreamingAssets\\BinaryVersion.bytes',
|
||||
})
|
||||
const versionRaw = otherGameVer.split('.')
|
||||
const version = {
|
||||
major: parseInt(versionRaw[0]),
|
||||
minor: parseInt(versionRaw[1]),
|
||||
// This will probably never matter, just use major/minor. If needed, full version values are near EOF
|
||||
release: 0,
|
||||
}
|
||||
|
||||
if (otherGameVer == null || otherGameVer.length < 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
const settings = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path: GameData + '/StreamingAssets/asb_settings.json',
|
||||
path: GameData + '\\StreamingAssets\\asb_settings.json',
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export default class Tr extends React.Component<IProps, IState> {
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
translated_text: translation_obj[text] || '',
|
||||
translated_text: translation_obj[text] || text,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
// Patch file from: https://github.com/34736384/RSAPatch/
|
||||
|
||||
export async function patchGame() {
|
||||
return invoke('patch_game')
|
||||
export async function patchGame(newerGame: boolean, version: string) {
|
||||
return invoke('patch_game', { newerGame, version })
|
||||
}
|
||||
|
||||
export async function unpatchGame() {
|
||||
|
||||
@@ -40,5 +40,32 @@ export async function encryptionEnabled(path: string) {
|
||||
return false
|
||||
}
|
||||
|
||||
return serverConf.server.http.encryption.useEncryption
|
||||
if ('server' in serverConf) {
|
||||
return serverConf.server.http.encryption.useEncryption
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export async function changeResourcePath(path: string) {
|
||||
let serverConf
|
||||
|
||||
try {
|
||||
serverConf = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path,
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(`Server config at ${path} not found or invalid. Be sure to run the server at least once to generate it`)
|
||||
return
|
||||
}
|
||||
|
||||
serverConf.folderStructure.resources = './resources/'
|
||||
|
||||
// Write file
|
||||
await invoke('write_file', {
|
||||
path,
|
||||
contents: JSON.stringify(serverConf, null, 2),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ const defaultTheme = {
|
||||
export async function getThemeList() {
|
||||
// Do some invoke to backend to get the theme list
|
||||
const themes = (await invoke('get_theme_list', {
|
||||
dataDir: `${await dataDir()}cultivation`,
|
||||
dataDir: `${await dataDir()}/cultivation`,
|
||||
})) as BackendThemeList[]
|
||||
const list: ThemeList[] = [
|
||||
// ALWAYS include default theme
|
||||
|
||||
Reference in New Issue
Block a user