Some globalisation stuff

Game executable now selectable in settings. Translations need updating for "Set Game Path".
This commit is contained in:
Benj 2022-07-09 15:11:19 +08:00
parent 99687f0550
commit f35b596eb2
13 changed files with 26442 additions and 105 deletions

26278
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@
"files_extracting": "文件解压中:"
},
"options": {
"game_exec": "选择游戏可执行文件",
"game_executable": "选择游戏可执行文件",
"grasscutter_jar": "选择 Grasscutter JAR 文件",
"java_path": "设置自定义 Java 路径",
"grasscutter_with_game": "随游戏自动启动 Grasscutter",

View File

@ -13,7 +13,7 @@
"options": {
"enabled": "已啟用",
"disabled": "未啟用",
"game_exec": "選擇遊戲執行檔",
"game_executable": "選擇遊戲執行檔",
"grasscutter_jar": "選擇伺服器JAR檔案",
"toggle_encryption": "設定加密",
"java_path": "設定自定義Java路徑",

View File

@ -13,7 +13,7 @@
"options": {
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"game_exec": "Spiel Datei auswählen",
"game_executable": "Spiel Datei auswählen",
"grasscutter_jar": "Grasscuter JAR auswählen",
"toggle_encryption": "Verschlüsselung umschalten",
"java_path": "Benutzerdefinierten Java Pfad setzen",

View File

@ -13,7 +13,8 @@
"options": {
"enabled": "Enabled",
"disabled": "Disabled",
"game_exec": "Set Game Executable",
"game_path": "Set Game Install Path",
"game_executable": "Set Game Executable",
"grasscutter_jar": "Set Grasscutter JAR",
"toggle_encryption": "Toggle Encryption",
"java_path": "Set Custom Java Path",
@ -57,7 +58,6 @@
"gc_dev_jar": "Download the latest development Grasscutter build, which includes jar file and data files.",
"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",
"game": "This will download a fresh copy of \"the certain anime game\" and set your game executable to it. This is useful if you don't want to patch your main game."
"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"
}
}

View File

@ -13,7 +13,7 @@
"options": {
"enabled": "active",
"disabled": "desactiver",
"game_exec": "definir l'executable du jeu",
"game_executable": "definir l'executable du jeu",
"grasscutter_jar": "definir le Jar Grasscutter",
"toggle_encryption": "activer l'encryption",
"java_path": "definir un chemin java personnalise",

View File

@ -10,7 +10,7 @@
"files_extracting": "MengExtract File: "
},
"options": {
"game_exec": "Set Game Executable",
"game_executable": "Set Game Executable",
"grasscutter_jar": "Path ke Grasscutter JAR",
"java_path": "Atur kustom Java path",
"grasscutter_with_game": "Otomatis Menjalankan Grasscutter Dengan Game",

View File

@ -13,7 +13,7 @@
"options": {
"enabled": "Iespējots",
"disabled": "Atspējots",
"game_exec": "Iestatīt spēles izpildāmu",
"game_executable": "Iestatīt spēles izpildāmu",
"grasscutter_jar": "Iestatiet Grasscutter JAR",
"toggle_encryption": "Pārslēgt Šifrēšanu",
"java_path": "Iestatiet pielāgotu Java ceļu",

View File

@ -13,7 +13,7 @@
"options": {
"enabled": "Включено",
"disabled": "Выключено",
"game_exec": "Установить исполняемый файл игры",
"game_executable": "Установить исполняемый файл игры",
"grasscutter_jar": "Установить Grasscutter JAR",
"toggle_encryption": "Переключить шифрование",
"java_path": "Установить пользовательский путь Java",

View File

@ -3,7 +3,7 @@ import Checkbox from './common/Checkbox'
import BigButton from './common/BigButton'
import TextInput from './common/TextInput'
import HelpButton from './common/HelpButton'
import { getConfig, saveConfig, setConfigOption } from '../../utils/configuration'
import { Configuration, getConfig, saveConfig, setConfigOption } from '../../utils/configuration'
import { translate } from '../../utils/language'
import { invoke } from '@tauri-apps/api/tauri'
@ -72,6 +72,24 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
})
}
getGameExecutable(config : Configuration) {
if(!config.game_install_path || !config.game_executable) {
alert('Game executable and/or path not set!')
return null
}
return config.game_install_path + '\\' + config.game_executable
}
getGameMetadataLocation(config : Configuration) {
if(!config.game_install_path || !config.game_executable) {
alert('Game executable and/or path not set!')
return null
}
return config.game_install_path + '\\' + config.game_executable.replace('.exe', '_Data') + '\\Managed\\Metadata'
}
async toggleGrasscutter() {
const config = await getConfig()
@ -89,7 +107,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
const config = await getConfig()
// Copy unpatched metadata to backup location
if(await invoke('copy_file_with_new_name', { path: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata\\global-metadata.dat', newPath: await dataDir() + 'cultivation\\metadata', newName: 'global-metadata-unpatched.dat' })) {
if(await invoke('copy_file_with_new_name', { path: this.getGameMetadataLocation(config) + '\\global-metadata.dat', newPath: await dataDir() + 'cultivation\\metadata', newName: 'global-metadata-unpatched.dat' })) {
// Backup successful
// Patch backedup metadata
@ -97,7 +115,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Patch successful
// Replace game metadata with patched metadata
if(!(await invoke('copy_file_with_new_name', { path: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', newPath: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata', newName: 'global-metadata.dat' }))) {
if(!(await invoke('copy_file_with_new_name', { path: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', newPath: this.getGameMetadataLocation(config), newName: 'global-metadata.dat' }))) {
// Replace failed
alert('Failed to replace game metadata!')
return
@ -114,8 +132,10 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
async playGame() {
const config = await getConfig()
if (!config.game_install_path) return alert('Game path not set!')
if(this.getGameExecutable(config) == null) {
return
}
// Connect to proxy
if (config.toggle_grasscutter) {
@ -124,13 +144,13 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Assume metadata has been patched
// Compare metadata files
if (!(await invoke('are_files_identical', { path1: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', path2: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata\\global-metadata.dat'}))) {
if (!(await invoke('are_files_identical', { path1: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', path2: this.getGameMetadataLocation(config) + '\\global-metadata.dat'}))) {
// Metadata is not patched
// Check to see if unpatched backup matches the game's version
if (await invoke('are_files_identical', { path1: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat', path2: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata\\global-metadata.dat'})) {
if (await invoke('are_files_identical', { path1: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat', path2: this.getGameMetadataLocation(config) + '\\global-metadata.dat'})) {
// Game's metadata is not patched, so we need to patch it
if(!(await invoke('copy_file_with_new_name', { path: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', newPath: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata', newName: 'global-metadata.dat' }))) {
if(!(await invoke('copy_file_with_new_name', { path: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', newPath: this.getGameMetadataLocation(config), newName: 'global-metadata.dat' }))) {
// Replace failed
alert('Failed to replace game metadata!')
return
@ -140,7 +160,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
alert('Deleting old metadata')
// Delete backed up metadata
if(!(await invoke('delete_file', { path: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat' }) && !(await invoke('delete_file', { path: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat' })))) {
if(!(await invoke('delete_file', { path: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat' })) && !(await invoke('delete_file', { path: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat' }))) {
// Delete failed
alert('Failed to delete backed up metadata!')
return
@ -154,12 +174,12 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
await this.patchMetadata()
}
let game_exe = config.game_install_path + '\\GenshinImpact.exe'
let game_exe = this.getGameExecutable(config) as string
if (game_exe.includes('\\')) {
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('\\') + 1)
game_exe = game_exe.substring((this.getGameExecutable(config) as string).lastIndexOf('\\') + 1)
} else {
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('/') + 1)
game_exe = game_exe.substring((this.getGameExecutable(config) as string).lastIndexOf('/') + 1)
}
// Save last connected server and port
@ -197,9 +217,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Check if metadata is patched
// Compare metadata files
if (await invoke('are_files_identical', { path1: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', path2: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata\\global-metadata.dat'})) {
if (await invoke('are_files_identical', { path1: await dataDir() + 'cultivation/metadata/global-metadata-patched.dat', path2: this.getGameMetadataLocation(config) + '\\global-metadata.dat'})) {
// Metadata is patched, so we need to unpatch it
if(!(await invoke('copy_file_with_new_name', { path: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat', newPath: config.game_install_path + '\\GenshinImpact_Data\\Managed\\Metadata', newName: 'global-metadata.dat' }))) {
if(!(await invoke('copy_file_with_new_name', { path: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat', newPath: this.getGameMetadataLocation(config), newName: 'global-metadata.dat' }))) {
// Replace failed
alert('Failed to unpatch game metadata!')
return
@ -210,11 +230,11 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Launch the program
const gameExists = await invoke('dir_exists', {
path: config.game_install_path + '\\GenshinImpact.exe'
path: this.getGameExecutable(config)
})
if (gameExists) await invoke('run_program', { path: config.game_install_path + '\\GenshinImpact.exe' })
else alert('Game not found! At: ' + config.game_install_path)
if (gameExists) await invoke('run_program', { path: this.getGameExecutable(config) })
else alert('Game not found! At: ' + this.getGameExecutable(config))
}
async launchServer() {

View File

@ -14,7 +14,8 @@ interface IProps {
readonly?: boolean
placeholder?: string
folder?: boolean
customClearBehaviour?: () => void
customClearBehaviour?: () => void,
openFolder?: string
}
interface IState {
@ -67,10 +68,12 @@ export default class DirInput extends React.Component<IProps, IState> {
directory: true
})
} else {
console.log(this.props.openFolder)
path = await open({
filters: [
{ name: 'Files', extensions: this.props.extensions || ['*'] }
]
],
defaultPath: this.props.openFolder
})
}

View File

@ -18,16 +18,17 @@ interface IProps {
}
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
encryption: boolean
game_install_path: string;
game_executable: 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;
encryption: boolean;
}
export default class Options extends React.Component<IProps, IState> {
@ -36,6 +37,7 @@ export default class Options extends React.Component<IProps, IState> {
this.state = {
game_install_path: '',
game_executable: '',
grasscutter_path: '',
java_path: '',
grasscutter_with_game: false,
@ -44,10 +46,11 @@ export default class Options extends React.Component<IProps, IState> {
bg_url_or_path: '',
themes: ['default'],
theme: '',
encryption: false
encryption: false,
}
this.setGameExec = this.setGameExec.bind(this)
this.setGamePath = this.setGamePath.bind(this)
this.setGameExecutable = this.setGameExecutable.bind(this)
this.setGrasscutterJar = this.setGrasscutterJar.bind(this)
this.setJavaPath = this.setJavaPath.bind(this)
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
@ -58,7 +61,7 @@ export default class Options extends React.Component<IProps, IState> {
async componentDidMount() {
const config = await getConfig()
const languages = await getLanguages()
// Remove jar from path
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
@ -66,33 +69,52 @@ export default class Options extends React.Component<IProps, IState> {
this.setState({
game_install_path: config.game_install_path || '',
game_executable: config.game_executable || '',
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.customBackground || '',
themes: (await getThemeList()).map(t => t.name),
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'),
})
this.forceUpdate()
}
setGameExec(value: string) {
setGamePath(value: string) {
setConfigOption('game_install_path', value)
this.setState({
game_install_path: value
game_install_path: value,
})
}
setGameExecutable(value: string) {
if (this.state.game_install_path != '') {
if (value.includes(this.state.game_install_path)) {
value = value.replace(this.state.game_install_path + '\\', '')
setConfigOption('game_executable', value)
this.setState({
game_executable: value,
})
} else {
alert('Game executable must be inside the game folder!')
}
} else {
alert('Please set the game install path first!')
}
}
setGrasscutterJar(value: string) {
setConfigOption('grasscutter_path', value)
this.setState({
grasscutter_path: value
grasscutter_path: value,
})
}
@ -100,7 +122,7 @@ export default class Options extends React.Component<IProps, IState> {
setConfigOption('java_path', value)
this.setState({
java_path: value
java_path: value,
})
}
@ -119,7 +141,7 @@ export default class Options extends React.Component<IProps, IState> {
setConfigOption('grasscutter_with_game', changedVal)
this.setState({
grasscutter_with_game: changedVal
grasscutter_with_game: changedVal,
})
}
@ -130,16 +152,16 @@ export default class Options extends React.Component<IProps, IState> {
if (!isUrl) {
const filename = value.replace(/\\/g, '/').split('/').pop()
const localBgPath = (await dataDir() as string).replace(/\\/g, '/')
const localBgPath = ((await dataDir()) as string).replace(/\\/g, '/')
await setConfigOption('customBackground', `${localBgPath}/cultivation/bg/${filename}`)
// Copy the file over to the local directory
await invoke('copy_file', {
path: value.replace(/\\/g, '/'),
newPath: `${localBgPath}cultivation/bg/`
newPath: `${localBgPath}cultivation/bg/`,
})
window.location.reload()
} else {
await setConfigOption('customBackground', value)
@ -163,68 +185,81 @@ export default class Options extends React.Component<IProps, IState> {
await server.toggleEncryption(folderPath + '/config.json')
this.setState({
encryption: await translate(await server.encryptionEnabled(folderPath + '/config.json') ? 'options.enabled' : 'options.disabled')
encryption: await translate(
(await server.encryptionEnabled(folderPath + '/config.json')) ? 'options.enabled' : 'options.disabled'
),
})
}
render() {
return (
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
<div className='OptionSection' id="menuOptionsContainerGameExec">
<div className='OptionLabel' id="menuOptionsLabelGameExec">
<Tr text="options.game_exec" />
<div className="OptionSection" id="menuOptionsContainerGamePath">
<div className="OptionLabel" id="menuOptionsLabelGamePath">
<Tr text="options.game_path" />
</div>
<div className='OptionValue' id="menuOptionsDirGameExec">
<DirInput onChange={this.setGameExec} value={this.state?.game_install_path} folder={true} />
<div className="OptionValue" id="menuOptionsDirGamePath">
<DirInput onChange={this.setGamePath} value={this.state?.game_install_path} folder={true} />
</div>
</div>
<div className='OptionSection' id="menuOptionsContainerGCJar">
<div className='OptionLabel' id="menuOptionsLabelGCJar">
<div className="OptionSection" id="menuOptionsContainerGameExecutable">
<div className="OptionLabel" id="menuOptionsLabelGameExecutable">
<Tr text="options.game_executable" />
</div>
<div className="OptionValue" id="menuOptionsDirGameExecutable">
<DirInput onChange={this.setGameExecutable} value={this.state?.game_executable} folder={false} extensions={['exe']} openFolder={this.state?.game_install_path} />
</div>
</div>
<div className="OptionSection" id="menuOptionsContainerGCJar">
<div className="OptionLabel" id="menuOptionsLabelGCJar">
<Tr text="options.grasscutter_jar" />
</div>
<div className='OptionValue' id="menuOptionsDirGCJar">
<div className="OptionValue" id="menuOptionsDirGCJar">
<DirInput onChange={this.setGrasscutterJar} value={this.state?.grasscutter_path} extensions={['jar']} />
</div>
</div>
<div className='OptionSection' id="menuOptionsContainerToggleEnc">
<div className='OptionLabel' id="menuOptionsLabelToggleEnc">
<div className="OptionSection" id="menuOptionsContainerToggleEnc">
<div className="OptionLabel" id="menuOptionsLabelToggleEnc">
<Tr text="options.toggle_encryption" />
</div>
<div className='OptionValue' id="menuOptionsButtonToggleEnc">
<div className="OptionValue" id="menuOptionsButtonToggleEnc">
<BigButton onClick={this.toggleEncryption} id="toggleEnc">
{
this.state.encryption
}
{this.state.encryption}
</BigButton>
</div>
</div>
<Divider />
<div className='OptionSection' id="menuOptionsContainerGCWGame">
<div className='OptionLabel' id="menuOptionsLabelGCWDame">
<div className="OptionSection" id="menuOptionsContainerGCWGame">
<div className="OptionLabel" id="menuOptionsLabelGCWDame">
<Tr text="options.grasscutter_with_game" />
</div>
<div className='OptionValue' id="menuOptionsCheckboxGCWGame">
<Checkbox onChange={this.toggleGrasscutterWithGame} checked={this.state?.grasscutter_with_game} id="gcWithGame" />
<div className="OptionValue" id="menuOptionsCheckboxGCWGame">
<Checkbox
onChange={this.toggleGrasscutterWithGame}
checked={this.state?.grasscutter_with_game}
id="gcWithGame"
/>
</div>
</div>
<Divider />
<div className='OptionSection' id="menuOptionsContainerThemes">
<div className='OptionLabel' id="menuOptionsLabelThemes">
<div className="OptionSection" id="menuOptionsContainerThemes">
<div className="OptionLabel" id="menuOptionsLabelThemes">
<Tr text="options.theme" />
</div>
<div className='OptionValue' id="menuOptionsSelectThemes">
<select value={this.state.theme} id="menuOptionsSelectMenuThemes" onChange={(event) => {
this.setTheme(event.target.value)
}}>
{this.state.themes.map(t => (
<option
key={t}
value={t}>
<div className="OptionValue" id="menuOptionsSelectThemes">
<select
value={this.state.theme}
id="menuOptionsSelectMenuThemes"
onChange={(event) => {
this.setTheme(event.target.value)
}}
>
{this.state.themes.map((t) => (
<option key={t} value={t}>
{t}
</option>
))}
@ -234,20 +269,20 @@ export default class Options extends React.Component<IProps, IState> {
<Divider />
<div className='OptionSection' id="menuOptionsContainerJavaPath">
<div className='OptionLabel' id="menuOptionsLabelJavaPath">
<div className="OptionSection" id="menuOptionsContainerJavaPath">
<div className="OptionLabel" id="menuOptionsLabelJavaPath">
<Tr text="options.java_path" />
</div>
<div className='OptionValue' id="menuOptionsDirJavaPath">
<div className="OptionValue" id="menuOptionsDirJavaPath">
<DirInput onChange={this.setJavaPath} value={this.state?.java_path} extensions={['exe']} />
</div>
</div>
<div className='OptionSection' id="menuOptionsContainerBG">
<div className='OptionLabel' id="menuOptionsLabelBG">
<div className="OptionSection" id="menuOptionsContainerBG">
<div className="OptionLabel" id="menuOptionsLabelBG">
<Tr text="options.background" />
</div>
<div className='OptionValue' id="menuOptionsDirBG">
<div className="OptionValue" id="menuOptionsDirBG">
<DirInput
onChange={this.setCustomBackground}
value={this.state?.bg_url_or_path}
@ -262,19 +297,20 @@ export default class Options extends React.Component<IProps, IState> {
</div>
</div>
<div className='OptionSection' id="menuOptionsContainerLang">
<div className='OptionLabel' id="menuOptionsLabelLang">
<div className="OptionSection" id="menuOptionsContainerLang">
<div className="OptionLabel" id="menuOptionsLabelLang">
<Tr text="options.language" />
</div>
<div className='OptionValue' id="menuOptionsSelectLang">
<select value={this.state.current_language} id="menuOptionsSelectMenuLang" onChange={(event) => {
this.setLanguage(event.target.value)
}}>
{this.state.language_options.map(lang => (
<option
key={Object.keys(lang)[0]}
value={Object.keys(lang)[0]}>
<div className="OptionValue" id="menuOptionsSelectLang">
<select
value={this.state.current_language}
id="menuOptionsSelectMenuLang"
onChange={(event) => {
this.setLanguage(event.target.value)
}}
>
{this.state.language_options.map((lang) => (
<option key={Object.keys(lang)[0]} value={Object.keys(lang)[0]}>
{lang[Object.keys(lang)[0]]}
</option>
))}
@ -284,4 +320,4 @@ export default class Options extends React.Component<IProps, IState> {
</Menu>
)
}
}
}

View File

@ -8,7 +8,7 @@ let defaultConfig: Configuration
defaultConfig = {
toggle_grasscutter: false,
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game',
game_version: 'global',
game_executable: 'GenshinImpact.exe',
grasscutter_with_game: false,
grasscutter_path: '',
java_path: '',
@ -31,7 +31,7 @@ let defaultConfig: Configuration
export interface Configuration {
toggle_grasscutter: boolean
game_install_path: string
game_version: string
game_executable: string
grasscutter_with_game: boolean
grasscutter_path: string
java_path: string