Inventories: data for slots 6-7, JSON in save files
Weapon sets and combat-art slots 6-7 live in a shadow inventory per
inventory; the slot methods run the original code on it for slots >= 5,
so item references, icon resources and selection events stay the
game's own. Save games (cInventoryManager::Serialize) and hero files
(saveHero / loadHero) store inventories as JSON ("SRIJ" + length);
vanilla blocks are still read, which migrates old saves. Multiplayer
transfers keep the binary format. nlohmann/json fetched via git.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
37634c77ba
commit
998e315d98
7 files changed
+754
-4
No files matched your search
@@ -0,0 +1,246 @@
|
||||
// 7 weapon sets and 7 combat-art slots per inventory.
|
||||
//
|
||||
// cInventoryEntry keeps 5 of each in packed arrays (docs/skill-slots.md). The
|
||||
// two extra slots live in a shadow inventory per inventory: a zeroed
|
||||
// cInventoryEntry-sized block whose slots 0-1 are slots 5-6. The slot methods
|
||||
// are hooked; for a slot >= 5 they run the original method on the shadow with
|
||||
// slot - 5, so all side effects (item references, icon resources, the
|
||||
// engine's art selection event) are the game's own code. The methods that
|
||||
// walk all slots (Clear, ReplaceItemId, FixInventory) also run on the shadow.
|
||||
//
|
||||
// The shadow carries the inventory's player index (+0x1160) and flags
|
||||
// (+0x1162, bit 2 = second art slot variant), copied before every call.
|
||||
// Network events sent from the shadow would name slot - 5: multiplayer with
|
||||
// more than 5 slots is not supported.
|
||||
|
||||
#include "mods/skillslots.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "re/hook.h"
|
||||
|
||||
namespace mods::skillslots {
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kInventoryBlock = 0x00ad23c8; // cInventoryManager: 32 inventories
|
||||
constexpr unsigned kInventories = 32;
|
||||
constexpr size_t kPlayerIndex = 0x1160;
|
||||
constexpr size_t kFlags = 0x1162;
|
||||
|
||||
std::mutex g_lock;
|
||||
uint8_t* g_shadows[kInventories] = {};
|
||||
|
||||
int IndexOf(const void* inventory) {
|
||||
const auto* base = *reinterpret_cast<const uint8_t* const*>(kInventoryBlock);
|
||||
const auto* p = static_cast<const uint8_t*>(inventory);
|
||||
if (!base || p < base || p >= base + kInventories * kInventorySize ||
|
||||
(p - base) % kInventorySize != 0) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int>((p - base) / kInventorySize);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint8_t* ShadowFor(const void* inventory, bool create) {
|
||||
const int index = IndexOf(inventory);
|
||||
if (index < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
std::lock_guard guard(g_lock);
|
||||
uint8_t*& shadow = g_shadows[index];
|
||||
if (!shadow && create) {
|
||||
shadow = static_cast<uint8_t*>(VirtualAlloc(nullptr, kInventorySize, MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_READWRITE));
|
||||
}
|
||||
if (shadow) {
|
||||
const auto* p = static_cast<const uint8_t*>(inventory);
|
||||
std::memcpy(shadow + kPlayerIndex, p + kPlayerIndex, 2);
|
||||
std::memcpy(shadow + kFlags, p + kFlags, 2);
|
||||
}
|
||||
return shadow;
|
||||
}
|
||||
|
||||
} // namespace mods::skillslots
|
||||
|
||||
namespace game {
|
||||
|
||||
using mods::skillslots::kGameSlots;
|
||||
using mods::skillslots::kSlots;
|
||||
using mods::skillslots::ShadowFor;
|
||||
|
||||
namespace {
|
||||
|
||||
// Slot argument of a method (u16 in the game).
|
||||
unsigned Slot(uint32_t value) {
|
||||
return value & 0xffff;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// --- weapon sets ---------------------------------------------------------------
|
||||
|
||||
uint32_t __fastcall Inv_GetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand);
|
||||
RE_HOOK(0x005d86c0, Inv_GetWeaponSetItem);
|
||||
uint32_t __fastcall Inv_GetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand) {
|
||||
if (Slot(set) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d86f0, Inv_SetWeaponSetItem);
|
||||
void __fastcall Inv_SetWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, int id, int notify) {
|
||||
if (Slot(set) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d8930, Inv_SetWeaponSet);
|
||||
void __fastcall Inv_SetWeaponSet(void* self, void* edx, uint32_t set, int left, int right, int notify) {
|
||||
if (Slot(set) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d8be0, Inv_TakeWeaponSetItem);
|
||||
uint32_t __fastcall Inv_TakeWeaponSetItem(void* self, void* edx, uint32_t set, uint32_t hand, void* out, int flag) {
|
||||
if (Slot(set) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d8d00, Inv_GetWeaponSetItemPos);
|
||||
uint32_t __fastcall Inv_GetWeaponSetItemPos(void* self, void* edx, uint32_t set, uint32_t hand, void* out) {
|
||||
if (Slot(set) < kGameSlots) {
|
||||
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 ----------------------------------------------------------
|
||||
|
||||
uint32_t __fastcall Inv_GetArtSlotType(void* self, void* edx, uint32_t slot);
|
||||
RE_HOOK(0x005d93e0, Inv_GetArtSlotType);
|
||||
uint32_t __fastcall Inv_GetArtSlotType(void* self, void* edx, uint32_t slot) {
|
||||
if (Slot(slot) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d9420, Inv_GetArtSlotRef);
|
||||
uint32_t __fastcall Inv_GetArtSlotRef(void* self, void* edx, uint32_t slot) {
|
||||
if (Slot(slot) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d9450, Inv_GetArtSlotData);
|
||||
uint32_t __fastcall Inv_GetArtSlotData(void* self, void* edx, uint32_t slot) {
|
||||
if (Slot(slot) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d9480, Inv_SetArtSlot);
|
||||
void __fastcall Inv_SetArtSlot(void* self, void* edx, uint32_t slot, uint32_t type, uint32_t data, void* refs) {
|
||||
if (Slot(slot) < kGameSlots) {
|
||||
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);
|
||||
RE_HOOK(0x005d9680, Inv_ClearArtSlot);
|
||||
void __fastcall Inv_ClearArtSlot(void* self, void* edx, uint32_t slot) {
|
||||
if (Slot(slot) < kGameSlots) {
|
||||
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
|
||||
// selection; for an art slot also tells the engine which art the right mouse
|
||||
// button uses (read from the slot entry).
|
||||
void __fastcall Inv_SelectSlot(void* self, void* edx, uint32_t slot, uint32_t kind, int apply, int notify);
|
||||
RE_HOOK(0x005d9820, Inv_SelectSlot);
|
||||
void __fastcall Inv_SelectSlot(void* self, void* edx, uint32_t slot, uint32_t kind, int apply, int notify) {
|
||||
if (Slot(slot) < kGameSlots) {
|
||||
return Inv_SelectSlot_original(self, edx, slot, kind, apply, notify);
|
||||
}
|
||||
if (Slot(slot) >= kSlots || Slot(kind) > 1) {
|
||||
return;
|
||||
}
|
||||
if (uint8_t* shadow = ShadowFor(self, true)) {
|
||||
Inv_SelectSlot_original(shadow, edx, Slot(slot) - kGameSlots, kind, apply, notify);
|
||||
}
|
||||
auto* selected = reinterpret_cast<uint16_t*>(static_cast<uint8_t*>(self) + mods::skillslots::kSelected);
|
||||
selected[Slot(kind)] = static_cast<uint16_t>(Slot(slot));
|
||||
}
|
||||
|
||||
// --- methods over all slots ----------------------------------------------------
|
||||
|
||||
void __fastcall Inv_Clear(void* self, void* edx, int full, uint32_t arg);
|
||||
RE_HOOK(0x005d7d30, Inv_Clear);
|
||||
void __fastcall Inv_Clear(void* self, void* edx, int full, uint32_t arg) {
|
||||
Inv_Clear_original(self, edx, 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);
|
||||
RE_HOOK(0x005d80e0, Inv_ReplaceItemId);
|
||||
void __fastcall Inv_ReplaceItemId(void* self, void* edx, int oldId, int newId) {
|
||||
Inv_ReplaceItemId_original(self, edx, oldId, newId);
|
||||
if (uint8_t* shadow = ShadowFor(self, false)) {
|
||||
Inv_ReplaceItemId_original(shadow, edx, oldId, newId);
|
||||
}
|
||||
}
|
||||
|
||||
void __fastcall Inv_FixInventory(void* self, void* edx);
|
||||
RE_HOOK(0x005de8a0, Inv_FixInventory);
|
||||
void __fastcall Inv_FixInventory(void* self, void* edx) {
|
||||
Inv_FixInventory_original(self, edx);
|
||||
if (uint8_t* shadow = ShadowFor(self, false)) {
|
||||
Inv_FixInventory_original(shadow, edx);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace game
|
||||
Reference in new issue
Block a user