diff --git a/src/main/index.ts b/src/main/index.ts index 60be798..19ea8cf 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -159,6 +159,72 @@ function createWindow(): void { win.focus() }) + ipcMain.on( + 'patch-game-client', + async (event, args: { basePath: string; gameVersion: number }) => { + event.returnValue = undefined + + const gameDirPath = path.join( + args.basePath, + args.gameVersion == 1 ? 'highRes' : 'lowRes', + 'KnockoutCity' + ) + + const gameExePath = path.join(gameDirPath, 'KnockoutCity.exe') + const backupGameExePath = path.join(gameDirPath, 'KnockoutCity.exe.bak') + + if (!fse.existsSync(backupGameExePath)) { + console.log(`Backing up ${gameExePath} to ${backupGameExePath}`) + fse.copySync(gameExePath, backupGameExePath) + } + + let data = await fse.readFile(backupGameExePath).catch((error) => { + console.error('Failed to read Game File:', error) + throw Error('Failed to read Game File') + }) + + const patches: { + name: string + startAddress: number + endAddress: number + replacement: () => Buffer + }[] = [ + { + name: 'Signature Verification', + startAddress: 0x3cad481, + endAddress: 0x3cad485, + replacement: () => Buffer.from([0xb8, 0x01, 0x00, 0x00]) + }, + { + name: 'Auth Provider', + startAddress: 0x4f97230, + endAddress: 0x4f97233, + replacement: () => Buffer.from([0x78, 0x79, 0x7a]) + } + ] + + for (const patch of patches) { + console.log(`Patching ${patch.name}...`) + const startBuffer = data.subarray(0, patch.startAddress) + const endBuffer = data.subarray(patch.endAddress) + + console.log('data pre') + console.log(data.subarray(patch.startAddress, patch.endAddress)) + data = Buffer.concat([startBuffer, patch.replacement(), endBuffer]) + + console.log('data post') + console.log(data.subarray(patch.startAddress, patch.endAddress)) + } + + await fse.writeFile(gameExePath, data).catch((error) => { + console.error('Failed to write patched Game File:', error) + throw Error('Failed to write patched Game File') + }) + + event.sender.send('patched-game-client') + } + ) + ipcMain.on('set-RPCstate', async (event, arg) => { event.returnValue = 'ok' diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index bcbbe02..1e0c742 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -23,6 +23,8 @@ declare global { getGameState: () => 'installed' | 'deprecated' | 'notInstalled' getGameInstalls: () => string[] + patchGameClient: () => Promise + installGame: (props: { setGameState: (state: 'installed' | 'deprecated' | 'notInstalled' | 'installing') => void setInstallData: (data: number) => void diff --git a/src/preload/index.ts b/src/preload/index.ts index e8a8972..10ea583 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -111,6 +111,18 @@ window.addEventListener('DOMContentLoaded', () => { return result } + // @ts-ignore (define in dts) + window.patchGameClient = (): Promise => { + return new Promise((resolve) => { + ipcRenderer.once('patched-game-client', () => resolve()) + + ipcRenderer.sendSync('patch-game-client', { + basePath: localStorage.getItem('gameDirectory'), + gameVersion: localStorage.getItem('gameVersion') + }) + }) + } + // @ts-ignore (define in dts) window.getGameInstalls = (): string[] => { const result = ipcRenderer.sendSync('get-game-installs', { diff --git a/src/renderer/src/components/LaunchSection.tsx b/src/renderer/src/components/LaunchSection.tsx index 28dd16e..6c511ea 100644 --- a/src/renderer/src/components/LaunchSection.tsx +++ b/src/renderer/src/components/LaunchSection.tsx @@ -50,6 +50,9 @@ function LaunchSection(): JSX.Element { setCurrServerName(localStorage.getItem('currServerName') || 'localhost') setCurrServerType(localStorage.getItem('currServerType') || 'private') + setPopUpState('patchGameClient') + await window.patchGameClient() + if (localStorage.getItem('currServerType') === 'public') { setPopUpState('authenticating') if ( diff --git a/src/renderer/src/components/popUp/index.tsx b/src/renderer/src/components/popUp/index.tsx index 47bdfb4..581c184 100644 --- a/src/renderer/src/components/popUp/index.tsx +++ b/src/renderer/src/components/popUp/index.tsx @@ -7,6 +7,7 @@ import Authenticating from './views/Authenticating' import ConfirmLogout from './views/ConfirmLogout' import SelectUninstall from './views/SelectUninstall' import AccountSettings from './views/AccountSettings' +import PatchGameClient from './views/PatchGameClient' // States import { useUIState } from '@renderer/states/uiState' @@ -271,6 +272,8 @@ function PopUp(): JSX.Element { return case 'accountSettings': return + case 'patchGameClient': + return default: return <> } diff --git a/src/renderer/src/components/popUp/views/PatchGameClient.tsx b/src/renderer/src/components/popUp/views/PatchGameClient.tsx new file mode 100644 index 0000000..353bbb2 --- /dev/null +++ b/src/renderer/src/components/popUp/views/PatchGameClient.tsx @@ -0,0 +1,39 @@ +import { Backdrop, Box, Typography, LinearProgress } from '@mui/material' + +function PatchGameClient(): JSX.Element { + return ( + + + + Patch Game Client + +

Patching the Game Client...

+ +
+
+ ) +} + +export default PatchGameClient