Taskbar: 7 weapon sets and 7 combat-art slots
cUI_Taskbar2's slot arrays move behind the (enlarged) object with room for 7 each; all instructions addressing them, the relative accesses between the two arrays, the slot loops and the count clamp are patched from tables in game/addresses.h (belt loops untouched). Slots 6 / 7 unlock at levels 45 / 65; AssignArtToSlot accepts slots up to 6. Slot keys call the taskbar's selection directly (honouring COMBINE_SLOTS). Shared code: re::PatchAll / PatchInstructionByte for patch tables, game/engine/objects.h for object, RTTI and kernel calls, slot-hook templates in skillslots.cpp. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
998e315d98
commit
dbc632750b
12 files changed
+363
-144
No files matched your search
@@ -39,6 +39,7 @@ add_library(sacred_re SHARED
|
|||||||
src/mods/input.cpp
|
src/mods/input.cpp
|
||||||
src/mods/skillslots.cpp
|
src/mods/skillslots.cpp
|
||||||
src/mods/inventory_save.cpp
|
src/mods/inventory_save.cpp
|
||||||
|
src/mods/skillbar.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(sacred_re PRIVATE src)
|
target_include_directories(sacred_re PRIVATE src)
|
||||||
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
target_compile_options(sacred_re PRIVATE ${SACRED_RE_CXX_FLAGS})
|
||||||
|
|||||||
+4
-2
@@ -1,4 +1,4 @@
|
|||||||
# 7 weapon sets and 7 combat-art slots (planned)
|
# 7 weapon sets and 7 combat-art slots
|
||||||
|
|
||||||
Goal (user request 2026-10-14): 7 weapon sets and 7 combat-art slots instead
|
Goal (user request 2026-10-14): 7 weapon sets and 7 combat-art slots instead
|
||||||
of 5 each; skills on 1-7, weapon sets on F1-F7. Inventories are saved as JSON
|
of 5 each; skills on 1-7, weapon sets on F1-F7. Inventories are saved as JSON
|
||||||
@@ -6,7 +6,9 @@ in place of the binary block; vanilla saves are migrated on load. Saves do not
|
|||||||
need to stay loadable by the original game. Status: inventory data for
|
need to stay loadable by the original game. Status: inventory data for
|
||||||
slots 6-7 (`src/mods/skillslots.cpp`) and the JSON save
|
slots 6-7 (`src/mods/skillslots.cpp`) and the JSON save
|
||||||
(`src/mods/inventory_save.cpp`) done and tested (vanilla save migrated,
|
(`src/mods/inventory_save.cpp`) done and tested (vanilla save migrated,
|
||||||
JSON save reloads unchanged); taskbar, unlock levels and equip test open.
|
JSON save reloads unchanged); taskbar with 7 + 7 slots (`src/mods/skillbar.cpp`), unlock levels
|
||||||
|
45 / 65 and keys 1-7 / F1-F7 done; tested in-game 2026-10-15 (drag & drop, casting,
|
||||||
|
equipping set 6 changes the stats, save / reload).
|
||||||
|
|
||||||
## Where the slots live
|
## Where the slots live
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "re/patch.h"
|
||||||
|
|
||||||
namespace game::addr {
|
namespace game::addr {
|
||||||
|
|
||||||
// --- functions ---------------------------------------------------------------
|
// --- functions ---------------------------------------------------------------
|
||||||
@@ -167,4 +169,45 @@ constexpr uint32_t kLabelAreaPushes[] = {0x006272f8, 0x0060e940};
|
|||||||
// stored as immediates.
|
// stored as immediates.
|
||||||
constexpr uint32_t kLetterboxRightImmediates[] = {0x0075895d, 0x007589f0, 0x00758aa5, 0x00758b29};
|
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)
|
||||||
|
};
|
||||||
|
// 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
|
} // namespace game::addr
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
// Game objects, RTTI casts and the event kernel: calls shared by the mods.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
// MSVC RTTI type descriptors (for DynamicCast).
|
||||||
|
constexpr uint32_t kTypeObject = 0x008e9648;
|
||||||
|
constexpr uint32_t kTypeCreature = 0x008e9660;
|
||||||
|
constexpr uint32_t kTypeControl = 0x009de168;
|
||||||
|
constexpr uint32_t kTypeEditControl = 0x009e3608;
|
||||||
|
|
||||||
|
// __RTDynamicCast (0x849701).
|
||||||
|
inline void* DynamicCast(void* object, uint32_t sourceType, uint32_t targetType) {
|
||||||
|
using Fn = void*(__cdecl*)(void*, int, uint32_t, uint32_t, int);
|
||||||
|
return object ? reinterpret_cast<Fn>(0x00849701)(object, 0, sourceType, targetType, 0) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cObjectManager::GetObject (0x5fe060) on the manager at *0xad3bc0.
|
||||||
|
inline void* GetObjectById(uint32_t id) {
|
||||||
|
using Fn = void*(__fastcall*)(void* manager, void* edx, uint32_t id);
|
||||||
|
void* manager = *reinterpret_cast<void**>(0x00ad3bc0);
|
||||||
|
return manager ? reinterpret_cast<Fn>(0x005fe060)(manager, nullptr, id) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The object `id` as a cCreature, or null.
|
||||||
|
inline void* GetCreature(uint32_t id) {
|
||||||
|
return DynamicCast(GetObjectById(id), kTypeObject, kTypeCreature);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cKernel::PostEvent (0x808930) on Kernel_Instance() (0x808490): the kernel
|
||||||
|
// copies the event and dispatches it (key events right away).
|
||||||
|
inline void PostEvent(void* event) {
|
||||||
|
using InstanceFn = void*(__cdecl*)();
|
||||||
|
using PostFn = void(__fastcall*)(void* kernel, void* edx, void* event, int a, int b);
|
||||||
|
reinterpret_cast<PostFn>(0x00808930)(reinterpret_cast<InstanceFn>(0x00808490)(), nullptr, event, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
+12
-33
@@ -5,9 +5,10 @@
|
|||||||
// several event receivers (engine 0x618130, UI manager 0x757120, taskbar
|
// several event receivers (engine 0x618130, UI manager 0x757120, taskbar
|
||||||
// 0x6e1970). This mod reads the raw key messages instead (virtual keys,
|
// 0x6e1970). This mod reads the raw key messages instead (virtual keys,
|
||||||
// independent of the keyboard layout): a bound key's messages are swallowed
|
// independent of the keyboard layout): a bound key's messages are swallowed
|
||||||
// and its action runs, usually by posting the key event of the original
|
// and its action runs. Slot keys call the taskbar's selection directly
|
||||||
// hotkey to the game's event kernel, the way the window procedure does (the
|
// (src/mods/skillbar.cpp); keys standing in for an original hotkey post that
|
||||||
// kernel dispatches it right away, on the window thread).
|
// hotkey's key event to the game's event kernel, the way the window
|
||||||
|
// procedure does (the kernel dispatches it right away, on the window thread).
|
||||||
//
|
//
|
||||||
// While a text field has the keyboard, or Ctrl / Alt is held, keys pass
|
// While a text field has the keyboard, or Ctrl / Alt is held, keys pass
|
||||||
// through unchanged.
|
// through unchanged.
|
||||||
@@ -27,6 +28,8 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "game/engine/objects.h"
|
||||||
|
#include "mods/skillbar.h"
|
||||||
#include "mods/skillkeys.h"
|
#include "mods/skillkeys.h"
|
||||||
#include "mods/wasd.h"
|
#include "mods/wasd.h"
|
||||||
#include "re/config.h"
|
#include "re/config.h"
|
||||||
@@ -41,15 +44,6 @@ namespace {
|
|||||||
constexpr uint32_t kUiManager = 0x017ecb3c; // cUI_Manager*: focused control at +0x148
|
constexpr uint32_t kUiManager = 0x017ecb3c; // cUI_Manager*: focused control at +0x148
|
||||||
constexpr uint32_t kKeyModifiers = 0x0182cdec; // u16, as the in-game window procedure keeps it
|
constexpr uint32_t kKeyModifiers = 0x0182cdec; // u16, as the in-game window procedure keeps it
|
||||||
constexpr uint32_t kEventKeyVtable = 0x00895568;
|
constexpr uint32_t kEventKeyVtable = 0x00895568;
|
||||||
constexpr uint32_t kTypeControl = 0x009de168;
|
|
||||||
constexpr uint32_t kTypeEditControl = 0x009e3608;
|
|
||||||
|
|
||||||
using DynamicCastFn = void*(__cdecl*)(void* object, int vfDelta, uint32_t source, uint32_t target, int isReference);
|
|
||||||
using KernelInstanceFn = void*(__cdecl*)();
|
|
||||||
using PostEventFn = void(__fastcall*)(void* kernel, void* edx, void* event, int a, int b);
|
|
||||||
const auto DynamicCast = reinterpret_cast<DynamicCastFn>(0x00849701);
|
|
||||||
const auto KernelInstance = reinterpret_cast<KernelInstanceFn>(0x00808490);
|
|
||||||
const auto PostEvent = reinterpret_cast<PostEventFn>(0x00808930); // copies the event
|
|
||||||
|
|
||||||
// cEvent_key: +4 1 = pressed, 0 = released; +6 / +8 the key (character when
|
// cEvent_key: +4 1 = pressed, 0 = released; +6 / +8 the key (character when
|
||||||
// pressed, virtual key when released); +0xa modifiers.
|
// pressed, virtual key when released); +0xa modifiers.
|
||||||
@@ -74,7 +68,7 @@ void PostGameKey(char key, bool pressed) {
|
|||||||
event.raw = code;
|
event.raw = code;
|
||||||
event.key = code;
|
event.key = code;
|
||||||
event.modifiers = *reinterpret_cast<uint16_t*>(kKeyModifiers);
|
event.modifiers = *reinterpret_cast<uint16_t*>(kKeyModifiers);
|
||||||
PostEvent(KernelInstance(), nullptr, &event, 1, 0);
|
game::PostEvent(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- bindings ------------------------------------------------------------------
|
// --- bindings ------------------------------------------------------------------
|
||||||
@@ -110,12 +104,6 @@ Binding g_bindings[] = {
|
|||||||
{"SaveGame", "K", Kind::GameKey, 0, 'S'},
|
{"SaveGame", "K", Kind::GameKey, 0, 'S'},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Taskbar digits: weapon sets 1-5 on '1'-'5', combat-art slots 1-5 on
|
|
||||||
// '6'-'9', '0'.
|
|
||||||
constexpr unsigned kGameSlots = 5;
|
|
||||||
char WeaponSetDigit(int set) { return static_cast<char>('1' + set); }
|
|
||||||
char SkillSlotDigit(int slot) { return slot < 4 ? static_cast<char>('6' + slot) : '0'; }
|
|
||||||
|
|
||||||
int ParseKey(const std::string& text) {
|
int ParseKey(const std::string& text) {
|
||||||
if (text.size() == 1 && isalnum(static_cast<unsigned char>(text[0]))) {
|
if (text.size() == 1 && isalnum(static_cast<unsigned char>(text[0]))) {
|
||||||
return toupper(static_cast<unsigned char>(text[0])); // VK of A-Z / 0-9
|
return toupper(static_cast<unsigned char>(text[0])); // VK of A-Z / 0-9
|
||||||
@@ -158,17 +146,14 @@ bool Reserved(int vk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Press(const Binding& binding, int vk) {
|
void Press(const Binding& binding, int vk) {
|
||||||
|
const auto slot = static_cast<unsigned>(binding.index);
|
||||||
switch (binding.kind) {
|
switch (binding.kind) {
|
||||||
case Kind::SkillSlot:
|
case Kind::SkillSlot:
|
||||||
if (binding.index < static_cast<int>(kGameSlots)) {
|
mods::skillkeys::CastOnSelect(slot, vk);
|
||||||
mods::skillkeys::CastOnSelect(static_cast<unsigned>(binding.index), vk);
|
mods::skillbar::SelectSkillSlot(slot);
|
||||||
PostGameKey(SkillSlotDigit(binding.index), true);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Kind::WeaponSet:
|
case Kind::WeaponSet:
|
||||||
if (binding.index < static_cast<int>(kGameSlots)) {
|
mods::skillbar::SelectWeaponSet(slot);
|
||||||
PostGameKey(WeaponSetDigit(binding.index), true);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Kind::GameKey:
|
case Kind::GameKey:
|
||||||
PostGameKey(binding.gameKey, true);
|
PostGameKey(binding.gameKey, true);
|
||||||
@@ -180,14 +165,8 @@ void Release(const Binding& binding, int vk) {
|
|||||||
switch (binding.kind) {
|
switch (binding.kind) {
|
||||||
case Kind::SkillSlot:
|
case Kind::SkillSlot:
|
||||||
mods::skillkeys::KeyReleased(vk);
|
mods::skillkeys::KeyReleased(vk);
|
||||||
if (binding.index < static_cast<int>(kGameSlots)) {
|
|
||||||
PostGameKey(SkillSlotDigit(binding.index), false);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Kind::WeaponSet:
|
case Kind::WeaponSet:
|
||||||
if (binding.index < static_cast<int>(kGameSlots)) {
|
|
||||||
PostGameKey(WeaponSetDigit(binding.index), false);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Kind::GameKey:
|
case Kind::GameKey:
|
||||||
PostGameKey(binding.gameKey, false);
|
PostGameKey(binding.gameKey, false);
|
||||||
@@ -223,7 +202,7 @@ bool TypingIntoTextField() {
|
|||||||
if (!focus) {
|
if (!focus) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto* edit = static_cast<uint8_t*>(DynamicCast(focus, 0, kTypeControl, kTypeEditControl, 0));
|
auto* edit = static_cast<uint8_t*>(game::DynamicCast(focus, game::kTypeControl, game::kTypeEditControl));
|
||||||
return edit && ((*reinterpret_cast<uint32_t*>(edit + 0x10) >> 16) & 1) == 0;
|
return edit && ((*reinterpret_cast<uint32_t*>(edit + 0x10) >> 16) & 1) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// 7 weapon-set and 7 combat-art slots on the taskbar.
|
||||||
|
//
|
||||||
|
// cUI_Taskbar2 keeps its slot controls in two packed arrays of 5 (+0x164
|
||||||
|
// weapon sets, +0x178 art slots) and walks them in loops of 5. The arrays move
|
||||||
|
// to the end of the taskbar object, which is allocated 0x40 bytes larger, with
|
||||||
|
// room for 7 each; every instruction addressing them, the loops and the slot
|
||||||
|
// count clamp are patched (tables in game/addresses.h, found by reviewing all
|
||||||
|
// of 0x6df340-0x6e6000, docs/skill-slots.md). The layout code then creates 7
|
||||||
|
// controls per row at the original spacing: weapon sets grow leftwards from
|
||||||
|
// the centre, art slots rightwards.
|
||||||
|
//
|
||||||
|
// The old arrays keep a copy of the first 5 controls, so code that was missed
|
||||||
|
// still sees valid controls.
|
||||||
|
//
|
||||||
|
// Also: 7 entries in the slot-count-by-level table (6 at level 45, 7 at 65),
|
||||||
|
// and cCreature::AssignArtToSlot accepts slots up to 6.
|
||||||
|
|
||||||
|
#include "mods/skillbar.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "game/addresses.h"
|
||||||
|
#include "mods/skillslots.h"
|
||||||
|
#include "re/hook.h"
|
||||||
|
#include "re/log.h"
|
||||||
|
#include "re/patch.h"
|
||||||
|
|
||||||
|
namespace mods::skillbar {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace game::addr;
|
||||||
|
using mods::skillslots::kGameSlots;
|
||||||
|
using mods::skillslots::kSlots;
|
||||||
|
|
||||||
|
constexpr uint32_t kWeaponSlots = kTaskbarSize; // new arrays behind the old object
|
||||||
|
constexpr uint32_t kSkillSlots = kWeaponSlots + kSlots * 4;
|
||||||
|
constexpr uint32_t kNewTaskbarSize = kSkillSlots + kSlots * 4 + 0x8;
|
||||||
|
constexpr int8_t kOldDistance = static_cast<int8_t>(kTaskbarSkillSlots - kTaskbarWeaponSlots); // 0x14
|
||||||
|
constexpr int8_t kNewDistance = static_cast<int8_t>(kSkillSlots - kWeaponSlots); // 0x1c
|
||||||
|
|
||||||
|
// (level, slots): the game's table plus slots 6 and 7.
|
||||||
|
constexpr uint16_t kSlotLevels[kSlots] = {0, 2, 8, 16, 30, 45, 65};
|
||||||
|
|
||||||
|
std::atomic<uint8_t*> g_taskbar{nullptr};
|
||||||
|
|
||||||
|
void Startup() {
|
||||||
|
int failed = 0;
|
||||||
|
failed += !re::PatchPushImm32(kTaskbarAllocSizePush, kTaskbarSize, kNewTaskbarSize);
|
||||||
|
failed += re::PatchAll(kTaskbarWeaponSlotUsers, kTaskbarWeaponSlots, kWeaponSlots);
|
||||||
|
failed += re::PatchAll(kTaskbarSkillSlotUsers, kTaskbarSkillSlots, kSkillSlots);
|
||||||
|
failed += re::PatchAll(kTaskbarSlotDistanceDown, static_cast<uint8_t>(-kNewDistance));
|
||||||
|
failed += re::PatchAll(kTaskbarSlotDistanceUp, static_cast<uint8_t>(kNewDistance));
|
||||||
|
failed += re::PatchAll(kTaskbarSlotLoopBounds, static_cast<uint8_t>(kSlots));
|
||||||
|
failed += re::PatchAll(kAssignArtMaxSlot, static_cast<uint8_t>(kSlots - 1));
|
||||||
|
static_assert(kOldDistance == 0x14);
|
||||||
|
re::Log("skillbar: %u weapon sets / art slots on the taskbar (%d patches failed)", kSlots, failed);
|
||||||
|
}
|
||||||
|
RE_STARTUP(SkillBarStartup, Startup);
|
||||||
|
|
||||||
|
using SelectFn = void(__fastcall*)(void* taskbar, void* edx, uint32_t slot, int notify);
|
||||||
|
|
||||||
|
void Select(uint32_t function, unsigned slot) {
|
||||||
|
if (uint8_t* taskbar = g_taskbar.load()) {
|
||||||
|
reinterpret_cast<SelectFn>(function)(taskbar, nullptr, slot, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void SelectSkillSlot(unsigned slot) {
|
||||||
|
Select(0x006e17d0, slot); // cUI_Taskbar2::SelectSkillSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the game option COMBINE_SLOTS (Options::CombineSlots 0x81c020) a
|
||||||
|
// weapon set also selects the art slot with the same number, as the digit
|
||||||
|
// keys do (cUI_Taskbar2::HandleEvent).
|
||||||
|
void SelectWeaponSet(unsigned slot) {
|
||||||
|
Select(0x006e1440, slot); // cUI_Taskbar2::SelectWeaponSet
|
||||||
|
using CombineSlotsFn = char(__cdecl*)();
|
||||||
|
if (reinterpret_cast<CombineSlotsFn>(0x0081c020)()) {
|
||||||
|
SelectSkillSlot(slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTaskbar(void* taskbar) {
|
||||||
|
g_taskbar = static_cast<uint8_t*>(taskbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy of the first 5 controls of each row into the old arrays.
|
||||||
|
void MirrorOldArrays(void* taskbar) {
|
||||||
|
auto* p = static_cast<uint8_t*>(taskbar);
|
||||||
|
std::memcpy(p + kTaskbarWeaponSlots, p + kWeaponSlots, kGameSlots * 4);
|
||||||
|
std::memcpy(p + kTaskbarSkillSlots, p + kSkillSlots, kGameSlots * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t SlotCountForLevel(unsigned level) {
|
||||||
|
uint16_t count = 1;
|
||||||
|
for (unsigned i = 0; i < kSlots; ++i) {
|
||||||
|
if (level >= kSlotLevels[i]) {
|
||||||
|
count = static_cast<uint16_t>(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mods::skillbar
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
|
||||||
|
// cUI_Taskbar2's layout (0x6e4ba0, from the constructor): creates the slot
|
||||||
|
// controls.
|
||||||
|
void __fastcall UiTaskbar_Layout(void* self, void* edx);
|
||||||
|
RE_HOOK(0x006e4ba0, UiTaskbar_Layout);
|
||||||
|
void __fastcall UiTaskbar_Layout(void* self, void* edx) {
|
||||||
|
mods::skillbar::OnTaskbar(self);
|
||||||
|
UiTaskbar_Layout_original(self, edx);
|
||||||
|
mods::skillbar::MirrorOldArrays(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cUI_Taskbar2::SetSlotCount (0x6dffa0): shows the first n slots of each row.
|
||||||
|
void __fastcall UiTaskbar_SetSlotCount(void* self, void* edx, uint32_t count);
|
||||||
|
RE_HOOK(0x006dffa0, UiTaskbar_SetSlotCount);
|
||||||
|
void __fastcall UiTaskbar_SetSlotCount(void* self, void* edx, uint32_t count) {
|
||||||
|
mods::skillbar::OnTaskbar(self);
|
||||||
|
UiTaskbar_SetSlotCount_original(self, edx, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t __stdcall SlotCountForLevel(uint32_t level);
|
||||||
|
RE_HOOK(addr::SlotCountForLevel, SlotCountForLevel);
|
||||||
|
uint32_t __stdcall SlotCountForLevel(uint32_t level) {
|
||||||
|
return mods::skillbar::SlotCountForLevel(level & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace game
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace mods::skillbar {
|
||||||
|
|
||||||
|
// Select weapon set / combat-art slot `slot` (0-6) on the taskbar, as its key
|
||||||
|
// or a click does.
|
||||||
|
void SelectSkillSlot(unsigned slot);
|
||||||
|
void SelectWeaponSet(unsigned slot);
|
||||||
|
|
||||||
|
} // namespace mods::skillbar
|
||||||
+58
-96
@@ -79,168 +79,130 @@ unsigned Slot(uint32_t value) {
|
|||||||
return value & 0xffff;
|
return value & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs a slot method: slots 0-4 on the inventory, 5-6 on its shadow (with
|
||||||
|
// slot - 5; created on demand if `create`), others not at all (result 0).
|
||||||
|
template <class Original, class... Args>
|
||||||
|
auto ForSlot(Original original, void* self, uint32_t slot, bool create, Args... args) {
|
||||||
|
if (Slot(slot) < kGameSlots) {
|
||||||
|
return original(self, nullptr, slot, args...);
|
||||||
|
}
|
||||||
|
uint8_t* shadow = Slot(slot) < kSlots ? ShadowFor(self, create) : nullptr;
|
||||||
|
if (!shadow) {
|
||||||
|
return decltype(original(self, nullptr, slot, args...))();
|
||||||
|
}
|
||||||
|
return original(shadow, nullptr, Slot(slot) - kGameSlots, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs a method on the inventory and, if it has one, on its shadow.
|
||||||
|
template <class Original, class... Args>
|
||||||
|
void WithShadow(Original original, void* self, Args... args) {
|
||||||
|
original(self, nullptr, args...);
|
||||||
|
if (uint8_t* shadow = ShadowFor(self, false)) {
|
||||||
|
original(shadow, nullptr, args...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// --- weapon sets ---------------------------------------------------------------
|
|
||||||
|
// --- weapon sets: (set, hand, ...) ---------------------------------------------
|
||||||
|
|
||||||
uint32_t __fastcall Inv_GetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand);
|
uint32_t __fastcall Inv_GetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand);
|
||||||
RE_HOOK(0x005d86c0, Inv_GetWeaponSetItem);
|
RE_HOOK(0x005d86c0, Inv_GetWeaponSetItem);
|
||||||
uint32_t __fastcall Inv_GetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand) {
|
uint32_t __fastcall Inv_GetWeaponSetItem(void* self, void*, uint32_t set, uint32_t hand) {
|
||||||
if (Slot(set) < kGameSlots) {
|
return ForSlot(Inv_GetWeaponSetItem_original, self, set, false, hand);
|
||||||
return Inv_GetWeaponSetItem_original(self, edx, set, hand);
|
|
||||||
}
|
|
||||||
uint8_t* shadow = Slot(set) < kSlots ? ShadowFor(self, false) : nullptr;
|
|
||||||
return shadow ? Inv_GetWeaponSetItem_original(shadow, edx, Slot(set) - kGameSlots, hand) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fastcall Inv_SetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, int id, int notify);
|
void __fastcall Inv_SetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, int id, int notify);
|
||||||
RE_HOOK(0x005d86f0, Inv_SetWeaponSetItem);
|
RE_HOOK(0x005d86f0, Inv_SetWeaponSetItem);
|
||||||
void __fastcall Inv_SetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, int id, int notify) {
|
void __fastcall Inv_SetWeaponSetItem(void* self, void*, uint32_t set, uint32_t hand, int id, int notify) {
|
||||||
if (Slot(set) < kGameSlots) {
|
ForSlot(Inv_SetWeaponSetItem_original, self, set, true, hand, id, notify);
|
||||||
return Inv_SetWeaponSetItem_original(self, edx, set, hand, id, notify);
|
|
||||||
}
|
|
||||||
if (Slot(set) < kSlots) {
|
|
||||||
if (uint8_t* shadow = ShadowFor(self, true)) {
|
|
||||||
Inv_SetWeaponSetItem_original(shadow, edx, Slot(set) - kGameSlots, hand, id, notify);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fastcall Inv_SetWeaponSet(void* self, void* edx, uint32_t set, int left, int right, int notify);
|
void __fastcall Inv_SetWeaponSet(void* self, void* edx, uint32_t set, int left, int right, int notify);
|
||||||
RE_HOOK(0x005d8930, Inv_SetWeaponSet);
|
RE_HOOK(0x005d8930, Inv_SetWeaponSet);
|
||||||
void __fastcall Inv_SetWeaponSet(void* self, void* edx, uint32_t set, int left, int right, int notify) {
|
void __fastcall Inv_SetWeaponSet(void* self, void*, uint32_t set, int left, int right, int notify) {
|
||||||
if (Slot(set) < kGameSlots) {
|
ForSlot(Inv_SetWeaponSet_original, self, set, true, left, right, notify);
|
||||||
return Inv_SetWeaponSet_original(self, edx, set, left, right, notify);
|
|
||||||
}
|
|
||||||
if (Slot(set) < kSlots) {
|
|
||||||
if (uint8_t* shadow = ShadowFor(self, true)) {
|
|
||||||
Inv_SetWeaponSet_original(shadow, edx, Slot(set) - kGameSlots, left, right, notify);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t __fastcall Inv_TakeWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, void* out, int flag);
|
uint32_t __fastcall Inv_TakeWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, void* out, int flag);
|
||||||
RE_HOOK(0x005d8be0, Inv_TakeWeaponSetItem);
|
RE_HOOK(0x005d8be0, Inv_TakeWeaponSetItem);
|
||||||
uint32_t __fastcall Inv_TakeWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, void* out, int flag) {
|
uint32_t __fastcall Inv_TakeWeaponSetItem(void* self, void*, uint32_t set, uint32_t hand, void* out, int flag) {
|
||||||
if (Slot(set) < kGameSlots) {
|
return ForSlot(Inv_TakeWeaponSetItem_original, self, set, false, hand, out, flag);
|
||||||
return Inv_TakeWeaponSetItem_original(self, edx, set, hand, out, flag);
|
|
||||||
}
|
|
||||||
uint8_t* shadow = Slot(set) < kSlots ? ShadowFor(self, false) : nullptr;
|
|
||||||
return shadow ? Inv_TakeWeaponSetItem_original(shadow, edx, Slot(set) - kGameSlots, hand, out, flag) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t __fastcall Inv_GetWeaponSetItemPos(void* self, void* edx, uint32_t set, uint32_t hand, void* out);
|
uint32_t __fastcall Inv_GetWeaponSetItemPos(void* self, void* edx, uint32_t set, uint32_t hand, void* out);
|
||||||
RE_HOOK(0x005d8d00, Inv_GetWeaponSetItemPos);
|
RE_HOOK(0x005d8d00, Inv_GetWeaponSetItemPos);
|
||||||
uint32_t __fastcall Inv_GetWeaponSetItemPos(void* self, void* edx, uint32_t set, uint32_t hand, void* out) {
|
uint32_t __fastcall Inv_GetWeaponSetItemPos(void* self, void*, uint32_t set, uint32_t hand, void* out) {
|
||||||
if (Slot(set) < kGameSlots) {
|
return ForSlot(Inv_GetWeaponSetItemPos_original, self, set, false, hand, out);
|
||||||
return Inv_GetWeaponSetItemPos_original(self, edx, set, hand, out);
|
|
||||||
}
|
|
||||||
uint8_t* shadow = Slot(set) < kSlots ? ShadowFor(self, false) : nullptr;
|
|
||||||
return shadow ? Inv_GetWeaponSetItemPos_original(shadow, edx, Slot(set) - kGameSlots, hand, out) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- combat-art slots ----------------------------------------------------------
|
// --- combat-art slots: (slot, ...) ---------------------------------------------
|
||||||
|
|
||||||
uint32_t __fastcall Inv_GetArtSlotType(void* self, void* edx, uint32_t slot);
|
uint32_t __fastcall Inv_GetArtSlotType(void* self, void* edx, uint32_t slot);
|
||||||
RE_HOOK(0x005d93e0, Inv_GetArtSlotType);
|
RE_HOOK(0x005d93e0, Inv_GetArtSlotType);
|
||||||
uint32_t __fastcall Inv_GetArtSlotType(void* self, void* edx, uint32_t slot) {
|
uint32_t __fastcall Inv_GetArtSlotType(void* self, void*, uint32_t slot) {
|
||||||
if (Slot(slot) < kGameSlots) {
|
return ForSlot(Inv_GetArtSlotType_original, self, slot, false);
|
||||||
return Inv_GetArtSlotType_original(self, edx, slot);
|
|
||||||
}
|
|
||||||
uint8_t* shadow = Slot(slot) < kSlots ? ShadowFor(self, false) : nullptr;
|
|
||||||
return shadow ? Inv_GetArtSlotType_original(shadow, edx, Slot(slot) - kGameSlots) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t __fastcall Inv_GetArtSlotRef(void* self, void* edx, uint32_t slot);
|
uint32_t __fastcall Inv_GetArtSlotRef(void* self, void* edx, uint32_t slot);
|
||||||
RE_HOOK(0x005d9420, Inv_GetArtSlotRef);
|
RE_HOOK(0x005d9420, Inv_GetArtSlotRef);
|
||||||
uint32_t __fastcall Inv_GetArtSlotRef(void* self, void* edx, uint32_t slot) {
|
uint32_t __fastcall Inv_GetArtSlotRef(void* self, void*, uint32_t slot) {
|
||||||
if (Slot(slot) < kGameSlots) {
|
return ForSlot(Inv_GetArtSlotRef_original, self, slot, false);
|
||||||
return Inv_GetArtSlotRef_original(self, edx, slot);
|
|
||||||
}
|
|
||||||
uint8_t* shadow = Slot(slot) < kSlots ? ShadowFor(self, false) : nullptr;
|
|
||||||
return shadow ? Inv_GetArtSlotRef_original(shadow, edx, Slot(slot) - kGameSlots) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t __fastcall Inv_GetArtSlotData(void* self, void* edx, uint32_t slot);
|
uint32_t __fastcall Inv_GetArtSlotData(void* self, void* edx, uint32_t slot);
|
||||||
RE_HOOK(0x005d9450, Inv_GetArtSlotData);
|
RE_HOOK(0x005d9450, Inv_GetArtSlotData);
|
||||||
uint32_t __fastcall Inv_GetArtSlotData(void* self, void* edx, uint32_t slot) {
|
uint32_t __fastcall Inv_GetArtSlotData(void* self, void*, uint32_t slot) {
|
||||||
if (Slot(slot) < kGameSlots) {
|
return ForSlot(Inv_GetArtSlotData_original, self, slot, false);
|
||||||
return Inv_GetArtSlotData_original(self, edx, slot);
|
|
||||||
}
|
|
||||||
uint8_t* shadow = Slot(slot) < kSlots ? ShadowFor(self, false) : nullptr;
|
|
||||||
return shadow ? Inv_GetArtSlotData_original(shadow, edx, Slot(slot) - kGameSlots) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fastcall Inv_SetArtSlot(void* self, void* edx, uint32_t slot, uint32_t type, uint32_t data, void* refs);
|
void __fastcall Inv_SetArtSlot(void* self, void* edx, uint32_t slot, uint32_t type, uint32_t data, void* refs);
|
||||||
RE_HOOK(0x005d9480, Inv_SetArtSlot);
|
RE_HOOK(0x005d9480, Inv_SetArtSlot);
|
||||||
void __fastcall Inv_SetArtSlot(void* self, void* edx, uint32_t slot, uint32_t type, uint32_t data, void* refs) {
|
void __fastcall Inv_SetArtSlot(void* self, void*, uint32_t slot, uint32_t type, uint32_t data, void* refs) {
|
||||||
if (Slot(slot) < kGameSlots) {
|
ForSlot(Inv_SetArtSlot_original, self, slot, true, type, data, refs);
|
||||||
return Inv_SetArtSlot_original(self, edx, slot, type, data, refs);
|
|
||||||
}
|
|
||||||
if (Slot(slot) < kSlots) {
|
|
||||||
if (uint8_t* shadow = ShadowFor(self, true)) {
|
|
||||||
Inv_SetArtSlot_original(shadow, edx, Slot(slot) - kGameSlots, type, data, refs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fastcall Inv_ClearArtSlot(void* self, void* edx, uint32_t slot);
|
void __fastcall Inv_ClearArtSlot(void* self, void* edx, uint32_t slot);
|
||||||
RE_HOOK(0x005d9680, Inv_ClearArtSlot);
|
RE_HOOK(0x005d9680, Inv_ClearArtSlot);
|
||||||
void __fastcall Inv_ClearArtSlot(void* self, void* edx, uint32_t slot) {
|
void __fastcall Inv_ClearArtSlot(void* self, void*, uint32_t slot) {
|
||||||
if (Slot(slot) < kGameSlots) {
|
ForSlot(Inv_ClearArtSlot_original, self, slot, false);
|
||||||
return Inv_ClearArtSlot_original(self, edx, slot);
|
|
||||||
}
|
|
||||||
if (uint8_t* shadow = Slot(slot) < kSlots ? ShadowFor(self, false) : nullptr) {
|
|
||||||
Inv_ClearArtSlot_original(shadow, edx, Slot(slot) - kGameSlots);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectSlot(slot, kind 0 weapon set / 1 art slot, apply, notify): stores the
|
// SelectSlot(slot, kind 0 weapon set / 1 art slot, apply, notify): stores the
|
||||||
// selection; for an art slot also tells the engine which art the right mouse
|
// selection; for an art slot also tells the engine which art the right mouse
|
||||||
// button uses (read from the slot entry).
|
// button uses (read from the slot entry, hence run on the shadow for 5-6).
|
||||||
void __fastcall Inv_SelectSlot(void* self, void* edx, uint32_t slot, uint32_t kind, int apply, int notify);
|
void __fastcall Inv_SelectSlot(void* self, void* edx, uint32_t slot, uint32_t kind, int apply, int notify);
|
||||||
RE_HOOK(0x005d9820, Inv_SelectSlot);
|
RE_HOOK(0x005d9820, Inv_SelectSlot);
|
||||||
void __fastcall Inv_SelectSlot(void* self, void* edx, uint32_t slot, uint32_t kind, int apply, int notify) {
|
void __fastcall Inv_SelectSlot(void* self, void*, uint32_t slot, uint32_t kind, int apply, int notify) {
|
||||||
if (Slot(slot) < kGameSlots) {
|
if (Slot(kind) > 1) {
|
||||||
return Inv_SelectSlot_original(self, edx, slot, kind, apply, notify);
|
|
||||||
}
|
|
||||||
if (Slot(slot) >= kSlots || Slot(kind) > 1) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (uint8_t* shadow = ShadowFor(self, true)) {
|
ForSlot(Inv_SelectSlot_original, self, slot, true, kind, apply, notify);
|
||||||
Inv_SelectSlot_original(shadow, edx, Slot(slot) - kGameSlots, kind, apply, notify);
|
if (Slot(slot) >= kGameSlots && Slot(slot) < kSlots) {
|
||||||
}
|
|
||||||
auto* selected = reinterpret_cast<uint16_t*>(static_cast<uint8_t*>(self) + mods::skillslots::kSelected);
|
auto* selected = reinterpret_cast<uint16_t*>(static_cast<uint8_t*>(self) + mods::skillslots::kSelected);
|
||||||
selected[Slot(kind)] = static_cast<uint16_t>(Slot(slot));
|
selected[Slot(kind)] = static_cast<uint16_t>(Slot(slot));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- methods over all slots ----------------------------------------------------
|
// --- methods over all slots ----------------------------------------------------
|
||||||
|
|
||||||
void __fastcall Inv_Clear(void* self, void* edx, int full, uint32_t arg);
|
void __fastcall Inv_Clear(void* self, void* edx, int full, uint32_t arg);
|
||||||
RE_HOOK(0x005d7d30, Inv_Clear);
|
RE_HOOK(0x005d7d30, Inv_Clear);
|
||||||
void __fastcall Inv_Clear(void* self, void* edx, int full, uint32_t arg) {
|
void __fastcall Inv_Clear(void* self, void*, int full, uint32_t arg) {
|
||||||
Inv_Clear_original(self, edx, full, arg);
|
WithShadow(Inv_Clear_original, self, full, arg);
|
||||||
if (uint8_t* shadow = ShadowFor(self, false)) {
|
|
||||||
Inv_Clear_original(shadow, edx, full, arg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fastcall Inv_ReplaceItemId(void* self, void* edx, int oldId, int newId);
|
void __fastcall Inv_ReplaceItemId(void* self, void* edx, int oldId, int newId);
|
||||||
RE_HOOK(0x005d80e0, Inv_ReplaceItemId);
|
RE_HOOK(0x005d80e0, Inv_ReplaceItemId);
|
||||||
void __fastcall Inv_ReplaceItemId(void* self, void* edx, int oldId, int newId) {
|
void __fastcall Inv_ReplaceItemId(void* self, void*, int oldId, int newId) {
|
||||||
Inv_ReplaceItemId_original(self, edx, oldId, newId);
|
WithShadow(Inv_ReplaceItemId_original, self, oldId, newId);
|
||||||
if (uint8_t* shadow = ShadowFor(self, false)) {
|
|
||||||
Inv_ReplaceItemId_original(shadow, edx, oldId, newId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void __fastcall Inv_FixInventory(void* self, void* edx);
|
void __fastcall Inv_FixInventory(void* self, void* edx);
|
||||||
RE_HOOK(0x005de8a0, Inv_FixInventory);
|
RE_HOOK(0x005de8a0, Inv_FixInventory);
|
||||||
void __fastcall Inv_FixInventory(void* self, void* edx) {
|
void __fastcall Inv_FixInventory(void* self, void*) {
|
||||||
Inv_FixInventory_original(self, edx);
|
WithShadow(Inv_FixInventory_original, self);
|
||||||
if (uint8_t* shadow = ShadowFor(self, false)) {
|
|
||||||
Inv_FixInventory_original(shadow, edx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace game
|
} // namespace game
|
||||||
+2
-13
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "game/engine/objects.h"
|
||||||
#include "mods/input.h"
|
#include "mods/input.h"
|
||||||
#include "re/hook.h"
|
#include "re/hook.h"
|
||||||
|
|
||||||
@@ -27,16 +28,9 @@ namespace mods::wasd {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// --- sacred.exe ---------------------------------------------------------------
|
// --- sacred.exe ---------------------------------------------------------------
|
||||||
constexpr uint32_t kObjectManager = 0x00ad3bc0; // object manager**
|
|
||||||
constexpr uint32_t kEventCreatureVtable = 0x0088e95c;
|
constexpr uint32_t kEventCreatureVtable = 0x0088e95c;
|
||||||
constexpr uint32_t kTypeObject = 0x008e9648; // RTTI type descriptors
|
|
||||||
constexpr uint32_t kTypeCreature = 0x008e9660;
|
|
||||||
|
|
||||||
using GetObjectFn = void*(__fastcall*)(void* manager, void* edx, uint32_t id);
|
|
||||||
using DynamicCastFn = void*(__cdecl*)(void* object, int vfDelta, uint32_t source, uint32_t target, int isReference);
|
|
||||||
using SendOrderFn = void(__fastcall*)(void* engine, void* edx, void* creature, void* event);
|
using SendOrderFn = void(__fastcall*)(void* engine, void* edx, void* creature, void* event);
|
||||||
const auto GetObjectById = reinterpret_cast<GetObjectFn>(0x005fe060);
|
|
||||||
const auto DynamicCast = reinterpret_cast<DynamicCastFn>(0x00849701);
|
|
||||||
const auto SendOrder = reinterpret_cast<SendOrderFn>(0x006170d0); // adjusts ground targets, then vf18
|
const auto SendOrder = reinterpret_cast<SendOrderFn>(0x006170d0); // adjusts ground targets, then vf18
|
||||||
|
|
||||||
// cEngine fields.
|
// cEngine fields.
|
||||||
@@ -65,12 +59,7 @@ bool KeyDown(int key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void* ControlledCreature(uint8_t* engine) {
|
void* ControlledCreature(uint8_t* engine) {
|
||||||
void* manager = *reinterpret_cast<void**>(kObjectManager);
|
return game::GetCreature(*reinterpret_cast<uint32_t*>(engine + kEngineControlledId));
|
||||||
if (!manager) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
void* object = GetObjectById(manager, nullptr, *reinterpret_cast<uint32_t*>(engine + kEngineControlledId));
|
|
||||||
return object ? DynamicCast(object, 0, kTypeObject, kTypeCreature, 0) : nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendWalk(uint8_t* engine, void* creature, int x, int y) {
|
void SendWalk(uint8_t* engine, void* creature, int x, int y) {
|
||||||
|
|||||||
@@ -51,6 +51,32 @@ bool PatchInstructionDword(uint32_t instruction, uint32_t from, uint32_t to) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PatchInstructionByte(uint32_t instruction, size_t offset, uint8_t from, uint8_t to) {
|
||||||
|
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(instruction));
|
||||||
|
if (code[offset] != from) {
|
||||||
|
Log("patch 0x%08x: expected byte 0x%02x at +%u, found 0x%02x, skipped", instruction, from,
|
||||||
|
static_cast<unsigned>(offset), code[offset]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return WriteCode(instruction + static_cast<uint32_t>(offset), &to, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PatchAll(const BytePatch* patches, size_t count, uint8_t to) {
|
||||||
|
int failed = 0;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
failed += !PatchInstructionByte(patches[i].instruction, patches[i].offset, patches[i].from, to);
|
||||||
|
}
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PatchAll(const uint32_t* instructions, size_t count, uint32_t from, uint32_t to) {
|
||||||
|
int failed = 0;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
failed += !PatchInstructionDword(instructions[i], from, to);
|
||||||
|
}
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
bool RedirectOperand(uint32_t instruction, uint32_t from, const void* to) {
|
bool RedirectOperand(uint32_t instruction, uint32_t from, const void* to) {
|
||||||
return PatchInstructionDword(instruction, from, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(to)));
|
return PatchInstructionDword(instruction, from, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(to)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,29 @@ bool RedirectOperand(uint32_t instruction, uint32_t from, const void* to);
|
|||||||
// `instruction` by `to` (e.g. a struct field offset in `[ebp+ecx*8+3FF2Ch]`).
|
// `instruction` by `to` (e.g. a struct field offset in `[ebp+ecx*8+3FF2Ch]`).
|
||||||
bool PatchInstructionDword(uint32_t instruction, uint32_t from, uint32_t to);
|
bool PatchInstructionDword(uint32_t instruction, uint32_t from, uint32_t to);
|
||||||
|
|
||||||
|
// Replace the byte `from` at `instruction + offset` (an imm8 or disp8) by `to`.
|
||||||
|
bool PatchInstructionByte(uint32_t instruction, size_t offset, uint8_t from, uint8_t to);
|
||||||
|
|
||||||
|
// One byte operand of an instruction, for tables of byte patches.
|
||||||
|
struct BytePatch {
|
||||||
|
uint32_t instruction;
|
||||||
|
uint8_t offset; // of the byte within the instruction
|
||||||
|
uint8_t from;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply the same change to a table of instructions; return the number that
|
||||||
|
// failed (each failure is logged).
|
||||||
|
int PatchAll(const BytePatch* patches, size_t count, uint8_t to);
|
||||||
|
int PatchAll(const uint32_t* instructions, size_t count, uint32_t from, uint32_t to);
|
||||||
|
template <size_t N>
|
||||||
|
int PatchAll(const BytePatch (&patches)[N], uint8_t to) {
|
||||||
|
return PatchAll(patches, N, to);
|
||||||
|
}
|
||||||
|
template <size_t N>
|
||||||
|
int PatchAll(const uint32_t (&instructions)[N], uint32_t from, uint32_t to) {
|
||||||
|
return PatchAll(instructions, N, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
// `push imm32` (68 xx xx xx xx) at `instruction`: replace `expected` by `value`.
|
// `push imm32` (68 xx xx xx xx) at `instruction`: replace `expected` by `value`.
|
||||||
bool PatchPushImm32(uint32_t instruction, uint32_t expected, uint32_t value);
|
bool PatchPushImm32(uint32_t instruction, uint32_t expected, uint32_t value);
|
||||||
|
|
||||||
|
|||||||
@@ -110,3 +110,12 @@
|
|||||||
0x0055fac0 cCreature::AssignArtToSlot # (art, slot), rejects slot > 4
|
0x0055fac0 cCreature::AssignArtToSlot # (art, slot), rejects slot > 4
|
||||||
0x006046f0 ItemDataMgr::saveHero
|
0x006046f0 ItemDataMgr::saveHero
|
||||||
0x00605900 ItemDataMgr::loadHero
|
0x00605900 ItemDataMgr::loadHero
|
||||||
|
0x006dffa0 cUI_Taskbar2::SetSlotCount # shows the first n slots of each row, n from SlotCountForLevel
|
||||||
|
0x006e4ba0 cUI_Taskbar2::Layout # creates the slot controls and buttons
|
||||||
|
0x006dfce0 cUI_Taskbar2::SkillSlotAt # (x, y, out slot)
|
||||||
|
0x006dfd40 cUI_Taskbar2::WeaponSetAt # (x, y, out slot)
|
||||||
|
0x006df9a0 cUI_Taskbar2::UpdateSlotHighlight
|
||||||
|
0x006e2790 cUI_Taskbar2::RenderWeaponSets
|
||||||
|
0x006e31e0 cUI_Taskbar2::RenderArtSlots
|
||||||
|
0x006e4310 cUI_Taskbar2::UpdateTooltips
|
||||||
|
0x0081c020 Options::CombineSlots # COMBINE_SLOTS: weapon set also selects the art slot
|
||||||
Reference in new issue
Block a user