Automatically apply RSA patch

This commit is contained in:
Thoronium
2023-02-27 17:15:21 -07:00
committed by GitHub
parent 55fa0a8d3c
commit 62a97d86cb
23 changed files with 184 additions and 468 deletions

View File

@@ -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)`
)
}
}

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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
View 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
}