import React from 'react' import { invoke } from '@tauri-apps/api' import { dataDir } from '@tauri-apps/api/path' import DirInput from '../common/DirInput' import Menu from './Menu' import Tr, { getLanguages } from '../../../utils/language' import { setConfigOption, getConfig, getConfigOption, Configuration } from '../../../utils/configuration' import Checkbox from '../common/Checkbox' import Divider from './Divider' import { getThemeList } from '../../../utils/themes' import * as server from '../../../utils/server' import './Options.css' import BigButton from '../common/BigButton' import DownloadHandler from '../../../utils/download' import * as meta from '../../../utils/rsa' import HelpButton from '../common/HelpButton' import SmallButton from '../common/SmallButton' 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', Capability = 'Capability', Root = 'Root', } interface IProps { closeFn: () => void downloadManager: DownloadHandler } interface IState { game_install_path: string grasscutter_path: string java_path: string grasscutter_with_game: boolean language_options: { [key: string]: string }[] current_language: string bg_url_or_path: string themes: string[] theme: string use_theme_background: boolean encryption: boolean patch_rsa: boolean use_internal_proxy: boolean wipe_login: boolean horny_mode: boolean auto_mongodb: boolean swag: boolean platform: string un_elevated: boolean redirect_more: boolean launch_args: string offline_mode: boolean newer_game: boolean // Linux stuff grasscutter_elevation: string // Swag stuff akebi_path: string migoto_path: string reshade_path: string } export default class Options extends React.Component { constructor(props: IProps) { super(props) this.state = { game_install_path: '', grasscutter_path: '', java_path: '', grasscutter_with_game: false, language_options: [], current_language: 'en', bg_url_or_path: '', themes: ['default'], theme: '', use_theme_background: false, encryption: false, patch_rsa: false, use_internal_proxy: false, wipe_login: false, horny_mode: false, swag: false, auto_mongodb: false, platform: '', un_elevated: false, redirect_more: false, launch_args: '', offline_mode: false, newer_game: false, // Linux stuff grasscutter_elevation: GrasscutterElevation.None, // Swag stuff akebi_path: '', migoto_path: '', reshade_path: '', } this.setGameExecutable = this.setGameExecutable.bind(this) this.setGrasscutterJar = this.setGrasscutterJar.bind(this) this.setJavaPath = this.setJavaPath.bind(this) this.setAkebi = this.setAkebi.bind(this) this.setMigoto = this.setMigoto.bind(this) this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this) this.setCustomBackground = this.setCustomBackground.bind(this) this.toggleEncryption = this.toggleEncryption.bind(this) this.removeRSA = this.removeRSA.bind(this) this.deleteWebCache = this.deleteWebCache.bind(this) this.addMigotoDelay = this.addMigotoDelay.bind(this) this.toggleUnElevatedGame = this.toggleUnElevatedGame.bind(this) this.setLaunchArgs = this.setLaunchArgs.bind(this) } async componentDidMount() { const config = await getConfig() const languages = await getLanguages() const platform: string = await invoke('get_platform') // 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) this.setState({ game_install_path: config.game_install_path || '', grasscutter_path: config.grasscutter_path || '', java_path: config.java_path || '', grasscutter_with_game: config.grasscutter_with_game || false, language_options: languages, current_language: config.language || 'en', bg_url_or_path: config.custom_background || '', themes: (await getThemeList()).map((t) => t.name), theme: config.theme || 'default', use_theme_background: config.use_theme_background || false, encryption: encEnabled || false, patch_rsa: config.patch_rsa || false, use_internal_proxy: config.use_internal_proxy || false, wipe_login: config.wipe_login || false, horny_mode: config.horny_mode || false, swag: config.swag_mode || false, auto_mongodb: config.auto_mongodb || false, platform, 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, // Swag stuff akebi_path: config.akebi_path || '', migoto_path: config.migoto_path || '', reshade_path: config.reshade_path || '', }) this.forceUpdate() } 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') || value.endsWith('.lnk')) { const pathArr = value.replace(/\\/g, '/').split('/') pathArr.pop() const path = pathArr.join('/') + '/Genshin Impact Game/' 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') || !value.toLowerCase().includes('yuanshen')) { if (!this.state.redirect_more) { this.toggleOption('redirect_more') } } this.setState({ game_install_path: value, }) emit('set_game', { game_path: value }) } async setGrasscutterJar(value: string) { setConfigOption('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') this.setState({ grasscutter_path: value, encryption: encEnabled, }) // Encryption refuses to re-render w/o reload unless updated twice this.forceUpdateEncyption() } setJavaPath(value: string) { setConfigOption('java_path', value) this.setState({ java_path: value, }) } setAkebi(value: string) { setConfigOption('akebi_path', value) this.setState({ akebi_path: value, }) } setMigoto(value: string) { setConfigOption('migoto_path', value) this.setState({ migoto_path: value, }) } setReshade(value: string) { setConfigOption('reshade_path', value) this.setState({ reshade_path: value, }) } async setLanguage(value: string) { await setConfigOption('language', value) window.location.reload() } async setTheme(value: string) { await setConfigOption('theme', value) window.location.reload() } async toggleGrasscutterWithGame() { const changedVal = !(await getConfigOption('grasscutter_with_game')) setConfigOption('grasscutter_with_game', changedVal) this.setState({ grasscutter_with_game: changedVal, }) } async setCustomBackground(value: string) { const isUrl = /^(?:http(s)?:\/\/)/gm.test(value) if (!value) return await setConfigOption('custom_background', '') if (!isUrl) { const filename = value.replace(/\\/g, '/').split('/').pop() const localBgPath = ((await dataDir()) as string).replace(/\\/g, '/') await setConfigOption('custom_background', `${localBgPath}/cultivation/bg/${filename}`) // Copy the file over to the local directory await invoke('copy_file', { path: value.replace(/\\/g, '/'), newPath: `${localBgPath}cultivation/bg/`, }) window.location.reload() } else { await setConfigOption('custom_background', value) window.location.reload() } } 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() // Check if grasscutter path is set if (!config.grasscutter_path) { alert('Grasscutter not set!') return } // Remove jar from path const path = config.grasscutter_path.replace(/\\/g, '/') const folderPath = path.substring(0, path.lastIndexOf('/')) if (!(await server.encryptionEnabled(folderPath + '/config.json'))) { if ( !(await confirm( 'Cultivation requires encryption DISABLED to connect and play locally. \n\n Are you sure you want to enable encryption?', 'Warning!' )) ) { return } } await server.toggleEncryption(folderPath + '/config.json') this.setState({ encryption: await server.encryptionEnabled(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 to apply encryption changes!') await invoke('restart_grasscutter') } } async toggleUnElevatedGame() { const changedVal = !(await getConfigOption('un_elevated')) setConfigOption('un_elevated', changedVal) this.setState({ un_elevated: changedVal, }) } async setGCElevation(value: string) { setConfigOption('grasscutter_elevation', value) this.setState({ grasscutter_elevation: value, }) } async removeRSA() { await meta.unpatchGame() } 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, }) } async installCert() { await invoke('generate_ca_files', { path: (await dataDir()) + 'cultivation', }) } 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 const pathArr = this.state.game_install_path.replace(/\\/g, '/').split('/') pathArr.pop() const path = pathArr.join('/') + '/GenshinImpact_Data/webCaches' const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches' // Delete the folder 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) { const changedVal = !(await getConfigOption(opt)) await setConfigOption(opt, changedVal) // @ts-expect-error shut up bitch this.setState({ [opt]: changedVal, }) } async setLaunchArgs(value: string) { await setConfigOption('launch_args', value) this.setState({ launch_args: value, }) } render() { return ( {this.state.platform === 'linux' && ( <> )} {this.state.swag && ( <> )} {this.state.platform !== 'linux' && ( )} {this.state.swag ? ( ) : null} ) } }