From bd05f4ddce5a75e3eedf0e0e4061caf1feb16050 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 18 Dec 2020 19:41:36 -0500 Subject: [PATCH 1/3] WIP support for GamePass/Xbox/WindowsStore edition of Among Us --- src/main/baked-offsets.ts | 107 ++++++++++++++++++++++++++++++++++++++ src/main/hook.ts | 51 ++++++++++++++---- src/main/index.ts | 14 ++--- 3 files changed, 155 insertions(+), 17 deletions(-) create mode 100644 src/main/baked-offsets.ts diff --git a/src/main/baked-offsets.ts b/src/main/baked-offsets.ts new file mode 100644 index 00000000..4b9d0287 --- /dev/null +++ b/src/main/baked-offsets.ts @@ -0,0 +1,107 @@ +export const offset_2020_12_5 = ` +meetingHud: [0x1BE0CB4, 0x5c, 0] +meetingHudCachePtr: [0x8] +meetingHudState: [0x84] +gameState: [0x1BE1074, 0x5C, 0, 0x64] + +allPlayersPtr: [0x1BE0BB8, 0x5c, 0, 0x24] +allPlayers: [0x08] +playerCount: [0x0c] +playerAddrPtr: 0x10 +exiledPlayerId: [0xff, 0x1BE0CB4, 0x5c, 0, 0x94, 0x08] + +gameCode: [0x1B5AB00, 0x5c, 0, 0x20, 0x28] + +player: + struct: + - type: SKIP + skip: 8 + name: unused + - type: UINT + name: id + - type: UINT + name: name + - type: UINT + name: color + - type: UINT + name: hat + - type: UINT + name: pet + - type: UINT + name: skin + - type: UINT + name: disconnected + - type: UINT + name: taskPtr + - type: BYTE + name: impostor + - type: BYTE + name: dead + - type: SKIP + skip: 2 + name: unused + - type: UINT + name: objectPtr + isLocal: [0x54] + localX: [0x60, 0x50] + localY: [0x60, 0x54] + remoteX: [0x60, 0x3C] + remoteY: [0x60, 0x40] + bufferLength: 56 + offsets: [0, 0] + inVent: [0x31] +` +export const offsets_2020_12_9 = ` +meetingHud: [0x1C573A4, 0x5c, 0] +meetingHudCachePtr: [0x8] +meetingHudState: [0x84] +gameState: [0x1C57F54, 0x5C, 0, 0x64] + +allPlayersPtr: [0x1C57BE8, 0x5c, 0, 0x24] +allPlayers: [0x08] +playerCount: [0x0c] +playerAddrPtr: 0x10 +exiledPlayerId: [0xff, 0x1C573A4, 0x5c, 0, 0x94, 0x08] + +gameCode: [0x1AF20FC, 0x5c, 0, 0x20, 0x28] + +player: + struct: + - type: SKIP + skip: 8 + name: unused + - type: UINT + name: id + - type: UINT + name: name + - type: UINT + name: color + - type: UINT + name: hat + - type: UINT + name: pet + - type: UINT + name: skin + - type: UINT + name: disconnected + - type: UINT + name: taskPtr + - type: BYTE + name: impostor + - type: BYTE + name: dead + - type: SKIP + skip: 2 + name: unused + - type: UINT + name: objectPtr + isLocal: [0x54] + localX: [0x60, 0x50] + localY: [0x60, 0x54] + remoteX: [0x60, 0x3C] + remoteY: [0x60, 0x40] + bufferLength: 56 + offsets: [0, 0] + inVent: [0x31] + +` \ No newline at end of file diff --git a/src/main/hook.ts b/src/main/hook.ts index 7c40ba13..2d532016 100644 --- a/src/main/hook.ts +++ b/src/main/hook.ts @@ -14,20 +14,22 @@ import { createCheckers } from 'ts-interface-checker'; import TI from './hook-ti'; import { existsSync, readFileSync } from 'fs'; import { IOffsets } from './IOffsets'; +import { offsets_2020_12_9 } from './baked-offsets'; const { IOffsets } = createCheckers(TI); interface IOHookEvent { - type: string - keychar?: number - keycode?: number - rawcode?: number - button?: number - clicks?: number - x?: number - y?: number + type: string + keychar?: number + keycode?: number + rawcode?: number + button?: number + clicks?: number + x?: number + y?: number } const store = new Store(); +let edition: 'steam' | 'windowsstore' = 'steam'; async function loadOffsets(event: Electron.IpcMainEvent): Promise { @@ -43,11 +45,37 @@ async function loadOffsets(event: Electron.IpcMainEvent): Promise { + if(edition === 'windowsstore') { + dialog.showErrorBox('Error', 'CrewLink thinks you are using the Windows Store / XBox edition of Among Us. You must launch Among Us manually.'); + } // Get steam path from registry const steamPath = enumerateValues(HKEY.HKEY_LOCAL_MACHINE, 'SOFTWARE\\WOW6432Node\\Valve\\Steam') diff --git a/src/main/index.ts b/src/main/index.ts index b271c0f6..6313cc44 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -18,16 +18,16 @@ function createMainWindow() { const mainWindowState = windowStateKeeper({}); const window = new BrowserWindow({ - width: 250, - height: 350, - maxWidth: 250, - minWidth: 250, - maxHeight: 350, - minHeight: 350, + width: 1000, + height: 1000, + // maxWidth: 1000, + // minWidth: 1000, + // maxHeight: 1000, + // minHeight: 1000, x: mainWindowState.x, y: mainWindowState.y, - resizable: false, + // resizable: false, frame: false, fullscreenable: false, maximizable: false, From d0f2a497c0aa94fb4d4e15e7b08a700d4f1edab7 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 20 Dec 2020 15:41:53 -0500 Subject: [PATCH 2/3] changes for 64bit: new offsets, read 64bit ints instead of 32 --- src/main/GameReader.ts | 32 +++++++++++-- src/main/IOffsets.d.ts | 1 + src/main/baked-offsets.ts | 97 +++++++++------------------------------ src/main/hook-ti.ts | 1 + src/main/hook.ts | 8 ++-- 5 files changed, 55 insertions(+), 84 deletions(-) diff --git a/src/main/GameReader.ts b/src/main/GameReader.ts index c9e30145..ec6f3e07 100644 --- a/src/main/GameReader.ts +++ b/src/main/GameReader.ts @@ -4,6 +4,19 @@ import patcher from '../patcher'; import { GameState, AmongUsState, Player } from '../common/AmongUsState'; import { IOffsets } from './IOffsets'; +const extraStructronTypes = { + __proto__: null, + // HACK assuming the value in a 64bit uint is never too large + 'UINT64': { + read(buffer: Buffer, offset: number) { + return Number(buffer.readBigUInt64LE(offset)); + }, + write(value: number, buffer: Buffer, offset: number) { + buffer.writeBigUInt64LE(BigInt(value), offset); + }, + SIZE: 8 + } +}; interface ValueType { read(buffer: BufferSource, offset: number): T; @@ -87,9 +100,19 @@ export default class GameReader { break; } + // memory address of allPlayers struct + /* + allplayers + +0x08: *vec + +0x10 - pointer to pointer to pointer to firstplayer + +0x0c: playerCount + */ const allPlayersPtr = this.readMemory('ptr', this.gameAssembly.modBaseAddr, this.offsets.allPlayersPtr) & 0xffffffff; + // memory address of allPlayers array, vector, whatever it is, pulled from allPlayers struct const allPlayers = this.readMemory('ptr', allPlayersPtr, this.offsets.allPlayers); + // number of players, pulled from allPlayers struct const playerCount = this.readMemory('int' as const, allPlayersPtr, this.offsets.playerCount); + // let playerAddrPtr = allPlayers + this.offsets.playerAddrPtr; const players = []; @@ -178,7 +201,8 @@ export default class GameReader { if (member.type === 'SKIP' && member.skip) { this.PlayerStruct = this.PlayerStruct.addMember(Struct.TYPES.SKIP(member.skip), member.name); } else { - this.PlayerStruct = this.PlayerStruct.addMember(Struct.TYPES[member.type] as ValueType, member.name); + const type = (extraStructronTypes[member.type as unknown as keyof typeof extraStructronTypes] ?? Struct.TYPES[member.type]) as ValueType; + this.PlayerStruct = this.PlayerStruct.addMember(type, member.name); } } @@ -199,7 +223,7 @@ export default class GameReader { if (!this.amongUs) throw 'Among Us not open? Weird error'; address = address & 0xffffffff; for (let i = 0; i < offsets.length - 1; i++) { - address = readMemoryRaw(this.amongUs.handle, address + offsets[i], 'uint32'); + address = readMemoryRaw(this.amongUs.handle, address + offsets[i], this.offsets.is64Bit ? 'uint64' : 'uint32'); if (address == 0) break; } @@ -208,8 +232,8 @@ export default class GameReader { } readString(address: number): string { if (address === 0 || !this.amongUs) return ''; - const length = readMemoryRaw(this.amongUs.handle, address + 0x8, 'int'); - const buffer = readBuffer(this.amongUs.handle, address + 0xC, length << 1); + const length = readMemoryRaw(this.amongUs.handle, address + (this.offsets.is64Bit ? 0x10 : 0x8), 'int'); + const buffer = readBuffer(this.amongUs.handle, address + (this.offsets.is64Bit ? 0x14 : 0xC), length << 1); return buffer.toString('utf8').replace(/\0/g, ''); } diff --git a/src/main/IOffsets.d.ts b/src/main/IOffsets.d.ts index 9d730e22..7ebd44ec 100644 --- a/src/main/IOffsets.d.ts +++ b/src/main/IOffsets.d.ts @@ -1,5 +1,6 @@ export interface IOffsets { + is64Bit?: boolean; meetingHud: number[]; meetingHudCachePtr: number[]; meetingHudState: number[]; diff --git a/src/main/baked-offsets.ts b/src/main/baked-offsets.ts index 4b9d0287..b5695469 100644 --- a/src/main/baked-offsets.ts +++ b/src/main/baked-offsets.ts @@ -1,25 +1,25 @@ -export const offset_2020_12_5 = ` -meetingHud: [0x1BE0CB4, 0x5c, 0] +export const bundledOffsets: Record = { + // Windows Store UWP 2020.12.4.0 + '2020.12.4.0': ` +is64Bit: true +meetingHud: [0x21D03E0, 0xB8, 0] meetingHudCachePtr: [0x8] -meetingHudState: [0x84] -gameState: [0x1BE1074, 0x5C, 0, 0x64] - -allPlayersPtr: [0x1BE0BB8, 0x5c, 0, 0x24] +meetingHudState: [0xC0] +gameState: [0x21D0EA0, 0xB8, 0, 0xAC] +allPlayersPtr: [0x1BE0BB8, 0xB8, 0, 0x30] allPlayers: [0x08] playerCount: [0x0c] playerAddrPtr: 0x10 -exiledPlayerId: [0xff, 0x1BE0CB4, 0x5c, 0, 0x94, 0x08] - -gameCode: [0x1B5AB00, 0x5c, 0, 0x20, 0x28] - +exiledPlayerId: [0xff, 0x21D03E0, 0xB8, 0, 0xE0, 0x10] +gameCode: [0x1D50138, 0xB8, 0, 0x40, 0x48] player: struct: - type: SKIP - skip: 8 + skip: 10 name: unused - type: UINT name: id - - type: UINT + - type: UINT64 name: name - type: UINT name: color @@ -31,7 +31,7 @@ player: name: skin - type: UINT name: disconnected - - type: UINT + - type: UINT64 name: taskPtr - type: BYTE name: impostor @@ -40,68 +40,15 @@ player: - type: SKIP skip: 2 name: unused - - type: UINT + - type: UINT64 name: objectPtr - isLocal: [0x54] - localX: [0x60, 0x50] - localY: [0x60, 0x54] - remoteX: [0x60, 0x3C] - remoteY: [0x60, 0x40] - bufferLength: 56 + isLocal: [0x78] + localX: [0x90, 0x6C] + localY: [0x90, 0x70] + remoteX: [0x90, 0x58] + remoteY: [0x90, 0x5C] + bufferLength: 64 offsets: [0, 0] - inVent: [0x31] + inVent: [0x3D] ` -export const offsets_2020_12_9 = ` -meetingHud: [0x1C573A4, 0x5c, 0] -meetingHudCachePtr: [0x8] -meetingHudState: [0x84] -gameState: [0x1C57F54, 0x5C, 0, 0x64] - -allPlayersPtr: [0x1C57BE8, 0x5c, 0, 0x24] -allPlayers: [0x08] -playerCount: [0x0c] -playerAddrPtr: 0x10 -exiledPlayerId: [0xff, 0x1C573A4, 0x5c, 0, 0x94, 0x08] - -gameCode: [0x1AF20FC, 0x5c, 0, 0x20, 0x28] - -player: - struct: - - type: SKIP - skip: 8 - name: unused - - type: UINT - name: id - - type: UINT - name: name - - type: UINT - name: color - - type: UINT - name: hat - - type: UINT - name: pet - - type: UINT - name: skin - - type: UINT - name: disconnected - - type: UINT - name: taskPtr - - type: BYTE - name: impostor - - type: BYTE - name: dead - - type: SKIP - skip: 2 - name: unused - - type: UINT - name: objectPtr - isLocal: [0x54] - localX: [0x60, 0x50] - localY: [0x60, 0x54] - remoteX: [0x60, 0x3C] - remoteY: [0x60, 0x40] - bufferLength: 56 - offsets: [0, 0] - inVent: [0x31] - -` \ No newline at end of file +}; \ No newline at end of file diff --git a/src/main/hook-ti.ts b/src/main/hook-ti.ts index fbd10bc7..f8412ed1 100644 --- a/src/main/hook-ti.ts +++ b/src/main/hook-ti.ts @@ -5,6 +5,7 @@ import * as t from 'ts-interface-checker'; // tslint:disable:object-literal-key-quotes export const IOffsets = t.iface([], { + 'is64Bit': t.opt('boolean'), 'meetingHud': t.array('number'), 'meetingHudCachePtr': t.array('number'), 'meetingHudState': t.array('number'), diff --git a/src/main/hook.ts b/src/main/hook.ts index 2d532016..04b071aa 100644 --- a/src/main/hook.ts +++ b/src/main/hook.ts @@ -14,7 +14,7 @@ import { createCheckers } from 'ts-interface-checker'; import TI from './hook-ti'; import { existsSync, readFileSync } from 'fs'; import { IOffsets } from './IOffsets'; -import { offsets_2020_12_9 } from './baked-offsets'; +import { bundledOffsets } from './baked-offsets'; const { IOffsets } = createCheckers(TI); interface IOHookEvent { @@ -71,14 +71,12 @@ async function loadOffsets(event: Electron.IpcMainEvent): Promise Date: Sun, 20 Dec 2020 15:55:11 -0500 Subject: [PATCH 3/3] revert unintentional diffs and add comments where I think its still broken --- src/main/GameReader.ts | 12 ++---------- src/main/hook.ts | 17 ++++++++--------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/main/GameReader.ts b/src/main/GameReader.ts index ec6f3e07..918af7a2 100644 --- a/src/main/GameReader.ts +++ b/src/main/GameReader.ts @@ -100,19 +100,11 @@ export default class GameReader { break; } - // memory address of allPlayers struct - /* - allplayers - +0x08: *vec - +0x10 - pointer to pointer to pointer to firstplayer - +0x0c: playerCount - */ + // TODO pretty sure this does not read 64bit ptrs correctly const allPlayersPtr = this.readMemory('ptr', this.gameAssembly.modBaseAddr, this.offsets.allPlayersPtr) & 0xffffffff; - // memory address of allPlayers array, vector, whatever it is, pulled from allPlayers struct + // TODO pretty sure this does not read 64bit ptrs correctly const allPlayers = this.readMemory('ptr', allPlayersPtr, this.offsets.allPlayers); - // number of players, pulled from allPlayers struct const playerCount = this.readMemory('int' as const, allPlayersPtr, this.offsets.playerCount); - // let playerAddrPtr = allPlayers + this.offsets.playerAddrPtr; const players = []; diff --git a/src/main/hook.ts b/src/main/hook.ts index 04b071aa..4ad4ab4c 100644 --- a/src/main/hook.ts +++ b/src/main/hook.ts @@ -18,14 +18,14 @@ import { bundledOffsets } from './baked-offsets'; const { IOffsets } = createCheckers(TI); interface IOHookEvent { - type: string - keychar?: number - keycode?: number - rawcode?: number - button?: number - clicks?: number - x?: number - y?: number + type: string + keychar?: number + keycode?: number + rawcode?: number + button?: number + clicks?: number + x?: number + y?: number } const store = new Store(); @@ -71,7 +71,6 @@ async function loadOffsets(event: Electron.IpcMainEvent): Promise