Self / ally arts cast on the hero when pointed at an enemy
Arts with launch type 2 (buffs, auras, heals) are only valid on allies or without a target; with an enemy under the cursor the game dropped the order. Orders of a player hero for such arts lose a non-ally target, which casts them on the hero. The launch type comes from the art definition tables (game/engine/arts.h, tools/arttable.mjs). Also: skill keys check the relocated taskbar slots (all 7), Ghidra data labels (g_ names in names.txt). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
dbc632750b
commit
30c93fc87e
9 files changed
+187
-10
No files matched your search
@@ -0,0 +1,37 @@
|
||||
// Dump sacred.exe's combat-art definition tables (see docs/skill-slots.md).
|
||||
// node tools/arttable.mjs <exe> [id...] (ids hex, e.g. 3f7; none: all)
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const [exePath, ...ids] = process.argv.slice(2);
|
||||
const file = readFileSync(exePath);
|
||||
const pe = file.readUInt32LE(0x3c);
|
||||
const sections = file.readUInt16LE(pe + 6);
|
||||
const optional = file.readUInt16LE(pe + 20);
|
||||
const imageBase = file.readUInt32LE(pe + 52);
|
||||
const table = pe + 24 + optional;
|
||||
function offset(va) {
|
||||
for (let i = 0; i < sections; i++) {
|
||||
const s = table + i * 40;
|
||||
const vsize = file.readUInt32LE(s + 8), vaddr = file.readUInt32LE(s + 12);
|
||||
const raw = file.readUInt32LE(s + 20), rawSize = file.readUInt32LE(s + 16);
|
||||
const rva = va - imageBase;
|
||||
if (rva >= vaddr && rva < vaddr + vsize) return rva - vaddr < rawSize ? rva - vaddr + raw : -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
const tables = [
|
||||
{ name: "A", base: 0x94ddb0, stride: 0x78, end: 0x950a38 },
|
||||
{ name: "B", base: 0x950a38, stride: 0x94, end: 0x953344 },
|
||||
];
|
||||
const wanted = new Set(ids.map((t) => parseInt(t, 16)));
|
||||
for (const t of tables) {
|
||||
for (let va = t.base; va < t.end; va += t.stride) {
|
||||
const o = offset(va);
|
||||
if (o < 0) { console.log(t.name, va.toString(16), "(no file data: filled at runtime)"); break; }
|
||||
const id = file.readUInt16LE(o);
|
||||
if (wanted.size && !wanted.has(id)) continue;
|
||||
const words = [];
|
||||
for (let k = 0; k < t.stride; k += 4) words.push(file.readUInt32LE(o + k).toString(16).padStart(8, "0"));
|
||||
console.log(`${t.name} ${va.toString(16)} id ${id.toString(16)}: ${words.join(" ")}`);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@
|
||||
//
|
||||
// names.txt: one "0xADDRESS Name" per line, "Class::Method" puts the function
|
||||
// into a class namespace; "#" starts a comment. Functions are created where
|
||||
// none starts at the address yet.
|
||||
// none starts at the address yet. Names starting with "g_" label data
|
||||
// (globals, tables) instead of functions.
|
||||
// @category sacred_re
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -33,6 +34,7 @@ public class Rename extends GhidraScript {
|
||||
}
|
||||
SymbolTable symbols = currentProgram.getSymbolTable();
|
||||
int renamed = 0;
|
||||
int labelled = 0;
|
||||
for (String raw : Files.readAllLines(Paths.get(args[0]), StandardCharsets.UTF_8)) {
|
||||
String line = raw.replaceAll("#.*", "").trim();
|
||||
if (line.isEmpty()) {
|
||||
@@ -44,6 +46,11 @@ public class Rename extends GhidraScript {
|
||||
continue;
|
||||
}
|
||||
Address address = toAddr(parts[0]);
|
||||
if (parts[1].startsWith("g_")) {
|
||||
createLabel(address, parts[1], true, SourceType.USER_DEFINED);
|
||||
labelled++;
|
||||
continue;
|
||||
}
|
||||
Function function = getFunctionAt(address);
|
||||
if (function == null) {
|
||||
new DisassembleCommand(address, null, true).applyTo(currentProgram, monitor);
|
||||
@@ -69,6 +76,6 @@ public class Rename extends GhidraScript {
|
||||
function.setName(name, SourceType.USER_DEFINED);
|
||||
renamed++;
|
||||
}
|
||||
println("renamed " + renamed + " functions");
|
||||
println("renamed " + renamed + " functions, labelled " + labelled + " data addresses");
|
||||
}
|
||||
}
|
||||
@@ -119,3 +119,25 @@
|
||||
0x006e31e0 cUI_Taskbar2::RenderArtSlots
|
||||
0x006e4310 cUI_Taskbar2::UpdateTooltips
|
||||
0x0081c020 Options::CombineSlots # COMBINE_SLOTS: weapon set also selects the art slot
|
||||
# combat arts
|
||||
0x0052e400 cCreature::ReceiveOrder # vf18; order 0x105 (use art) at 0x52fc58
|
||||
0x006170d0 cEngine::SendCreatureOrder
|
||||
0x005521e0 cCreature::ResolveArtId # art variant for the creature class (+0x10)
|
||||
0x00580cb0 cArtCooldowns::IsReady # (art): cooldown <= 0.01
|
||||
0x00548ef0 cCreature::IsAlive
|
||||
0x004d1930 CombatMoveDef::Find # (id) -> g_combatMoveDefs entry
|
||||
0x004d2150 SpellDef::Find # (id) -> g_spellDefs entry
|
||||
0x004d1a20 CombatMoveDef::UsableMounted # flags +0x74
|
||||
0x004d23e0 SpellDef::UsableMounted # flags +0x90
|
||||
0x0094ddb0 g_combatMoveDefs # 95 x 0x78: id +0, icon name +0xc, target type +0x4c
|
||||
0x00950a38 g_spellDefs # 71 x 0x94: id +0, icon name +0xc, target type u16 +0x3c
|
||||
0x0095503c g_slotCountByLevel # (level, slots) u16 pairs
|
||||
0x009ed058 g_taskbarSlotColors
|
||||
0x00ad23c8 g_inventoryManager
|
||||
0x00ad3bc0 g_objectManager
|
||||
0x0051d680 cCreature::IsValidArtTarget # (pick, launch type): can the art be used on the picked object
|
||||
0x0051d670 NormalizeArtId
|
||||
0x00548f20 cCreature::IsAlly # (object id)
|
||||
0x00548e70 cCreature::IsEnemy # (object id)
|
||||
0x00611f10 cEngine::UpdateCursorPick # cursor target for the selected art
|
||||
0x00548060 cCreature::ChooseAiArt # monsters: art per creature class
|
||||
Reference in new issue
Block a user