mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-14 16:14:48 +01:00
Automatically apply RSA patch
This commit is contained in:
@@ -17,7 +17,7 @@ import { invoke } from '@tauri-apps/api'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { unpatchGame } from '../utils/metadata'
|
||||
import { unpatchGame } from '../utils/rsa'
|
||||
import DownloadHandler from '../utils/download'
|
||||
|
||||
// Graphics
|
||||
@@ -65,16 +65,16 @@ export class Main extends React.Component<IProps, IState> {
|
||||
setConfigOption('grasscutter_path', payload)
|
||||
})
|
||||
|
||||
// Emitted for metadata replacing-purposes
|
||||
// Emitted for rsa replacing-purposes
|
||||
listen('game_closed', async () => {
|
||||
const wasPatched = await getConfigOption('patch_metadata')
|
||||
const wasPatched = await getConfigOption('patch_rsa')
|
||||
|
||||
if (wasPatched) {
|
||||
const unpatched = await unpatchGame()
|
||||
|
||||
if (!unpatched) {
|
||||
alert(
|
||||
`Could not unpatch game! (You should be able to find your metadata backup in ${await dataDir()}\\cultivation\\)`
|
||||
`Could not unpatch game! (Delete version.dll in your game folder)`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import Plus from '../../resources/icons/plus.svg'
|
||||
import './ServerLaunchSection.css'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { getGameExecutable, getGameVersion } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/metadata'
|
||||
import { patchGame, unpatchGame } from '../../utils/rsa'
|
||||
|
||||
interface IProps {
|
||||
openExtras: (playGame: () => void) => void
|
||||
@@ -109,7 +109,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
|
||||
// Connect to proxy
|
||||
if (config.toggle_grasscutter) {
|
||||
if (config.patch_metadata) {
|
||||
if (config.patch_rsa) {
|
||||
const gameVersion = await getGameVersion()
|
||||
console.log(gameVersion)
|
||||
|
||||
@@ -120,16 +120,16 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
return
|
||||
}
|
||||
|
||||
if (gameVersion?.major == 2 && gameVersion?.minor < 8) {
|
||||
if (gameVersion?.major == 2 && gameVersion?.minor < 9) {
|
||||
alert(
|
||||
'Game version is too old for metadata patching. Please disable metadata patching in the settings and try again.'
|
||||
'Game version is too old for RSA patching. Please disable RSA patching in the settings and try again.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (gameVersion?.major == 3 && gameVersion?.minor >= 1) {
|
||||
if (gameVersion?.major == 3 && gameVersion?.minor < 1) {
|
||||
alert(
|
||||
'Game version is too new for metadata patching. TO FIX: Please disable metadata patching in the settings menu to launch the game!! \nNOTE: You will require an RSA patch to play the game.'
|
||||
'Game version is too old for RSA patching. Please disable RSA patching in the settings and try again.'
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
|
||||
if (!unpatched) {
|
||||
alert(
|
||||
`Could not unpatch game, aborting launch! (You can find your metadata backup in ${await dataDir()}\\cultivation\\)`
|
||||
`Could not unpatch game, aborting launch! (You can unpatch in your game install folder, either remove version.dll or repair mhypbase.dll in launcher)`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as server from '../../../utils/server'
|
||||
import './Options.css'
|
||||
import BigButton from '../common/BigButton'
|
||||
import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/metadata'
|
||||
import * as meta from '../../../utils/rsa'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import TextInput from '../common/TextInput'
|
||||
|
||||
@@ -33,7 +33,7 @@ interface IState {
|
||||
themes: string[]
|
||||
theme: string
|
||||
encryption: boolean
|
||||
patch_metadata: boolean
|
||||
patch_rsa: boolean
|
||||
use_internal_proxy: boolean
|
||||
wipe_login: boolean
|
||||
horny_mode: boolean
|
||||
@@ -61,7 +61,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
themes: ['default'],
|
||||
theme: '',
|
||||
encryption: false,
|
||||
patch_metadata: false,
|
||||
patch_rsa: false,
|
||||
use_internal_proxy: false,
|
||||
wipe_login: false,
|
||||
horny_mode: false,
|
||||
@@ -82,7 +82,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.toggleGrasscutterWithGame = this.toggleGrasscutterWithGame.bind(this)
|
||||
this.setCustomBackground = this.setCustomBackground.bind(this)
|
||||
this.toggleEncryption = this.toggleEncryption.bind(this)
|
||||
this.restoreMetadata = this.restoreMetadata.bind(this)
|
||||
this.removeRSA = this.removeRSA.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -108,7 +108,7 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
themes: (await getThemeList()).map((t) => t.name),
|
||||
theme: config.theme || 'default',
|
||||
encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled'),
|
||||
patch_metadata: config.patch_metadata || 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,
|
||||
@@ -266,8 +266,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
async restoreMetadata() {
|
||||
await meta.restoreMetadata(this.props.downloadManager)
|
||||
async removeRSA() {
|
||||
await meta.unpatchGame()
|
||||
}
|
||||
|
||||
async installCert() {
|
||||
@@ -311,24 +311,24 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
)}
|
||||
<div className="OptionSection" id="menuOptionsContainermetaDownload">
|
||||
<div className="OptionLabel" id="menuOptionsLabelmetaDownload">
|
||||
<Tr text="options.recover_metadata" />
|
||||
<HelpButton contents="help.emergency_metadata" />
|
||||
<Tr text="options.recover_rsa" />
|
||||
<HelpButton contents="help.emergency_rsa" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsButtonmetaDownload">
|
||||
<BigButton onClick={this.restoreMetadata} id="metaDownload">
|
||||
<BigButton onClick={this.removeRSA} id="metaDownload">
|
||||
<Tr text="components.download" />
|
||||
</BigButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionSection" id="menuOptionsContainerPatchMeta">
|
||||
<div className="OptionLabel" id="menuOptionsLabelPatchMeta">
|
||||
<Tr text="options.patch_metadata" />
|
||||
<HelpButton contents="help.patch_metadata" />
|
||||
<Tr text="options.patch_rsa" />
|
||||
<HelpButton contents="help.patch_rsa" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxPatchMeta">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('patch_metadata')}
|
||||
checked={this.state?.patch_metadata}
|
||||
onChange={() => this.toggleOption('patch_rsa')}
|
||||
checked={this.state?.patch_rsa}
|
||||
id="patchMeta"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ let defaultConfig: Configuration
|
||||
theme: 'default',
|
||||
https_enabled: false,
|
||||
debug_enabled: false,
|
||||
patch_metadata: false,
|
||||
patch_rsa: true,
|
||||
use_internal_proxy: true,
|
||||
wipe_login: false,
|
||||
horny_mode: false,
|
||||
@@ -46,7 +46,7 @@ export interface Configuration {
|
||||
theme: string
|
||||
https_enabled: boolean
|
||||
debug_enabled: boolean
|
||||
patch_metadata: boolean
|
||||
patch_rsa: boolean
|
||||
use_internal_proxy: boolean
|
||||
wipe_login: boolean
|
||||
horny_mode: boolean
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import DownloadHandler from './download'
|
||||
import { getGameDataFolder } from './game'
|
||||
|
||||
export async function patchMetadata() {
|
||||
const metadataExists = await invoke('dir_exists', {
|
||||
path: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
})
|
||||
|
||||
if (!metadataExists) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('Copying unpatched metadata to backup location')
|
||||
|
||||
// Copy unpatched metadata to backup location
|
||||
const copiedMeta = await invoke('copy_file_with_new_name', {
|
||||
path: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
newPath: await getBackupMetadataPath(),
|
||||
newName: 'global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
if (!copiedMeta) {
|
||||
console.log(await getBackupMetadataPath())
|
||||
return false
|
||||
}
|
||||
|
||||
// backup was successful! Time to patch
|
||||
|
||||
console.log('Patching backedup metadata')
|
||||
|
||||
const patchedMeta = await invoke('patch_metadata', {
|
||||
metadataFolder: await getBackupMetadataPath(),
|
||||
})
|
||||
|
||||
if (!patchedMeta) {
|
||||
// Remove metadata backup, in case it invalid or something
|
||||
await invoke('delete_file', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Patch also worked! Time to replace
|
||||
console.log('Replacing unpatched game metadata with patched metadata')
|
||||
|
||||
const replacedMeta = await invoke('copy_file_with_new_name', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat',
|
||||
})
|
||||
|
||||
if (!replacedMeta) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('Replacement successful!')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function patchGame() {
|
||||
const backupExists = await invoke('dir_exists', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
if (!backupExists) {
|
||||
// No backup found? Patching creates one
|
||||
const patched = await patchMetadata()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a patch already?
|
||||
const patchedExists = await invoke('dir_exists', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
})
|
||||
|
||||
if (!patchedExists) {
|
||||
// No patch found? Patching creates one
|
||||
const patched = await patchMetadata()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Are we already patched? If so, that's fine, just continue as normal
|
||||
const gameIsPatched = await invoke('are_files_identical', {
|
||||
path1: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
path2: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
})
|
||||
|
||||
if (gameIsPatched) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Is the current backup the same as the games current metadata?
|
||||
const backupIsCurrent = await invoke('are_files_identical', {
|
||||
path1: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
path2: (await getGameMetadataPath()) + '\\global-metadata.dat',
|
||||
})
|
||||
|
||||
// Game has probably been updated. We need to repatch the game...
|
||||
if (!backupIsCurrent) {
|
||||
const deletedOldBackup = await invoke('delete_file', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
const deletedOldPatched = await invoke('delete_file', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
})
|
||||
|
||||
// It's fine if these deletes fail. The game will be replaced anyway.
|
||||
if (!deletedOldBackup) {
|
||||
console.log('Warning: Failed to delete old backup!')
|
||||
}
|
||||
|
||||
if (!deletedOldPatched) {
|
||||
console.log('Warning: Failed to delete old patched metadata!')
|
||||
}
|
||||
|
||||
console.log('Patching Metadata')
|
||||
|
||||
const patched = await patchMetadata()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
console.log('Metadata is not patched')
|
||||
console.log('Replacing unpatched metadata')
|
||||
|
||||
// Finally, replace the unpatched metadata with the patched one
|
||||
const replaced = await invoke('copy_file_with_new_name', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-patched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat',
|
||||
})
|
||||
|
||||
if (!replaced) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function unpatchGame() {
|
||||
const backupExists = await invoke('dir_exists', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
if (!backupExists) {
|
||||
// Let's just hope the game isn't on a patched metadata since we don't have a backup...
|
||||
return true
|
||||
}
|
||||
|
||||
const replaced = await invoke('copy_file_with_new_name', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
newPath: await getGameMetadataPath(),
|
||||
newName: 'global-metadata.dat',
|
||||
})
|
||||
|
||||
return replaced
|
||||
}
|
||||
|
||||
export async function getGameMetadataPath() {
|
||||
const gameData = await getGameDataFolder()
|
||||
|
||||
if (!gameData) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (gameData + '\\Managed\\Metadata').replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
export async function getBackupMetadataPath() {
|
||||
return (await dataDir()) + 'cultivation\\metadata'
|
||||
}
|
||||
|
||||
export async function globalMetadataLink() {
|
||||
// TODO: Get metadata based on current game version.
|
||||
const versionAPIUrl =
|
||||
'https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10&sub_channel_id=0'
|
||||
|
||||
// Get versions from API
|
||||
const versions = JSON.parse(
|
||||
await invoke('web_get', {
|
||||
url: versionAPIUrl,
|
||||
})
|
||||
)
|
||||
|
||||
if (!versions || versions.retcode !== 0) {
|
||||
console.log('Failed to get versions from API')
|
||||
return null
|
||||
}
|
||||
|
||||
// Get latest version
|
||||
const latest = versions.data.game.latest
|
||||
|
||||
return (latest.decompressed_path as string) + '/GenshinImpact_Data/Managed/Metadata/global-metadata.dat'
|
||||
}
|
||||
|
||||
export async function restoreMetadata(manager: DownloadHandler) {
|
||||
const metaLink = await globalMetadataLink()
|
||||
|
||||
if (!metaLink) {
|
||||
console.log('Could not get global metadata link!')
|
||||
return false
|
||||
}
|
||||
|
||||
// Should make sure metadata path exists since the user may have deleted it
|
||||
await invoke('dir_create', {
|
||||
path: await getBackupMetadataPath(),
|
||||
})
|
||||
|
||||
// It is possible the unpatched backup is mistakenly patched
|
||||
await invoke('delete_file', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
// Download the file
|
||||
manager.addDownload(metaLink, (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat', () => {
|
||||
unpatchGame()
|
||||
})
|
||||
console.log('Restoring backedup metadata')
|
||||
|
||||
await unpatchGame()
|
||||
|
||||
return true
|
||||
}
|
||||
112
src/utils/rsa.ts
Normal file
112
src/utils/rsa.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import DownloadHandler from './download'
|
||||
import { getGameFolder } from './game'
|
||||
|
||||
interface IProps {
|
||||
downloadHandler: DownloadHandler
|
||||
}
|
||||
|
||||
export async function patchRSA() {
|
||||
const rsaExists = await invoke('dir_exists', {
|
||||
path: (await getBackupRSAPath()) + '\\version.dll',
|
||||
})
|
||||
|
||||
if (rsaExists) {
|
||||
// Already patched
|
||||
return true
|
||||
}
|
||||
|
||||
console.log('Downloading rsa patch to backup location')
|
||||
|
||||
// Download RSA patch to backup location
|
||||
const downloadedRSA = await downloadRSA(this.props.downloadHandler)
|
||||
|
||||
if (!downloadedRSA) {
|
||||
console.log(await getBackupRSAPath())
|
||||
return false
|
||||
}
|
||||
|
||||
console.log('RSA download successful!')
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function patchGame() {
|
||||
// Do we have a patch already?
|
||||
const patchedExists = await invoke('dir_exists', {
|
||||
path: (await getBackupRSAPath()) + '\\version.dll',
|
||||
})
|
||||
|
||||
if (!patchedExists) {
|
||||
// No patch found? Patching creates one
|
||||
const patched = await patchRSA()
|
||||
|
||||
if (!patched) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Are we already patched with mhypbase? If so, that's fine, just continue as normal
|
||||
const gameIsPatched = await invoke('are_files_identical', {
|
||||
path1: (await getBackupRSAPath()) + '\\version.dll',
|
||||
path2: (await getGameRSAPath()) + '\\mhypbase.dll',
|
||||
})
|
||||
|
||||
// Tell user they won't be unpatched with manual mhypbase patch
|
||||
if (gameIsPatched) {
|
||||
console.log('You are already patched using mhypbase, so you will not be auto patched and unpatched!')
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy the patch to game files
|
||||
const replaced = await invoke('copy_file', {
|
||||
path: (await getBackupRSAPath()) + '\\version.dll',
|
||||
newPath: await getGameRSAPath(),
|
||||
})
|
||||
|
||||
if (!replaced) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function unpatchGame() {
|
||||
// Just delete patch since it's not replacing any existing file
|
||||
const deleted = await invoke('delete_file', {
|
||||
path: (await getGameRSAPath()) + '\\version.dll',
|
||||
})
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
export async function getGameRSAPath() {
|
||||
const gameData = await getGameFolder()
|
||||
|
||||
if (!gameData) {
|
||||
return null
|
||||
}
|
||||
|
||||
return gameData
|
||||
}
|
||||
|
||||
export async function getBackupRSAPath() {
|
||||
return (await dataDir()) + 'cultivation\\rsa'
|
||||
}
|
||||
|
||||
export async function downloadRSA(manager: DownloadHandler) {
|
||||
const rsaLink = 'https://github.com/34736384/RSAPatch/releases/download/v1.1.0/RSAPatch.dll'
|
||||
|
||||
// Should make sure rsa path exists
|
||||
await invoke('dir_create', {
|
||||
path: await getBackupRSAPath(),
|
||||
})
|
||||
|
||||
// Download the file
|
||||
manager.addDownload(rsaLink, (await getBackupRSAPath()) + '\\version.dll', () => {
|
||||
null
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user