mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2026-02-05 17:56:53 +01:00
Autopatching on game launch
Plus adding some non-functional options for later Need to add support for Chinese version of the game
This commit is contained in:
8
src-tauri/Cargo.lock
generated
8
src-tauri/Cargo.lock
generated
@@ -742,7 +742,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
|||||||
name = "cultivation"
|
name = "cultivation"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cc",
|
||||||
"duct",
|
"duct",
|
||||||
|
"file_diff",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"hudsucker",
|
"hudsucker",
|
||||||
@@ -1043,6 +1045,12 @@ dependencies = [
|
|||||||
"rustc_version 0.3.3",
|
"rustc_version 0.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "file_diff"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ rcgen = { version = "0.9", features = ["x509-parser"] }
|
|||||||
libloading = "0.7"
|
libloading = "0.7"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
# other
|
||||||
|
file_diff = "1.0.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
"grasscutter_latest": "Download Grasscutter Latest",
|
"grasscutter_latest": "Download Grasscutter Latest",
|
||||||
"grasscutter_stable_update": "Update Grasscutter Stable",
|
"grasscutter_stable_update": "Update Grasscutter Stable",
|
||||||
"grasscutter_latest_update": "Update Grasscutter Latest",
|
"grasscutter_latest_update": "Update Grasscutter Latest",
|
||||||
"resources": "Download Grasscutter Resources"
|
"resources": "Download Grasscutter Resources",
|
||||||
|
"game": "Download Game"
|
||||||
},
|
},
|
||||||
"download_status": {
|
"download_status": {
|
||||||
"downloading": "Downloading",
|
"downloading": "Downloading",
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
"gc_dev_jar": "Download the latest development Grasscutter build, which includes jar file and data files.",
|
"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_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.",
|
"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"
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
use file_diff::diff;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn rename(path: String, new_name: String) {
|
pub fn rename(path: String, new_name: String) {
|
||||||
@@ -33,3 +34,8 @@ pub fn dir_is_empty(path: &str) -> bool {
|
|||||||
pub fn dir_delete(path: &str) {
|
pub fn dir_delete(path: &str) {
|
||||||
fs::remove_dir_all(path).unwrap();
|
fs::remove_dir_all(path).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn are_files_identical(path1: &str, path2: &str) -> bool {
|
||||||
|
return diff(path1, path2);
|
||||||
|
}
|
||||||
@@ -50,6 +50,8 @@ fn main() {
|
|||||||
system_helpers::run_jar,
|
system_helpers::run_jar,
|
||||||
system_helpers::open_in_browser,
|
system_helpers::open_in_browser,
|
||||||
system_helpers::copy_file,
|
system_helpers::copy_file,
|
||||||
|
system_helpers::copy_file_with_new_name,
|
||||||
|
system_helpers::delete_file,
|
||||||
system_helpers::install_location,
|
system_helpers::install_location,
|
||||||
system_helpers::is_elevated,
|
system_helpers::is_elevated,
|
||||||
proxy::set_proxy_addr,
|
proxy::set_proxy_addr,
|
||||||
@@ -59,6 +61,7 @@ fn main() {
|
|||||||
file_helpers::dir_exists,
|
file_helpers::dir_exists,
|
||||||
file_helpers::dir_is_empty,
|
file_helpers::dir_is_empty,
|
||||||
file_helpers::dir_delete,
|
file_helpers::dir_delete,
|
||||||
|
file_helpers::are_files_identical,
|
||||||
downloader::download_file,
|
downloader::download_file,
|
||||||
downloader::stop_download,
|
downloader::stop_download,
|
||||||
lang::get_lang,
|
lang::get_lang,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
@@ -23,16 +24,38 @@ fn dll_encrypt_global_metadata(data : *mut u8, size : u64) -> Result<bool, Box<d
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn patch_metadata(metadata_folder: &str) {
|
pub fn patch_metadata(metadata_folder: &str) -> bool {
|
||||||
let metadata_file = &(metadata_folder.to_owned() + "\\global-metadata.dat");
|
let metadata_file = &(metadata_folder.to_owned() + "\\global-metadata-unpatched.dat");
|
||||||
println!("Patching metadata file: {}", metadata_file);
|
println!("Patching metadata file: {}", metadata_file);
|
||||||
let decrypted = decrypt_metadata(metadata_file);
|
let decrypted = decrypt_metadata(metadata_file);
|
||||||
|
if do_vecs_match(&decrypted, &Vec::new()) {
|
||||||
|
println!("Failed to decrypt metadata file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let modified = replace_keys(&decrypted);
|
let modified = replace_keys(&decrypted);
|
||||||
|
if do_vecs_match(&modified, &Vec::new()) {
|
||||||
|
println!("Failed to replace keys in metadata file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
let encrypted = encrypt_metadata(&modified);
|
let encrypted = encrypt_metadata(&modified);
|
||||||
|
if do_vecs_match(&encrypted, &Vec::new()) {
|
||||||
|
println!("Failed to re-encrypt metadata file.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//write encrypted to file
|
//write encrypted to file
|
||||||
let mut file = File::create(&(metadata_folder.to_owned() + "\\encrypted-metadata.dat")).unwrap();
|
let mut file = match OpenOptions::new().create(true).write(true).open(&(metadata_folder.to_owned() + "\\global-metadata-patched.dat")) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to open global-metadata: {}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
file.write_all(&encrypted).unwrap();
|
file.write_all(&encrypted).unwrap();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_metadata(file_path: &str) -> Vec<u8> {
|
fn decrypt_metadata(file_path: &str) -> Vec<u8> {
|
||||||
@@ -124,3 +147,8 @@ fn encrypt_metadata(old_data: &Vec<u8>) -> Vec<u8> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_vecs_match<T: PartialEq>(a: &Vec<T>, b: &Vec<T>) -> bool {
|
||||||
|
let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count();
|
||||||
|
matching == a.len() && matching == b.len()
|
||||||
|
}
|
||||||
@@ -66,6 +66,36 @@ pub fn copy_file(path: String, new_path: String) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String) -> bool {
|
||||||
|
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||||
|
|
||||||
|
// If the new path doesn't exist, create it.
|
||||||
|
if !file_helpers::dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||||
|
std::fs::create_dir_all(&new_path).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy old to new
|
||||||
|
match std::fs::copy(&path, format!("{}/{}", new_path, new_name)) {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to copy file: {}", e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn delete_file(path: String) -> bool {
|
||||||
|
match std::fs::remove_file(path) {
|
||||||
|
Ok(_) => return true,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failed to delete file: {}", e);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn install_location() -> String {
|
pub fn install_location() -> String {
|
||||||
let mut exe_path = std::env::current_exe().unwrap();
|
let mut exe_path = std::env::current_exe().unwrap();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import BigButton from './common/BigButton'
|
|||||||
import TextInput from './common/TextInput'
|
import TextInput from './common/TextInput'
|
||||||
import HelpButton from './common/HelpButton'
|
import HelpButton from './common/HelpButton'
|
||||||
import { getConfig, saveConfig, setConfigOption } from '../../utils/configuration'
|
import { getConfig, saveConfig, setConfigOption } from '../../utils/configuration'
|
||||||
import { patchMetadata } from '../../utils/patcher'
|
|
||||||
import { translate } from '../../utils/language'
|
import { translate } from '../../utils/language'
|
||||||
import { invoke } from '@tauri-apps/api/tauri'
|
import { invoke } from '@tauri-apps/api/tauri'
|
||||||
|
|
||||||
@@ -87,7 +86,30 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
async patchMetadata() {
|
async patchMetadata() {
|
||||||
await patchMetadata()
|
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' })) {
|
||||||
|
// Backup successful
|
||||||
|
|
||||||
|
// Patch backedup metadata
|
||||||
|
if(await invoke('patch_metadata', {metadataFolder: await dataDir() + 'cultivation/metadata'})) {
|
||||||
|
// 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' }))) {
|
||||||
|
// Replace failed
|
||||||
|
alert('Failed to replace game metadata!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert ('Failed to patch metadata!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert ('Failed to backup metadata!')
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async playGame() {
|
async playGame() {
|
||||||
@@ -97,7 +119,42 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
|
|
||||||
// Connect to proxy
|
// Connect to proxy
|
||||||
if (config.toggle_grasscutter) {
|
if (config.toggle_grasscutter) {
|
||||||
let game_exe = config.game_install_path
|
// Check if metadata has been backed up
|
||||||
|
if (await invoke('dir_exists', { path: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat'})) {
|
||||||
|
// 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'}))) {
|
||||||
|
// 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'})) {
|
||||||
|
// 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' }))) {
|
||||||
|
// Replace failed
|
||||||
|
alert('Failed to replace game metadata!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Game has probably been updated. We need to repatch the game...
|
||||||
|
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' })))) {
|
||||||
|
// Delete failed
|
||||||
|
alert('Failed to delete backed up metadata!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.patchMetadata()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Assume metadata has not been patched
|
||||||
|
await this.patchMetadata()
|
||||||
|
}
|
||||||
|
|
||||||
|
let game_exe = config.game_install_path + '\\GenshinImpact.exe'
|
||||||
|
|
||||||
if (game_exe.includes('\\')) {
|
if (game_exe.includes('\\')) {
|
||||||
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('\\') + 1)
|
game_exe = game_exe.substring(config.game_install_path.lastIndexOf('\\') + 1)
|
||||||
@@ -134,14 +191,29 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
javaPath: config.java_path || ''
|
javaPath: config.java_path || ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Check if metadata has been backed up
|
||||||
|
if (await invoke('dir_exists', { path: await dataDir() + 'cultivation/metadata/global-metadata-unpatched.dat'})) {
|
||||||
|
// 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'})) {
|
||||||
|
// 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' }))) {
|
||||||
|
// Replace failed
|
||||||
|
alert('Failed to unpatch game metadata!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch the program
|
// Launch the program
|
||||||
const gameExists = await invoke('dir_exists', {
|
const gameExists = await invoke('dir_exists', {
|
||||||
path: config.game_install_path
|
path: config.game_install_path + '\\GenshinImpact.exe'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (gameExists) await invoke('run_program', { path: config.game_install_path })
|
if (gameExists) await invoke('run_program', { path: config.game_install_path + '\\GenshinImpact.exe' })
|
||||||
else alert('Game not found! At: ' + config.game_install_path)
|
else alert('Game not found! At: ' + config.game_install_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +273,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
{
|
{
|
||||||
this.state.grasscutterEnabled && (
|
this.state.grasscutterEnabled && (
|
||||||
<div>
|
<div>
|
||||||
<div className="ServerConfig" id="serverConfigContainer">Compiled with problems:
|
<div className="ServerConfig" id="serverConfigContainer">
|
||||||
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
|
<TextInput id="ip" key="ip" placeholder={this.state.ipPlaceholder} onChange={this.setIp} initalValue={this.state.ip} />
|
||||||
<TextInput style={{
|
<TextInput style={{
|
||||||
width: '10%',
|
width: '10%',
|
||||||
@@ -216,7 +288,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
|
|
||||||
|
|
||||||
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
<div className="ServerLaunchButtons" id="serverLaunchContainer">
|
||||||
<BigButton onClick={this.patchMetadata} id="officialPlay">Patch Metadata</BigButton>
|
<BigButton onClick={this.playGame} id="officialPlay">{this.state.buttonLabel}</BigButton>
|
||||||
<BigButton onClick={this.launchServer} id="serverLaunch">
|
<BigButton onClick={this.launchServer} id="serverLaunch">
|
||||||
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
<img className="ServerIcon" id="serverLaunchIcon" src={Server} />
|
||||||
</BigButton>
|
</BigButton>
|
||||||
|
|||||||
@@ -269,6 +269,21 @@ export default class Downloads extends React.Component<IProps, IState> {
|
|||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<div className='DownloadMenuSection' id="downloadMenuContainerResources">
|
||||||
|
<div className='DownloadLabel' id="downloadMenuLabelResources">
|
||||||
|
<Tr text="downloads.game" />
|
||||||
|
<HelpButton>
|
||||||
|
<Tr text="help.game" />
|
||||||
|
</HelpButton>
|
||||||
|
</div>
|
||||||
|
<div className='DownloadValue' id="downloadMenuButtonResources">
|
||||||
|
<BigButton disabled={this.state.resources_downloading || !this.state.grasscutter_set || this.state.resources_exist} onClick={this.downloadResources} id="resourcesBtn" >
|
||||||
|
<Tr text="components.download" />
|
||||||
|
</BigButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
<Tr text="options.game_exec" />
|
<Tr text="options.game_exec" />
|
||||||
</div>
|
</div>
|
||||||
<div className='OptionValue' id="menuOptionsDirGameExec">
|
<div className='OptionValue' id="menuOptionsDirGameExec">
|
||||||
<DirInput onChange={this.setGameExec} value={this.state?.game_install_path} extensions={['exe']} />
|
<DirInput onChange={this.setGameExec} value={this.state?.game_install_path} folder={true} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='OptionSection' id="menuOptionsContainerGCJar">
|
<div className='OptionSection' id="menuOptionsContainerGCJar">
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ let defaultConfig: Configuration
|
|||||||
(async() => {
|
(async() => {
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
toggle_grasscutter: false,
|
toggle_grasscutter: false,
|
||||||
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game\\GenshinImpact.exe',
|
game_install_path: 'C:\\Program Files\\Genshin Impact\\Genshin Impact game',
|
||||||
|
game_version: 'global',
|
||||||
grasscutter_with_game: false,
|
grasscutter_with_game: false,
|
||||||
grasscutter_path: '',
|
grasscutter_path: '',
|
||||||
java_path: '',
|
java_path: '',
|
||||||
@@ -30,6 +31,7 @@ let defaultConfig: Configuration
|
|||||||
export interface Configuration {
|
export interface Configuration {
|
||||||
toggle_grasscutter: boolean
|
toggle_grasscutter: boolean
|
||||||
game_install_path: string
|
game_install_path: string
|
||||||
|
game_version: string
|
||||||
grasscutter_with_game: boolean
|
grasscutter_with_game: boolean
|
||||||
grasscutter_path: string
|
grasscutter_path: string
|
||||||
java_path: string
|
java_path: string
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api'
|
|
||||||
|
|
||||||
export async function patchMetadata() {
|
|
||||||
console.log('patching')
|
|
||||||
await invoke('patch_metadata', {metadataFolder: 'C:\\Users\\benja\\Desktop'})
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user