In the Underworld the potion opens the Underworld portals (busy dialog mode 6, as the teleporters there do); g_campaign moved to game/addresses.h. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
252 lines
14 KiB
C++
252 lines
14 KiB
C++
#pragma once
|
|
// Every sacred.exe address we use, in one place (Sacred Gold 2.28, image base
|
|
// 0x400000, no ASLR). Hooks, patches and docs refer to these names; see
|
|
// docs/function-map.md and docs/widescreen.md for what each one does.
|
|
|
|
#include <cstdint>
|
|
|
|
#include "re/patch.h"
|
|
|
|
namespace game::addr {
|
|
|
|
// --- functions ---------------------------------------------------------------
|
|
constexpr uint32_t Log = 0x0066f1c0;
|
|
constexpr uint32_t CmdLineHasFlag = 0x00813140;
|
|
constexpr uint32_t GetMouseClientPos = 0x0066e500;
|
|
constexpr uint32_t MainWindowProc = 0x008122a0;
|
|
constexpr uint32_t DxDriver7_SetDisplayMode = 0x00645390;
|
|
constexpr uint32_t DxDriver7_Flip = 0x00645f70;
|
|
constexpr uint32_t DxDriver7_BeginScene = 0x00646f50;
|
|
constexpr uint32_t DxDriver7_LoadingScreen = 0x006469d0; // GDI progress screen, 1024x768
|
|
constexpr uint32_t UiManager_ThreadLoop = 0x00756940; // main menu render loop (own thread)
|
|
constexpr uint32_t Engine_RenderThreadLoop = 0x0060e3f0; // in-game render loop
|
|
constexpr uint32_t WorldView0_SetupView = 0x00624ae0;
|
|
constexpr uint32_t WorldView0_SetZoom = 0x00628210;
|
|
constexpr uint32_t WorldView0_Render = 0x006322b0;
|
|
constexpr uint32_t WorldToScreen = 0x00623c40;
|
|
|
|
// --- globals -----------------------------------------------------------------
|
|
constexpr uint32_t g_dxDriver = 0x00cda99c; // DxDriver7*
|
|
constexpr uint32_t g_appFlags = 0x0182cdb8; // uint32_t, 0x20000 = inactive
|
|
constexpr uint32_t g_campaign = 0x0182cdc8; // uint16_t, the running game's campaign: 0 Ancaria, 1 Underworld
|
|
constexpr uint32_t g_mouseCursor = 0x00cdbadc; // mouse cursor object*: x +4, y +8 (render pixels)
|
|
|
|
// --- read-only constants -----------------------------------------------------
|
|
constexpr uint32_t kScreenWidthF = 0x0088e6f4; // float 1024.0
|
|
constexpr uint32_t kScreenHeightF = 0x00890040; // float 768.0
|
|
|
|
// Instructions in the world view that read kScreenWidthF / kScreenHeightF as
|
|
// a memory operand (fmul/fcomp dword ptr [addr]). Redirecting their operand
|
|
// resizes the world view without touching the UI's uses of the same floats.
|
|
constexpr uint32_t kViewWidthUsers[] = {
|
|
0x0061e5c2, 0x00623c82, 0x00624af2, 0x00628f65, 0x00629294,
|
|
0x0062b43a, 0x0062dc32, 0x0062ea2b, 0x0062f30d, 0x00630656, 0x00630f3f,
|
|
};
|
|
constexpr uint32_t kViewHeightUsers[] = {
|
|
0x0061e5ec, 0x00623c9c, 0x00624b03, 0x00628f8f, 0x006292bc,
|
|
0x0062ea5e, 0x0062f345, 0x0063068a, 0x00630f77,
|
|
};
|
|
|
|
// More screen constants the world view reads (float, `f... dword ptr [addr]`).
|
|
constexpr uint32_t kScreenHalfWidthF = 0x0088e6fc; // float 512.0 (screen centre x)
|
|
constexpr uint32_t kScreenHalfHeightF = 0x0088e9f0; // float 384.0 (screen centre y)
|
|
constexpr uint32_t kGroundClipBottomF = 0x0089011c; // float 818.0 (768 + 50)
|
|
constexpr uint32_t kClipBottom888F = 0x00890124; // float 888.0 (768 + 120)
|
|
constexpr uint32_t kViewHalfWidthUsers[] = {
|
|
0x0062b485, 0x0062dc83, 0x0062e37c, 0x0062e960, 0x0062e989, 0x0062efd8, 0x0062eff2,
|
|
0x0063058a, 0x006305b5, 0x00630c08, 0x00630c22, 0x00632032, 0x0063205c,
|
|
};
|
|
constexpr uint32_t kViewHalfHeightUsers[] = {
|
|
0x006252fe, 0x00625341, 0x0062b4be, 0x0062dcbe, 0x0062e390, 0x0062e996, 0x0062e9a2, 0x0062f002,
|
|
0x0062f00e, 0x006305c2, 0x006305ce, 0x00630c2f, 0x00630c3b, 0x0063206a, 0x0063207a,
|
|
};
|
|
// Outside the world view object, but drawing into the world: the zoom
|
|
// transform around the screen centre in FUN_0041eaf0. (The 512/1024 in
|
|
// cGranny::renderComplex case 9 are an effect animation, not the screen.)
|
|
constexpr uint32_t kCreatureHalfWidthUsers[] = {0x0041ed2b, 0x0041ed35};
|
|
constexpr uint32_t kCreatureHalfHeightUsers[] = {0x0041ed41, 0x0041ed4b};
|
|
// Precomputed reciprocals of the screen size. 0x623a20 (screen -> view,
|
|
// inverse of 0x623c40) uses 1/1024 and 1/768. Other users of these constants
|
|
// are texel/time maths.
|
|
constexpr uint32_t kScreenInvWidthF = 0x0088e710; // float 1/1024
|
|
constexpr uint32_t kScreenInvHeightF = 0x0089005c; // float 1/768
|
|
constexpr uint32_t kViewInvWidthUsers[] = {0x00623a49};
|
|
constexpr uint32_t kViewInvHeightUsers[] = {0x00623a5f};
|
|
constexpr uint32_t kGroundClipBottomUsers[] = {0x0062b464};
|
|
constexpr uint32_t kClipBottom888Users[] = {0x0062dc62};
|
|
|
|
// Screen centre as integer immediates (`add reg, 200h` / `mov reg, 200h` and
|
|
// the matching 180h), in pairs x, y.
|
|
constexpr uint32_t kCenterImmediates[][2] = {
|
|
{0x0062b4c6, 0x0062b4e9}, {0x0062cc40, 0x0062cc50}, {0x0062dcc6, 0x0062dce1},
|
|
{0x0062e398, 0x0062e3a9}, {0x0062f967, 0x0062f96d}, {0x0062fe60, 0x0062fe7d},
|
|
{0x006315aa, 0x006315b0}, {0x00631aca, 0x00631ae7}, {0x00631c53, 0x00631c70},
|
|
// screen positions stored for later (list at +0xce14, up to 1024 entries)
|
|
{0x0041b184, 0x0041b1a1}, {0x0041b25c, 0x0041b27e}, {0x0041b3f6, 0x0041b3e4},
|
|
{0x0041b4d6, 0x0041b4c4},
|
|
// floating texts (damage numbers): world -> screen, and back (sub)
|
|
{0x0041e560, 0x0041e596}, {0x0041eb07, 0x0041eaf7},
|
|
};
|
|
// The same, negated (`add reg, -200h` / `lea reg, [...-200h]`), pairs x, y.
|
|
constexpr uint32_t kNegativeCenterImmediates[][2] = {
|
|
// aiming at the cursor (0x4fb620): cursor - centre + camera -> world
|
|
{0x004fb6ef, 0x004fb6f9},
|
|
// row renderer 0x62b000: screen origin (camera - centre) for the static
|
|
// objects it puts into the per-view draw lists
|
|
{0x0062babb, 0x0062bac1},
|
|
};
|
|
|
|
// Creatures are drawn only while their screen position lies within
|
|
// -200 .. 1024+200 x -200 .. 768+200 (`cmp reg, 4C8h` / `cmp eax, 3C8h`, in
|
|
// the two object passes 0x62e580 and 0x6300d0); outside it they only update.
|
|
constexpr uint32_t kCreatureClipRight[] = {0x0062f988, 0x006315cb}; // 0x4c8
|
|
constexpr uint32_t kCreatureClipBottom[] = {0x0062f9a7, 0x006315ea}; // 0x3c8
|
|
constexpr uint32_t kCreatureClipMargin = 200;
|
|
|
|
// cWorldView0 is allocated with `push 970F4h; call operator new` here.
|
|
constexpr uint32_t kWorldViewAllocSizePush = 0x006101aa;
|
|
constexpr uint32_t kWorldViewSize = 0x970f4;
|
|
|
|
// cWorldView0's tile draw list: entries of 0x98 bytes at +0x3ff2c, count at
|
|
// +0x80e3c, room for ~1750 entries and no bounds check (enough for 1024x768
|
|
// only). Instructions addressing it by displacement (+0x3ff2c + field):
|
|
constexpr uint32_t kTileListOffset = 0x3ff2c;
|
|
constexpr uint32_t kTileListEntrySize = 0x98;
|
|
constexpr uint32_t kTileListUsers[] = {
|
|
0x0062b6d9, 0x0062b6ec, 0x0062b709, 0x0062b730, 0x0062b74b, 0x0062b763, // writer, row renderer FUN_0062b000
|
|
0x0062b777, 0x0062b797, 0x0062b7b7, 0x0062b7d7,
|
|
0x0062df47, 0x0062e252, // readers (lea base pointer)
|
|
};
|
|
|
|
// cWorldView0's ground quad list: 0x90-byte entries at +0xb64 plus a parallel
|
|
// int array at +0x3e3c4, count at +0x3ff1c, capped at 0x6d5 (1749): beyond
|
|
// that the last quad is overwritten, so the ground stops drawing.
|
|
constexpr uint32_t kGroundQuadOffset = 0xb64;
|
|
constexpr uint32_t kGroundQuadEntrySize = 0x90;
|
|
constexpr uint32_t kGroundQuadIndexOffset = 0x3e3c4;
|
|
constexpr uint32_t kGroundQuadCap = 0x6d5;
|
|
constexpr uint32_t kGroundQuadCapCompare = 0x0062b603; // cmp eax, 6D5h
|
|
constexpr uint32_t kGroundQuadUsers[] = {0x0062b5e2, 0x0062d58e}; // lea [..+0B64h] / [..+0B7Ch]
|
|
constexpr uint32_t kGroundQuadIndexUsers[] = {0x0062b5f6, 0x0062d588}; // [..+3E3C4h]
|
|
|
|
// Ortho projection of the 3D models: x -267..267, y -200..200 world units
|
|
// span the 1024x768 screen (times zoom). Doubles, read with `fmul qword ptr`.
|
|
constexpr uint32_t kOrthoLeftD = 0x0088ee40; // double -267.0
|
|
constexpr uint32_t kOrthoRightD = 0x0088ee48; // double 267.0
|
|
constexpr uint32_t kOrthoBottomD = 0x0088ee50; // double -200.0
|
|
constexpr uint32_t kOrthoTopD = 0x0088ee58; // double 200.0
|
|
|
|
// World view instructions `fmul st, qword ptr [kOrtho*D]` (zoomed projection),
|
|
// four per site, in the order top, bottom, right, left.
|
|
constexpr uint32_t kOrthoZoomedUsers[][4] = {
|
|
{0x004b19c1, 0x004b19dc, 0x004b19ed, 0x004b19fe}, // FUN_004b1740, outside the view class
|
|
{0x00624fdb, 0x00624ff3, 0x00625001, 0x0062500f},
|
|
{0x00628355, 0x0062836d, 0x0062837b, 0x00628389},
|
|
{0x0062852f, 0x00628547, 0x00628555, 0x00628563},
|
|
{0x00628cd7, 0x00628cef, 0x00628d00, 0x00628d11},
|
|
};
|
|
|
|
// Unzoomed projection pushed as immediates: 4 consecutive `push imm32`
|
|
// (5 bytes each) starting here, in the order top, bottom, right, left.
|
|
constexpr uint32_t kOrthoPushSequences[] = {
|
|
0x0060c1f7, 0x0060da54, 0x00625039, 0x006283b3, 0x0062858d, 0x00628d3b,
|
|
};
|
|
|
|
// cGranny::render variants (3D creatures) project in screen pixels with an
|
|
// ortho camera of x -512..512, y -384..384: 4 consecutive `push imm32` in the
|
|
// order top (384), bottom (-384), right (512), left (-512).
|
|
// The last one is cUI_NetGranny (3D items in the HUD, e.g. the belt). It must
|
|
// be scaled too: game logic (0x44d9f0, camera target) converts world to screen
|
|
// with whatever projection the device has, which is usually the HUD's, and
|
|
// uses the (redirected) view width with it.
|
|
constexpr uint32_t kGrannyOrthoPushSequences[] = {0x0040ebc9, 0x0040f5cf, 0x0040feff, 0x006f1ecb};
|
|
|
|
// Screen-space labels (object names on Alt, floating texts) are packed without
|
|
// overlap into a screen-sized area (0x662300), constructed with push 768 /
|
|
// push 1024: the height push, the width push follows 5 bytes later.
|
|
constexpr uint32_t kLabelAreaPushes[] = {0x006272f8, 0x0060e940};
|
|
|
|
// Cinematic letterbox bars (UI frame 0x7587b0): quads from x = 0 to 1024.0f,
|
|
// stored as immediates.
|
|
constexpr uint32_t kLetterboxRightImmediates[] = {0x0075895d, 0x007589f0, 0x00758aa5, 0x00758b29};
|
|
|
|
// --- taskbar slots (docs/skill-slots.md) -----------------------------------------
|
|
// cUI_Taskbar2 keeps 5 weapon-set controls at +0x164 and 5 combat-art slot
|
|
// controls right after them at +0x178. For 7 slots each, both arrays move to
|
|
// the end of an enlarged object (allocated with `push 0D98h` here), 0x1c apart.
|
|
constexpr uint32_t kTaskbarAllocSizePush = 0x007593fa;
|
|
constexpr uint32_t kTaskbarSize = 0xd98;
|
|
constexpr uint32_t kTaskbarWeaponSlots = 0x164;
|
|
constexpr uint32_t kTaskbarSkillSlots = 0x178;
|
|
// Instructions addressing the arrays by displacement.
|
|
constexpr uint32_t kTaskbarWeaponSlotUsers[] = {
|
|
0x006df40f, 0x006df9db, 0x006dfd4e, 0x006dffea, 0x006e1289, 0x006e14b8, 0x006e14fe,
|
|
0x006e1544, 0x006e156a, 0x006e16b1, 0x006e1713, 0x006e1a49, 0x006e1bfb, 0x006e1e7c,
|
|
0x006e20d5, 0x006e258d, 0x006e280b, 0x006e5dee,
|
|
};
|
|
constexpr uint32_t kTaskbarSkillSlotUsers[] = { // incl. `add ebp, 178h`
|
|
0x006df438, 0x006df7bf, 0x006dfcee, 0x006e004a, 0x006e182a, 0x006e1831, 0x006e187a,
|
|
0x006e18b6, 0x006e1953, 0x006e19f9, 0x006e1aa0, 0x006e1ecf, 0x006e2081, 0x006e3263,
|
|
0x006e4496, 0x006e4cac, 0x006e5e27,
|
|
};
|
|
// Loops that walk one array and reach the other with a disp8 of -0x14 / +0x14.
|
|
constexpr re::BytePatch kTaskbarSlotDistanceDown[] = { // [reg-14h]
|
|
{0x006df7ca, 2, 0xec}, {0x006e44b0, 2, 0xec}, {0x006e44e5, 2, 0xec}, {0x006e4d89, 2, 0xec},
|
|
};
|
|
constexpr re::BytePatch kTaskbarSlotDistanceUp[] = { // [reg+14h]
|
|
{0x006dfa85, 2, 0x14}, {0x006e2598, 2, 0x14},
|
|
};
|
|
// Slot loop bounds and the slot count clamp (5). The belt loops (5 x 0xbc at
|
|
// +0x258 / +0x604) stay.
|
|
constexpr re::BytePatch kTaskbarSlotLoopBounds[] = { // cmp reg, 5
|
|
{0x006dfaaa, 2, 5}, {0x006dfd21, 2, 5}, {0x006dfd81, 2, 5}, {0x006e0043, 2, 5},
|
|
{0x006e0074, 2, 5}, {0x006e1a30, 2, 5}, {0x006e1a86, 2, 5}, {0x006e1eb1, 2, 5},
|
|
{0x006e1f04, 2, 5}, {0x006e20bc, 2, 5}, {0x006e2110, 2, 5}, {0x006e3199, 2, 5},
|
|
{0x006e39a6, 2, 5}, {0x006e49ba, 2, 5}, {0x006e4e49, 2, 5},
|
|
{0x006dffd2, 3, 5}, // SetSlotCount: cmp si, 5
|
|
{0x006df7c5, 1, 5}, {0x006e2593, 1, 5}, {0x006dffd8, 1, 5}, // mov reg, 5 (count / clamp)
|
|
};
|
|
// Combat-art slots without a selection: slot 0 (the right mouse button's art)
|
|
// is drawn as the selected one, every slot's ready art with the selected icon.
|
|
constexpr re::CodeReplacement kTaskbarSkillSelectionLook[] = {
|
|
// UpdateSlotHighlight, slot background: `cmp ebx, eax` (slot == selected)
|
|
// -> `test ebx, ebx` (slot == 0)
|
|
{0x006dfa77, {0x3b, 0xd8}, {0x85, 0xdb}, 2},
|
|
// RenderArtSlots, frame: `cmp edi, eax` -> `test edi, edi`
|
|
{0x006e3813, {0x3b, 0xf8}, {0x85, 0xff}, 2},
|
|
// RenderArtSlots: a ready art is drawn with its third ("selected") icon
|
|
// only in the selected slot: `jne` skipped
|
|
{0x006e3575, {0x75, 0x54}, {0x90, 0x90}, 2},
|
|
};
|
|
// Taskbar code passes mouse positions to the slot controls' hit tests as
|
|
// `and reg, 0FFFFh` (zero-extended u16): with 7 slots the leftmost weapon set
|
|
// lies at x < 0 of the layout and never matched. Sign-extended instead:
|
|
// `and edx, 0FFFFh` -> `movsx edx, dx`, `and eax, 0FFFFh` -> `cwde`.
|
|
constexpr re::CodeReplacement kTaskbarSignedHitTests[] = {
|
|
{0x006dfcfe, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006dfd07, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006dfd5e, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006dfd67, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e1a5c, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e1a67, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e2093, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e209e, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e20e7, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e20f2, {0x81, 0xe2, 0xff, 0xff, 0x00, 0x00}, {0x0f, 0xbf, 0xd2, 0x90, 0x90, 0x90}, 6},
|
|
{0x006e1a0c, {0x25, 0xff, 0xff, 0x00, 0x00}, {0x98, 0x90, 0x90, 0x90, 0x90}, 5},
|
|
{0x006e1a16, {0x25, 0xff, 0xff, 0x00, 0x00}, {0x98, 0x90, 0x90, 0x90, 0x90}, 5},
|
|
{0x006e1e8e, {0x25, 0xff, 0xff, 0x00, 0x00}, {0x98, 0x90, 0x90, 0x90, 0x90}, 5},
|
|
{0x006e1e98, {0x25, 0xff, 0xff, 0x00, 0x00}, {0x98, 0x90, 0x90, 0x90, 0x90}, 5},
|
|
{0x006e1ee1, {0x25, 0xff, 0xff, 0x00, 0x00}, {0x98, 0x90, 0x90, 0x90, 0x90}, 5},
|
|
{0x006e1eeb, {0x25, 0xff, 0xff, 0x00, 0x00}, {0x98, 0x90, 0x90, 0x90, 0x90}, 5},
|
|
};
|
|
// SetSlotCount: the decorative strip right of the centre starts at
|
|
// 0x23a + n * 0x40 (`add ecx, 23Ah`).
|
|
constexpr uint32_t kTaskbarRightStripStart = 0x006e01cc;
|
|
// cCreature::AssignArtToSlot (0x55fac0) rejects slots > 4 (`cmp eax, 4`).
|
|
constexpr re::BytePatch kAssignArtMaxSlot[] = {{0x0055fae3, 2, 4}};
|
|
// SlotCountForLevel (stdcall, level): (level, count) table at 0x95503c, max 5.
|
|
constexpr uint32_t SlotCountForLevel = 0x0057f750;
|
|
|
|
} // namespace game::addr
|