Skills, auto-attack, aura and chain lightning rework, Esc, cache fix
- Skill bar: no art selection; slot 0 is the right-click art, keys 0-6 cast slots directly; cooldown labels in exact seconds; slot 0 set apart. - Arts regenerate on their own (casting no longer refills the group). - Auto-attack after targeting or casting at a hostile creature, at the speed of a held button. - Auras can be recast while active (renew, no second visual). - Blicke wie Klingen hits every enemy in range, physical, with life leech. - Blitzschlag steers the game's own chain: 3 + level/5 hits, 1 + level/10 chains, re-hits a lone enemy. - Weapon sets: click selects, drag only from the selected set; the leftmost set is clickable (signed hit tests). - Esc closes open windows before opening the game menu. - HUD: standalone buttons and texts are centred. - Record cache sized for widened views (town lag while walking). - Shared game code: orders, fx events, text drawing, cooldowns, effects. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f721d2a486
commit
1fd89524d3
41 files changed
+2347
-214
No files matched your search
@@ -61,6 +61,63 @@ bool PatchInstructionByte(uint32_t instruction, size_t offset, uint8_t from, uin
|
||||
return WriteCode(instruction + static_cast<uint32_t>(offset), &to, 1);
|
||||
}
|
||||
|
||||
bool ReplaceCode(uint32_t address, const void* from, const void* to, size_t size) {
|
||||
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(address));
|
||||
if (std::memcmp(code, from, size) != 0) {
|
||||
Log("patch 0x%08x: expected %u other bytes (found %02x %02x ...), skipped", address,
|
||||
static_cast<unsigned>(size), code[0], size > 1 ? code[1] : 0);
|
||||
return false;
|
||||
}
|
||||
return WriteCode(address, to, size);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Redirects the rel32 operand at instruction + opcodeLength (instruction
|
||||
// length opcodeLength + 4) from `from` to `to`.
|
||||
bool RedirectRel32(uint32_t instruction, size_t opcodeLength, uint32_t from, const void* to) {
|
||||
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(instruction));
|
||||
const uint32_t end = instruction + static_cast<uint32_t>(opcodeLength) + 4;
|
||||
int32_t rel;
|
||||
std::memcpy(&rel, code + opcodeLength, 4);
|
||||
if (end + static_cast<uint32_t>(rel) != from) {
|
||||
Log("patch 0x%08x: expected a branch to 0x%08x, skipped", instruction, from);
|
||||
return false;
|
||||
}
|
||||
const auto target = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(to));
|
||||
const int32_t newRel = static_cast<int32_t>(target - end);
|
||||
return WriteCode(instruction + static_cast<uint32_t>(opcodeLength), &newRel, 4);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool RedirectCall(uint32_t instruction, uint32_t from, const void* to) {
|
||||
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(instruction));
|
||||
if (code[0] != 0xe8) {
|
||||
Log("patch 0x%08x: expected a call, skipped", instruction);
|
||||
return false;
|
||||
}
|
||||
return RedirectRel32(instruction, 1, from, to);
|
||||
}
|
||||
|
||||
bool RedirectJcc(uint32_t instruction, uint32_t from, const void* to) {
|
||||
const auto* code = reinterpret_cast<const uint8_t*>(static_cast<uintptr_t>(instruction));
|
||||
if (code[0] != 0x0f || (code[1] & 0xf0) != 0x80) {
|
||||
Log("patch 0x%08x: expected a conditional jump, skipped", instruction);
|
||||
return false;
|
||||
}
|
||||
return RedirectRel32(instruction, 2, from, to);
|
||||
}
|
||||
|
||||
int ReplaceAll(const CodeReplacement* replacements, size_t count) {
|
||||
int failed = 0;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const CodeReplacement& r = replacements[i];
|
||||
failed += !ReplaceCode(r.address, r.from, r.to, r.size);
|
||||
}
|
||||
return failed;
|
||||
}
|
||||
|
||||
int PatchAll(const BytePatch* patches, size_t count, uint8_t to) {
|
||||
int failed = 0;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
|
||||
@@ -20,6 +20,30 @@ 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);
|
||||
|
||||
// Replace the code bytes `from` at `address` by `to` (same length), e.g. a
|
||||
// whole short instruction.
|
||||
bool ReplaceCode(uint32_t address, const void* from, const void* to, size_t size);
|
||||
|
||||
// The `call rel32` at `instruction` (E8) calls `from`; make it call `to`.
|
||||
bool RedirectCall(uint32_t instruction, uint32_t from, const void* to);
|
||||
|
||||
// The near conditional jump at `instruction` (0F 8x rel32) goes to `from`;
|
||||
// make it go to `to`.
|
||||
bool RedirectJcc(uint32_t instruction, uint32_t from, const void* to);
|
||||
|
||||
// A code replacement, for tables.
|
||||
struct CodeReplacement {
|
||||
uint32_t address;
|
||||
uint8_t from[8];
|
||||
uint8_t to[8];
|
||||
uint8_t size;
|
||||
};
|
||||
int ReplaceAll(const CodeReplacement* replacements, size_t count);
|
||||
template <size_t N>
|
||||
int ReplaceAll(const CodeReplacement (&replacements)[N]) {
|
||||
return ReplaceAll(replacements, N);
|
||||
}
|
||||
|
||||
// One byte operand of an instruction, for tables of byte patches.
|
||||
struct BytePatch {
|
||||
uint32_t instruction;
|
||||
|
||||
Reference in new issue
Block a user