@@ -0,0 +1,229 @@
|
||||
import path from 'path';
|
||||
|
||||
import { screen } from 'electron';
|
||||
import fs from 'fs-extra';
|
||||
import Logger from 'electron-log/main';
|
||||
|
||||
import Preferences from '~main/modules/preferences';
|
||||
import { ConfigWtfSchema, type PreferencesSchema } from '~common/schemas';
|
||||
import { isNotUndef } from '~common/utils';
|
||||
import { fetchFile } from '~main/modules/updater';
|
||||
|
||||
const Servers = {
|
||||
live: {
|
||||
realmList: 'octowow.st',
|
||||
patchList: 'octowow.st',
|
||||
realmName: 'OctoWoW'
|
||||
},
|
||||
ptr: {
|
||||
realmList: 'octowow.st',
|
||||
patchList: 'octowow.st',
|
||||
realmName: 'OctoWoW PTR'
|
||||
}
|
||||
} as const;
|
||||
|
||||
type Tweak = { key: keyof PreferencesSchema['config']; default?: unknown; forced?: boolean } & (
|
||||
| {
|
||||
type: 'bytes';
|
||||
tweaks: [number, number[]][];
|
||||
}
|
||||
| {
|
||||
type: 'int8' | 'uint16' | 'float';
|
||||
offset: number;
|
||||
value?: number;
|
||||
}
|
||||
);
|
||||
|
||||
export const patchExecutable = async () => {
|
||||
Logger.log('Patching WoW.exe...');
|
||||
|
||||
const { clientDir, config } = Preferences.data;
|
||||
if (!clientDir) return;
|
||||
const exePath = path.join(clientDir, 'WoW.exe');
|
||||
|
||||
try {
|
||||
Logger.log('Fetching clean WoW.exe...');
|
||||
const file = await fetchFile('WoW.exe');
|
||||
const buffer = Buffer.from(file);
|
||||
|
||||
const Tweaks = [
|
||||
{
|
||||
key: 'largeAddress',
|
||||
type: 'uint16',
|
||||
offset: 0x126,
|
||||
value: buffer.readUint16LE(0x126) | 0x20,
|
||||
default: false
|
||||
},
|
||||
{ key: 'farClip', type: 'float', offset: 0x40fed8 },
|
||||
{
|
||||
key: 'fieldOfView',
|
||||
type: 'float',
|
||||
offset: 0x4089b4,
|
||||
value: (config.fieldOfView ?? 1) * (Math.PI / 180),
|
||||
default: 90
|
||||
},
|
||||
{ key: 'frillDistance', type: 'float', offset: 0x467958 },
|
||||
{
|
||||
key: 'soundInBackground',
|
||||
type: 'int8',
|
||||
offset: 0x3a4869,
|
||||
value: config.soundInBackground ? 0x27 : 0x14,
|
||||
default: false
|
||||
},
|
||||
{
|
||||
key: 'alwaysAutoLoot',
|
||||
type: 'bytes',
|
||||
tweaks: [
|
||||
[0x0c1ecf, [0x75]],
|
||||
[0x0c2b25, [0x75]]
|
||||
]
|
||||
},
|
||||
{ key: 'nameplateRange', type: 'float', offset: 0x40c448 },
|
||||
{ key: 'cameraDistance', type: 'float', offset: 0x4089a4 },
|
||||
{
|
||||
key: 'crossFactionResurrect' as never,
|
||||
type: 'bytes',
|
||||
default: true,
|
||||
tweaks: [
|
||||
[0x006e5fb8, [0x006e5fb9]],
|
||||
[0x006e62a8, [0x006e62a9]]
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'skillUiGateHijack' as never,
|
||||
type: 'bytes',
|
||||
default: true,
|
||||
forced: true,
|
||||
tweaks: [
|
||||
[
|
||||
0x002ddf90,
|
||||
[
|
||||
0x55, 0x8b, 0xec, 0x83, 0xec, 0x08, 0x53, 0x56,
|
||||
0x57, 0x8b, 0x3d, 0x60, 0xab, 0xce, 0x00, 0x83,
|
||||
0xff, 0xff, 0x89, 0x55, 0xfc, 0x89, 0x4d, 0xf8,
|
||||
0x74, 0x79, 0x8b, 0x75, 0x08, 0x8b, 0x15, 0x58,
|
||||
0xab, 0xce, 0x00, 0x8b, 0xc7, 0x23, 0xc6, 0x8d,
|
||||
0x04, 0x40, 0x8b, 0x4c, 0x82, 0x08, 0xf6, 0xc1,
|
||||
0x01, 0x8d, 0x44, 0x82, 0x04, 0x75, 0x04, 0x85,
|
||||
0xc9, 0x75, 0x05, 0x33, 0xc9, 0x8d, 0x49, 0x00,
|
||||
0xf6, 0xc1, 0x01, 0x75, 0x4e, 0x85, 0xc9, 0x74,
|
||||
0x4a, 0x39, 0x31, 0x74, 0x13, 0x8b, 0xc7, 0x23,
|
||||
0xc6, 0x8d, 0x04, 0x40, 0x8d, 0x04, 0x82, 0x8b,
|
||||
0x00, 0x03, 0xc1, 0x8b, 0x48, 0x04, 0xeb, 0xe0,
|
||||
0x8b, 0x59, 0x1c, 0x8b, 0x71, 0x18, 0x33, 0xff,
|
||||
0x85, 0xdb, 0x7e, 0x27, 0x8d, 0x64, 0x24, 0x00,
|
||||
0x8b, 0x4e, 0x0c, 0x8b, 0x56, 0x08, 0x6a, 0x00,
|
||||
0x6a, 0x00, 0x51, 0x8b, 0x4d, 0xf8, 0x52, 0x8b,
|
||||
0x55, 0xfc, 0xe8, 0xb9, 0xfd, 0xff, 0xff, 0x84,
|
||||
0xc0, 0x75, 0x13, 0x47, 0x83, 0xc6, 0x20, 0x3b,
|
||||
0xfb, 0x7c, 0xdd, 0x5f, 0x5e, 0x33, 0xc0, 0x5b,
|
||||
0x8b, 0xe5, 0x5d, 0xc2, 0x04, 0x00, 0x5f, 0x8b,
|
||||
0xc6, 0x5e, 0x5b, 0x8b, 0xe5, 0x5d, 0xc2, 0x04,
|
||||
0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
] satisfies Tweak[];
|
||||
|
||||
// Apply patches
|
||||
Tweaks.forEach(t => {
|
||||
const val =
|
||||
config[t.key] ?? t.default ?? ConfigWtfSchema.parse({})[t.key];
|
||||
|
||||
Logger.log(`Applying "${t.key}" patch with value: ${val}`);
|
||||
if (t.type === 'float') {
|
||||
buffer.writeFloatLE(t.value ?? (val as never), t.offset);
|
||||
} else if (t.type === 'int8') {
|
||||
buffer.writeInt8(t.value ?? (val as never), t.offset);
|
||||
} else if (t.type === 'uint16') {
|
||||
if (!t.forced && !val) return;
|
||||
buffer.writeUInt16LE(t.value ?? (val as never), t.offset);
|
||||
} else if (t.type === 'bytes') {
|
||||
if (!t.forced && !val) return;
|
||||
t.tweaks.forEach(([offset, bytes]) =>
|
||||
Buffer.from(bytes).copy(buffer, offset)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await fs.writeFile(exePath, buffer);
|
||||
Logger.log('WoW.exe successfully patched');
|
||||
} catch (e) {
|
||||
Logger.error('Failed to patch WoW.exe', e);
|
||||
}
|
||||
};
|
||||
|
||||
export const patchConfig = async () => {
|
||||
const { clientDir, server, config } = Preferences.data;
|
||||
if (!clientDir) return;
|
||||
|
||||
const configPath = path.join(clientDir, 'WTF', 'Config.wtf');
|
||||
await fs.ensureDir(path.dirname(configPath));
|
||||
const raw = (await fs.pathExists(configPath))
|
||||
? await fs.readFile(configPath, { encoding: 'utf-8' })
|
||||
: '';
|
||||
if (raw) await fs.remove(configPath);
|
||||
|
||||
const configWtf = Object.fromEntries(
|
||||
raw
|
||||
.split('\n')
|
||||
.map(l => {
|
||||
const [_, k, v] = l.match(/SET (\w+) "(.+)"/) ?? [];
|
||||
return !k || !v ? undefined : [k, v];
|
||||
})
|
||||
.filter(isNotUndef)
|
||||
);
|
||||
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const { width, height } = primaryDisplay.bounds;
|
||||
|
||||
const parsed = {
|
||||
scriptMemory: 512000,
|
||||
gxResolution: `${width}x${height}`,
|
||||
gxColorBits: primaryDisplay.colorDepth,
|
||||
gxDepthBits: primaryDisplay.colorDepth,
|
||||
gxRefresh: 60,
|
||||
gxMultisample: 8,
|
||||
gxMultisampleQuality: 0,
|
||||
gxTripleBuffer: 1,
|
||||
anisotropic: 16,
|
||||
frillDensity: 48,
|
||||
fullAlpha: 1,
|
||||
SmallCull: 0.01,
|
||||
DistCull: 888.8,
|
||||
shadowLevel: 0,
|
||||
trilinear: 1,
|
||||
specular: 1,
|
||||
pixelShaders: 1,
|
||||
M2UsePixelShaders: 1,
|
||||
particleDensity: 1,
|
||||
unitDrawDist: 300,
|
||||
weatherDensity: 3,
|
||||
movieSubtitle: 1,
|
||||
minimapZoom: 0,
|
||||
minimapInsideZoom: 0,
|
||||
SoundZoneMusicNoDelay: 1,
|
||||
patchList: configWtf['patchList'] ?? Servers[server].patchList,
|
||||
realmName: configWtf['realmName'] ?? Servers[server].realmName,
|
||||
gxWindow: configWtf['gxWindow'] ?? 1,
|
||||
gxMaximize: configWtf['gxMaximize'] ?? 1,
|
||||
gxCursor: configWtf['gxCursor'] ?? 1,
|
||||
checkAddonVersion: configWtf['checkAddonVersion'] ?? 0,
|
||||
...configWtf,
|
||||
CameraDistanceMax: config.cameraDistance,
|
||||
farClip: config.farClip,
|
||||
realmList: Servers[server].realmList,
|
||||
hwDetect: 0,
|
||||
M2UseShaders: 1
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
Object.entries(parsed)
|
||||
.filter(v => v[1] !== undefined && v[1] !== null)
|
||||
.map(l => `SET ${l[0]} "${l[1]}"`)
|
||||
.join('\n')
|
||||
);
|
||||
Logger.log('Config.wtf successfully patched');
|
||||
};
|
||||
Reference in New Issue
Block a user