more work done
This commit is contained in:
parent
5cc5612f4e
commit
d323bb28fc
68 changed files with 714 additions and 19 deletions
14
scripts/_tmp_apply_broker_slot08.py
Normal file
14
scripts/_tmp_apply_broker_slot08.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
print(bridge.rename_function_by_address("1288:0fc3", "allocator_phase_finalize_pass"))
|
||||
print(
|
||||
bridge.set_decompiler_comment(
|
||||
"1288:0fc3",
|
||||
"Live re-anchor for raw 0009:b1c3. Calls PresentationCallbackBroker vtable slot +0x08 twice through the installed 0x4588 broker pointer, then sweeps allocator heads for finalize cleanup.",
|
||||
)
|
||||
)
|
||||
print(bridge.get_function_by_address("1288:0fc3"))
|
||||
38
scripts/_tmp_apply_cache_backend_methods.py
Normal file
38
scripts/_tmp_apply_cache_backend_methods.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
updates = [
|
||||
{
|
||||
"address": "1250:026c",
|
||||
"name": "LoadEntryTableFromManifest",
|
||||
"comment": (
|
||||
"Constructor-selected cache-backend branch. Fetches a manifest-style buffer through the "
|
||||
"object callback table, allocates the +0x10/+0x18 entry tables, and copies parsed entry "
|
||||
"records into backend-owned storage."
|
||||
),
|
||||
},
|
||||
{
|
||||
"address": "1250:0749",
|
||||
"name": "InitFixedEntryTable",
|
||||
"comment": (
|
||||
"Constructor-selected cache-backend fallback branch. Uses the current entry count and a "
|
||||
"caller-supplied fixed record size to allocate default entry records and seed the +0x10/+0x18 tables."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
for update in updates:
|
||||
print(f"=== {update['address']} -> {update['name']} ===")
|
||||
print(
|
||||
bridge.set_function_class(
|
||||
function_address=update["address"],
|
||||
class_path="Remorse::CacheBackendObject",
|
||||
method_name=update["name"],
|
||||
)
|
||||
)
|
||||
print(bridge.set_decompiler_comment(update["address"], update["comment"]))
|
||||
print(bridge.get_function_by_address(update["address"]))
|
||||
print()
|
||||
20
scripts/_tmp_apply_cache_backend_setter.py
Normal file
20
scripts/_tmp_apply_cache_backend_setter.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
print(
|
||||
bridge.set_function_class(
|
||||
function_address="1250:0910",
|
||||
class_path="Remorse::CacheBackendObject",
|
||||
method_name="SetEntryNameAndTag",
|
||||
)
|
||||
)
|
||||
print(
|
||||
bridge.set_decompiler_comment(
|
||||
"1250:0910",
|
||||
"Indexed CacheBackendObject writer. Resolves the logical entry id through the +0x18 remap table, ensures the +0x10 entry buffer exists and is large enough, stores a 4-byte tag/header, and copies the caller-supplied name string at offset +4.",
|
||||
)
|
||||
)
|
||||
print(bridge.get_function_by_address("1250:0910"))
|
||||
39
scripts/_tmp_apply_owner_resource_accessors.py
Normal file
39
scripts/_tmp_apply_owner_resource_accessors.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
updates = [
|
||||
{
|
||||
"address": "1430:014c",
|
||||
"name": "MaterializeChecked",
|
||||
"comment": (
|
||||
"Owner-resource wrapper over helper vtable slot +0x0c. "
|
||||
"Called by InitSlotOwnerBuffers and EnsureSlotChunkLoaded to materialize owner data "
|
||||
"and assert if the first output byte is 0xff."
|
||||
),
|
||||
},
|
||||
{
|
||||
"address": "1430:0195",
|
||||
"name": "QueryMaterializationSize",
|
||||
"comment": (
|
||||
"Owner-resource wrapper over helper vtable slot +0x04. "
|
||||
"Current best read from the 1430:0000 create path is a size-query callback used ahead "
|
||||
"of owner-data materialization."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
for update in updates:
|
||||
print(f"=== {update['address']} -> {update['name']} ===")
|
||||
print(
|
||||
bridge.set_function_class(
|
||||
function_address=update["address"],
|
||||
class_path="Remorse::EntityVmOwnerResource",
|
||||
method_name=update["name"],
|
||||
)
|
||||
)
|
||||
print(bridge.set_decompiler_comment(update["address"], update["comment"]))
|
||||
print(bridge.get_function_by_address(update["address"]))
|
||||
print()
|
||||
39
scripts/_tmp_apply_presentation_callback_broker.py
Normal file
39
scripts/_tmp_apply_presentation_callback_broker.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
updates = [
|
||||
{
|
||||
"address": "12d0:0513",
|
||||
"name": "InitOnce",
|
||||
"comment": (
|
||||
"Live re-anchor for the 0x4588 presentation-callback broker install path. "
|
||||
"Guards on 0x4594, snapshots current video/system state into 0x458c/0x4590, installs "
|
||||
"the nullable broker pointer at 0x4588, and ensures the fallback buffer at 0x45a6 exists."
|
||||
),
|
||||
},
|
||||
{
|
||||
"address": "12d0:0656",
|
||||
"name": "TeardownOnce",
|
||||
"comment": (
|
||||
"Live re-anchor for the 0x4588 presentation-callback broker teardown path. "
|
||||
"Guards on 0x4595, clears the installed broker pointer, conditionally emits broker slot +0x0c "
|
||||
"when 0x4590 != 0x458c, then calls broker slot +0x04 before final video/system cleanup."
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
for update in updates:
|
||||
print(f"=== {update['address']} -> {update['name']} ===")
|
||||
print(
|
||||
bridge.set_function_class(
|
||||
function_address=update["address"],
|
||||
class_path="Remorse::PresentationCallbackBroker",
|
||||
method_name=update["name"],
|
||||
)
|
||||
)
|
||||
print(bridge.set_decompiler_comment(update["address"], update["comment"]))
|
||||
print(bridge.get_function_by_address(update["address"]))
|
||||
print()
|
||||
129
scripts/_tmp_apply_vm_class_types.py
Normal file
129
scripts/_tmp_apply_vm_class_types.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
from java.util import ArrayList
|
||||
|
||||
from ghidra.program.model.data import DWordDataType, VoidDataType, WordDataType
|
||||
from ghidra.program.model.listing import Function, ParameterImpl, ReturnParameterImpl
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
|
||||
from tools.pyghidra_crusader.common import transaction
|
||||
|
||||
|
||||
def clone_type(data_type):
|
||||
return data_type.clone(program.getDataTypeManager())
|
||||
|
||||
|
||||
def get_required_data_type(path):
|
||||
data_type = program.getDataTypeManager().getDataType(path)
|
||||
if data_type is None:
|
||||
raise RuntimeError("Missing datatype: %s" % path)
|
||||
return data_type
|
||||
|
||||
|
||||
def pointer_to(data_type):
|
||||
return program.getDataTypeManager().getPointer(data_type)
|
||||
|
||||
|
||||
def build_params(specs):
|
||||
params = ArrayList()
|
||||
for spec in specs:
|
||||
if spec.get("keep"):
|
||||
params.add(ParameterImpl(spec["parameter"], program))
|
||||
else:
|
||||
params.add(ParameterImpl(spec["name"], spec["datatype"], program, SourceType.USER_DEFINED))
|
||||
return params
|
||||
|
||||
|
||||
def update_signature(function_address, return_type, param_specs):
|
||||
function = helpers["get_function"](program, function_address)
|
||||
if function is None:
|
||||
raise RuntimeError("Function not found: %s" % function_address)
|
||||
|
||||
params = build_params(param_specs)
|
||||
return_param = ReturnParameterImpl(return_type, program)
|
||||
function.updateFunction(
|
||||
function.getCallingConventionName(),
|
||||
return_param,
|
||||
params,
|
||||
Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS,
|
||||
True,
|
||||
SourceType.USER_DEFINED,
|
||||
)
|
||||
return function
|
||||
|
||||
|
||||
runtime_struct = get_required_data_type("/Remorse/EntityVmRuntime")
|
||||
slot_entry_struct = get_required_data_type("/Remorse/EntityVmSlotEntry")
|
||||
|
||||
runtime_ptr = pointer_to(runtime_struct)
|
||||
slot_entry_ptr = pointer_to(slot_entry_struct)
|
||||
word_type = clone_type(WordDataType.dataType)
|
||||
dword_type = clone_type(DWordDataType.dataType)
|
||||
void_type = clone_type(VoidDataType.dataType)
|
||||
|
||||
with transaction(program, "Apply VM class type fixes"):
|
||||
create_or_clear = update_signature(
|
||||
"1420:2040",
|
||||
slot_entry_ptr,
|
||||
[
|
||||
{"name": "slot_entry", "datatype": slot_entry_ptr},
|
||||
],
|
||||
)
|
||||
|
||||
acquire_slot = helpers["get_function"](program, "1420:167c")
|
||||
if acquire_slot is None:
|
||||
raise RuntimeError("Function not found: 1420:167c")
|
||||
acquire_slot_params = acquire_slot.getParameters()
|
||||
acquire_slot = update_signature(
|
||||
"1420:167c",
|
||||
slot_entry_ptr,
|
||||
[
|
||||
{"name": "this", "datatype": runtime_ptr},
|
||||
{"name": "slot_index", "datatype": word_type},
|
||||
{"keep": True, "parameter": acquire_slot_params[2]},
|
||||
],
|
||||
)
|
||||
|
||||
init_slot_owner_buffers = update_signature(
|
||||
"1420:1866",
|
||||
void_type,
|
||||
[
|
||||
{"name": "this", "datatype": runtime_ptr},
|
||||
{"name": "slot_index", "datatype": word_type},
|
||||
{"name": "slot_entry", "datatype": slot_entry_ptr},
|
||||
],
|
||||
)
|
||||
|
||||
create_runtime = update_signature(
|
||||
"1420:1499",
|
||||
dword_type,
|
||||
[
|
||||
{"name": "this", "datatype": runtime_ptr},
|
||||
{"name": "owner_type", "datatype": word_type},
|
||||
{"name": "owner_id", "datatype": word_type},
|
||||
],
|
||||
)
|
||||
|
||||
init_slots = update_signature(
|
||||
"1420:1536",
|
||||
void_type,
|
||||
[
|
||||
{"name": "this", "datatype": runtime_ptr},
|
||||
],
|
||||
)
|
||||
|
||||
release_slots = update_signature(
|
||||
"1420:1575",
|
||||
void_type,
|
||||
[
|
||||
{"name": "this", "datatype": runtime_ptr},
|
||||
],
|
||||
)
|
||||
|
||||
for function in [
|
||||
create_or_clear,
|
||||
acquire_slot,
|
||||
init_slot_owner_buffers,
|
||||
create_runtime,
|
||||
init_slots,
|
||||
release_slots,
|
||||
]:
|
||||
print("UPDATED", function.getEntryPoint(), function.getPrototypeString(True, True))
|
||||
33
scripts/_tmp_changer_ir_dump.py
Normal file
33
scripts/_tmp_changer_ir_dump.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from tools.poc_crusader_usecode_parser import parse_ir
|
||||
|
||||
|
||||
def dump_variant(label: str, extracted_root: str, class_name: str, slot: int) -> None:
|
||||
ir = parse_ir(class_name, slot, extracted_root)
|
||||
print(f"=== {label} {class_name} slot=0x{slot:02X} ===")
|
||||
print(json.dumps(ir["class"], indent=2))
|
||||
print(json.dumps(ir["event"], indent=2))
|
||||
for op in ir["ops"]:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"offset": op["offset"],
|
||||
"mnemonic": op["mnemonic"],
|
||||
"operands": op["operands"],
|
||||
"raw_bytes": op["raw_bytes"],
|
||||
},
|
||||
separators=(",", ":"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
root = Path(__file__).resolve().parent
|
||||
dump_variant("remorse", str(root / "USECODE" / "EUSECODE_extracted"), "CHANGER", 0x07)
|
||||
dump_variant("regret", str(root / "USECODE" / "REGRET" / "REGRET_USECODE_extracted"), "CHANGER", 0x07)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
21
scripts/_tmp_comment_broker_slot0c_callers.py
Normal file
21
scripts/_tmp_comment_broker_slot0c_callers.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
comments = {
|
||||
"1278:0616": (
|
||||
"Verified live caller of PresentationCallbackBroker slot +0x0c through the installed 0x4588 broker pointer. "
|
||||
"When the local vport state takes the fallback path and param_2 == 0, this function emits the broker callback instead of the normal direct graphics path."
|
||||
),
|
||||
"1320:1588": (
|
||||
"Verified live caller of PresentationCallbackBroker slot +0x0c through the installed 0x4588 broker pointer. "
|
||||
"Dispatch_ModalGump emits the broker callback before and after modal dispatch when the requested state pair differs from the current SuperVGA mode snapshot."
|
||||
),
|
||||
}
|
||||
|
||||
for address, comment in comments.items():
|
||||
print(bridge.set_decompiler_comment(address, comment))
|
||||
print(bridge.get_function_by_address(address))
|
||||
print()
|
||||
22
scripts/_tmp_comment_watch_controller_mapping.py
Normal file
22
scripts/_tmp_comment_watch_controller_mapping.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
comments = {
|
||||
"1180:0000": (
|
||||
"Live re-anchor for raw 0007:ba00 watch_entity_controller_create_global. "
|
||||
"The current live NE build exposes this family more concretely as Camera_Init / Camera_CreateProcess "
|
||||
"rather than under the older watch-entity-controller label."
|
||||
),
|
||||
"1180:0045": (
|
||||
"Live re-anchor for raw 0007:ba45 watch_entity_controller_create. "
|
||||
"This family now reads as the camera-process create path: allocates the process object, stores the global at 0x2bd8, and seeds the process-name row at 0x2be4."
|
||||
),
|
||||
}
|
||||
|
||||
for address, comment in comments.items():
|
||||
print(bridge.set_decompiler_comment(address, comment))
|
||||
print(bridge.get_function_by_address(address))
|
||||
print()
|
||||
24
scripts/_tmp_create_entity_vm_slot_entry_datatype.py
Normal file
24
scripts/_tmp_create_entity_vm_slot_entry_datatype.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from ghidra.program.model.data import CategoryPath, DataTypeConflictHandler, Structure, StructureDataType, WordDataType
|
||||
|
||||
|
||||
data_type_manager = program.getDataTypeManager()
|
||||
category_path = CategoryPath("/Remorse")
|
||||
slot_entry = data_type_manager.getDataType("/Remorse/EntityVmSlotEntry")
|
||||
|
||||
if slot_entry is None:
|
||||
slot_entry = StructureDataType(category_path, "EntityVmSlotEntry", 0x26)
|
||||
slot_entry = data_type_manager.addDataType(slot_entry, DataTypeConflictHandler.REPLACE_HANDLER)
|
||||
|
||||
if not isinstance(slot_entry, Structure):
|
||||
raise RuntimeError("/Remorse/EntityVmSlotEntry is not a structure: %s" % slot_entry.getClass().getName())
|
||||
|
||||
word_type = WordDataType.dataType.clone(data_type_manager)
|
||||
|
||||
slot_entry.replaceAtOffset(0x1e, word_type, 2, "owner_buffer_offset", None)
|
||||
slot_entry.replaceAtOffset(0x20, word_type, 2, "owner_buffer_segment", None)
|
||||
slot_entry.replaceAtOffset(0x22, word_type, 2, "chunk_state_offset", None)
|
||||
slot_entry.replaceAtOffset(0x24, word_type, 2, "chunk_state_segment", None)
|
||||
|
||||
print("DATATYPE", slot_entry.getPathName(), slot_entry.getLength())
|
||||
for component in slot_entry.getDefinedComponents():
|
||||
print("FIELD", hex(component.getOffset()), component.getFieldName(), component.getDataType().getDisplayName())
|
||||
32
scripts/_tmp_create_sprite_node_datatypes.py
Normal file
32
scripts/_tmp_create_sprite_node_datatypes.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
struct_result = bridge.create_or_update_struct(
|
||||
name="SpriteNodeBase",
|
||||
category_path="/Remorse",
|
||||
size=0x2b,
|
||||
fields=[
|
||||
{"offset": 25, "name": "child_or_next_farptr", "datatype": "undefined4", "comment": "Child-chain pointer pair used by traversal and accumulated-position helpers.", "confidence": "high"},
|
||||
{"offset": 33, "name": "local_x_offset", "datatype": "undefined2", "comment": "Summed by the live SpriteNode offset-accumulation helpers.", "confidence": "high"},
|
||||
{"offset": 35, "name": "local_y_offset", "datatype": "undefined2", "comment": "Summed by the live SpriteNode offset-accumulation helpers.", "confidence": "high"},
|
||||
{"offset": 41, "name": "dirty_flags", "datatype": "undefined2", "comment": "Checked by IsDirty and updated by MarkDirty.", "confidence": "high"},
|
||||
],
|
||||
)
|
||||
print("=== struct ===")
|
||||
print(struct_result)
|
||||
print()
|
||||
|
||||
vtable_result = bridge.create_or_update_struct(
|
||||
name="SpriteNodeVtable",
|
||||
category_path="/Remorse",
|
||||
size=40,
|
||||
fields=[
|
||||
{"offset": 20, "name": "event_handler_slot14", "datatype": "undefined4", "comment": "Verified DispatchEvent target for one event class.", "confidence": "high"},
|
||||
{"offset": 24, "name": "event_handler_slot18", "datatype": "undefined4", "comment": "Verified DispatchEvent target for one event class.", "confidence": "high"},
|
||||
{"offset": 32, "name": "event_handler_slot20", "datatype": "undefined4", "comment": "Verified DispatchEvent target for one event class.", "confidence": "high"},
|
||||
{"offset": 36, "name": "event_handler_slot24", "datatype": "undefined4", "comment": "Verified DispatchEvent target for one event class.", "confidence": "high"},
|
||||
],
|
||||
)
|
||||
print("=== vtable ===")
|
||||
print(vtable_result)
|
||||
8
scripts/_tmp_decompile_entity_dispatch_periodic.py
Normal file
8
scripts/_tmp_decompile_entity_dispatch_periodic.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["11e0:14fb", "11e0:1814", "11e0:1913", "11e0:19e6", "11e0:1a33"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["11e0:17a4", "11e0:17dc", "11e0:187e"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
22
scripts/_tmp_disasm_entity_dispatch_word_list.py
Normal file
22
scripts/_tmp_disasm_entity_dispatch_word_list.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
RANGES = [
|
||||
("11e0:1ff0", "11e0:2090"),
|
||||
("11e0:2190", "11e0:2260"),
|
||||
("11e0:2290", "11e0:22f0"),
|
||||
("11e0:2390", "11e0:23f0"),
|
||||
("11e0:24d0", "11e0:2520"),
|
||||
("11e0:2580", "11e0:25d0"),
|
||||
]
|
||||
|
||||
for start, end in RANGES:
|
||||
print(f"=== {start} .. {end} ===")
|
||||
result = bridge.disassemble_region(start, end)
|
||||
if isinstance(result, list):
|
||||
for line in result:
|
||||
print(line)
|
||||
else:
|
||||
print(result)
|
||||
print()
|
||||
70
scripts/_tmp_entity_vm_context_this_type.py
Normal file
70
scripts/_tmp_entity_vm_context_this_type.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
from java.util import ArrayList
|
||||
|
||||
from ghidra.program.model.listing import Function, ParameterImpl
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
|
||||
|
||||
TARGETS = [
|
||||
"1420:0eec",
|
||||
"1420:10b6",
|
||||
"1420:10da",
|
||||
"1420:1162",
|
||||
"1420:118f",
|
||||
"1420:1278",
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
dtm = program.getDataTypeManager()
|
||||
this_base = dtm.getDataType("/Remorse/EntityVmContext")
|
||||
if this_base is None:
|
||||
print("failed\tdatatype\t/Remorse/EntityVmContext not found")
|
||||
return
|
||||
|
||||
this_type = dtm.getPointer(this_base)
|
||||
ok_count = 0
|
||||
failed = []
|
||||
|
||||
for entry_text in TARGETS:
|
||||
func = helpers["get_function"](program, entry_text)
|
||||
if func is None:
|
||||
failed.append((entry_text, "function not found"))
|
||||
print("failed\t{}\tfunction not found".format(entry_text))
|
||||
continue
|
||||
|
||||
before = func.getPrototypeString(True, True)
|
||||
params = list(func.getParameters())
|
||||
replacements = ArrayList()
|
||||
replacements.add(ParameterImpl("this", this_type, program, SourceType.USER_DEFINED))
|
||||
for param in params[1:]:
|
||||
replacements.add(ParameterImpl(param, program))
|
||||
|
||||
try:
|
||||
transaction_id = program.startTransaction("Type EntityVmContext this")
|
||||
commit = False
|
||||
try:
|
||||
func.replaceParameters(
|
||||
replacements,
|
||||
Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS,
|
||||
True,
|
||||
SourceType.USER_DEFINED,
|
||||
)
|
||||
commit = True
|
||||
finally:
|
||||
program.endTransaction(transaction_id, commit)
|
||||
ok_count += 1
|
||||
print(
|
||||
"ok\t{}\tbefore={}\tafter={}".format(
|
||||
entry_text,
|
||||
before,
|
||||
func.getPrototypeString(True, True),
|
||||
)
|
||||
)
|
||||
except Exception as exc:
|
||||
failed.append((entry_text, str(exc)))
|
||||
print("failed\t{}\t{}".format(entry_text, exc))
|
||||
|
||||
print("summary\tok={}\tfailed={}".format(ok_count, len(failed)))
|
||||
|
||||
|
||||
main()
|
||||
5
scripts/_tmp_find_text_section.py
Normal file
5
scripts/_tmp_find_text_section.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import re
|
||||
from pathlib import Path
|
||||
xml = Path('exports/CRUSADER.EXE.xml').read_text(encoding='utf-8', errors='ignore')
|
||||
for m in re.finditer(r'<MEMORY_SECTION NAME="\.text" START_ADDR="([^"]+)" LENGTH="([^"]+)"', xml):
|
||||
print(m.group(0))
|
||||
48
scripts/_tmp_fix_entity_vm_runtime_create.py
Normal file
48
scripts/_tmp_fix_entity_vm_runtime_create.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
from java.util import ArrayList
|
||||
|
||||
from ghidra.program.model.data import DWordDataType, WordDataType
|
||||
from ghidra.program.model.listing import Function, ParameterImpl, ReturnParameterImpl, VariableStorage
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
|
||||
|
||||
def _clone(data_type):
|
||||
return data_type.clone(program.getDataTypeManager())
|
||||
|
||||
|
||||
function = helpers["get_function"](program, "1420:1499")
|
||||
if function is None:
|
||||
raise RuntimeError("Function 1420:1499 not found")
|
||||
|
||||
dword_type = _clone(DWordDataType.dataType)
|
||||
word_type = _clone(WordDataType.dataType)
|
||||
runtime_offset_param = ParameterImpl("this", word_type, VariableStorage(program, 4, 2), program, SourceType.USER_DEFINED)
|
||||
runtime_segment_param = ParameterImpl("runtime_segment", word_type, VariableStorage(program, 6, 2), program, SourceType.USER_DEFINED)
|
||||
owner_type_param = ParameterImpl("owner_type", word_type, VariableStorage(program, 8, 2), program, SourceType.USER_DEFINED)
|
||||
owner_id_param = ParameterImpl("owner_id", word_type, VariableStorage(program, 10, 2), program, SourceType.USER_DEFINED)
|
||||
|
||||
ax_reg = program.getRegister("AX")
|
||||
dx_reg = program.getRegister("DX")
|
||||
return_param = ReturnParameterImpl(dword_type, VariableStorage(program, ax_reg, dx_reg), program)
|
||||
|
||||
params = ArrayList()
|
||||
params.add(runtime_offset_param)
|
||||
params.add(runtime_segment_param)
|
||||
params.add(owner_type_param)
|
||||
params.add(owner_id_param)
|
||||
|
||||
function.updateFunction(
|
||||
function.getCallingConventionName(),
|
||||
return_param,
|
||||
params,
|
||||
Function.FunctionUpdateType.CUSTOM_STORAGE,
|
||||
True,
|
||||
SourceType.USER_DEFINED,
|
||||
)
|
||||
|
||||
function.setName("Create", SourceType.USER_DEFINED)
|
||||
function.setComment("Factory-style runtime creator. Uses split 16-bit this/segment parameters so Ghidra can represent the incoming far runtime pointer without corrupting decompilation.")
|
||||
|
||||
print("updated", function.getEntryPoint(), function.getSignature())
|
||||
for param in function.getParameters():
|
||||
print("param", param.getName(), param.getDataType().getDisplayName(), param.getVariableStorage())
|
||||
print("return", function.getReturnType().getDisplayName(), function.getReturn().getVariableStorage())
|
||||
50
scripts/_tmp_fix_entity_vm_runtime_create_typed.py
Normal file
50
scripts/_tmp_fix_entity_vm_runtime_create_typed.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from java.util import ArrayList
|
||||
|
||||
from ghidra.program.model.data import DWordDataType
|
||||
from ghidra.program.model.listing import Function, ParameterImpl, ReturnParameterImpl, VariableStorage
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
|
||||
from tools.pyghidra_crusader.common import transaction
|
||||
|
||||
|
||||
function = helpers["get_function"](program, "1420:1499")
|
||||
if function is None:
|
||||
raise RuntimeError("Function 1420:1499 not found")
|
||||
|
||||
runtime_type = program.getDataTypeManager().getDataType("/Remorse/EntityVmRuntime")
|
||||
if runtime_type is None:
|
||||
raise RuntimeError("Missing datatype /Remorse/EntityVmRuntime")
|
||||
|
||||
runtime_ptr = program.getDataTypeManager().getPointer(runtime_type)
|
||||
dword_type = DWordDataType.dataType.clone(program.getDataTypeManager())
|
||||
word_type = program.getDataTypeManager().getDataType("/undefined2")
|
||||
if word_type is None:
|
||||
raise RuntimeError("Missing datatype /undefined2")
|
||||
|
||||
this_param = ParameterImpl("this", runtime_ptr, VariableStorage(program, 4, 4), program, SourceType.USER_DEFINED)
|
||||
owner_type_param = ParameterImpl("owner_type", word_type, VariableStorage(program, 8, 2), program, SourceType.USER_DEFINED)
|
||||
owner_id_param = ParameterImpl("owner_id", word_type, VariableStorage(program, 10, 2), program, SourceType.USER_DEFINED)
|
||||
|
||||
ax_reg = program.getRegister("AX")
|
||||
dx_reg = program.getRegister("DX")
|
||||
return_param = ReturnParameterImpl(dword_type, VariableStorage(program, ax_reg, dx_reg), program)
|
||||
|
||||
params = ArrayList()
|
||||
params.add(this_param)
|
||||
params.add(owner_type_param)
|
||||
params.add(owner_id_param)
|
||||
|
||||
with transaction(program, "Repair EntityVmRuntime Create custom storage"):
|
||||
function.updateFunction(
|
||||
function.getCallingConventionName(),
|
||||
return_param,
|
||||
params,
|
||||
Function.FunctionUpdateType.CUSTOM_STORAGE,
|
||||
True,
|
||||
SourceType.USER_DEFINED,
|
||||
)
|
||||
|
||||
print("UPDATED", function.getEntryPoint(), function.getSignature())
|
||||
for param in function.getParameters():
|
||||
print("PARAM", param.getName(), param.getDataType().getDisplayName(), param.getVariableStorage())
|
||||
print("RETURN", function.getReturnType().getDisplayName(), function.getReturn().getVariableStorage())
|
||||
50
scripts/_tmp_fix_probably_some_alloc_1000_42e2.py
Normal file
50
scripts/_tmp_fix_probably_some_alloc_1000_42e2.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from java.util import ArrayList
|
||||
|
||||
from ghidra.program.model.data import DWordDataType, WordDataType
|
||||
from ghidra.program.model.listing import Function, ParameterImpl, ReturnParameterImpl, VariableStorage
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
|
||||
|
||||
def _clone(data_type):
|
||||
return data_type.clone(program.getDataTypeManager())
|
||||
|
||||
|
||||
function = helpers["get_function"](program, "1000:42e2")
|
||||
if function is None:
|
||||
raise RuntimeError("Function 1000:42e2 not found")
|
||||
|
||||
dword_type = _clone(DWordDataType.dataType)
|
||||
word_type = _clone(WordDataType.dataType)
|
||||
|
||||
param_1 = ParameterImpl("base_ptr", dword_type, VariableStorage(program, 4, 4), program, SourceType.USER_DEFINED)
|
||||
blocksize = ParameterImpl("blocksize", word_type, VariableStorage(program, 8, 2), program, SourceType.USER_DEFINED)
|
||||
nblocks = ParameterImpl("nblocks", word_type, VariableStorage(program, 10, 2), program, SourceType.USER_DEFINED)
|
||||
param_4 = ParameterImpl("param_4", word_type, VariableStorage(program, 12, 2), program, SourceType.USER_DEFINED)
|
||||
param_5 = ParameterImpl("param_5", word_type, VariableStorage(program, 14, 2), program, SourceType.USER_DEFINED)
|
||||
transform_func = ParameterImpl("transform_func", dword_type, VariableStorage(program, 16, 4), program, SourceType.USER_DEFINED)
|
||||
|
||||
ax_reg = program.getRegister("AX")
|
||||
dx_reg = program.getRegister("DX")
|
||||
return_param = ReturnParameterImpl(dword_type, VariableStorage(program, ax_reg, dx_reg), program)
|
||||
|
||||
params = ArrayList()
|
||||
params.add(param_1)
|
||||
params.add(blocksize)
|
||||
params.add(nblocks)
|
||||
params.add(param_4)
|
||||
params.add(param_5)
|
||||
params.add(transform_func)
|
||||
|
||||
function.updateFunction(
|
||||
function.getCallingConventionName(),
|
||||
return_param,
|
||||
params,
|
||||
Function.FunctionUpdateType.CUSTOM_STORAGE,
|
||||
True,
|
||||
SourceType.USER_DEFINED,
|
||||
)
|
||||
|
||||
print("updated", function.getEntryPoint(), function.getSignature())
|
||||
for parameter in function.getParameters():
|
||||
print("param", parameter.getName(), parameter.getDataType().getDisplayName(), parameter.getVariableStorage())
|
||||
print("return", function.getReturnType().getDisplayName(), function.getReturn().getVariableStorage())
|
||||
55
scripts/_tmp_inspect_entity_vm_runtime_create_frame.py
Normal file
55
scripts/_tmp_inspect_entity_vm_runtime_create_frame.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
def dump_function(address):
|
||||
function = helpers["get_function"](program, address)
|
||||
if function is None:
|
||||
raise RuntimeError("Function %s not found" % address)
|
||||
|
||||
frame = function.getStackFrame()
|
||||
symbol_table = program.getSymbolTable()
|
||||
print("FUNCTION", address)
|
||||
print("SIGNATURE", function.getSignature())
|
||||
print("CALLING_CONVENTION", function.getCallingConventionName())
|
||||
print("CUSTOM_STORAGE", function.hasCustomVariableStorage())
|
||||
print("STACK_PURGE_SIZE", function.getStackPurgeSize())
|
||||
print("STACK_PURGE_VALID", function.isStackPurgeSizeValid())
|
||||
print("HAS_NO_RETURN", function.hasNoReturn())
|
||||
print("ENTRY", function.getEntryPoint())
|
||||
print("BODY", function.getBody().getMinAddress(), function.getBody().getMaxAddress())
|
||||
print("PARENT_NAMESPACE", function.getParentNamespace())
|
||||
print("STACK_GROWS_NEGATIVE", frame.growsNegative())
|
||||
print("LOCAL_SIZE", frame.getLocalSize())
|
||||
print("PARAM_OFFSET", frame.getParameterOffset())
|
||||
print("RETURN_ADDR_OFFSET", frame.getReturnAddressOffset())
|
||||
print("FRAME_SIZE", frame.getFrameSize())
|
||||
print("PARAM_SIZE", frame.getParameterSize())
|
||||
print("SYMBOLS_AT_ENTRY")
|
||||
for symbol in symbol_table.getSymbols(function.getEntryPoint()):
|
||||
print(" ", symbol.getName(True), symbol.getSymbolType(), symbol.getSource())
|
||||
|
||||
print("PARAMETERS")
|
||||
for parameter in function.getParameters():
|
||||
print(" ", parameter.getName(), parameter.getDataType().getDisplayName(), parameter.getVariableStorage())
|
||||
|
||||
print("LOCALS")
|
||||
for variable in function.getLocalVariables():
|
||||
print(" ", variable.getName(), variable.getDataType().getDisplayName(), variable.getVariableStorage(), type(variable).__name__)
|
||||
|
||||
print("ALL_VARS")
|
||||
for variable in function.getAllVariables():
|
||||
print(" ", variable.getName(), variable.getDataType().getDisplayName(), variable.getVariableStorage(), type(variable).__name__)
|
||||
|
||||
print("STACK_REFERENCES")
|
||||
listing = program.getListing()
|
||||
instruction = listing.getInstructionAt(function.getEntryPoint())
|
||||
while instruction is not None and function.getBody().contains(instruction.getAddress()):
|
||||
operands = []
|
||||
for operand_index in range(instruction.getNumOperands()):
|
||||
references = instruction.getOperandReferences(operand_index)
|
||||
if references:
|
||||
operands.append((operand_index, [str(reference) for reference in references]))
|
||||
print(instruction.getAddress(), instruction, operands)
|
||||
instruction = instruction.getNext()
|
||||
print()
|
||||
|
||||
|
||||
dump_function("1420:1499")
|
||||
dump_function("1430:0000")
|
||||
197
scripts/_tmp_inspect_psx_banks.js
Normal file
197
scripts/_tmp_inspect_psx_banks.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
const fs = require("fs");
|
||||
|
||||
function readU32LE(buffer, offset) {
|
||||
return buffer.readUInt32LE(offset);
|
||||
}
|
||||
|
||||
function readU16LE(buffer, offset) {
|
||||
return buffer.readUInt16LE(offset);
|
||||
}
|
||||
|
||||
function parseLsetWdl(data) {
|
||||
const headerSize = readU32LE(data, 0);
|
||||
if (headerSize !== 0x34 || headerSize > data.length) {
|
||||
throw new Error(`unexpected header size ${headerSize}`);
|
||||
}
|
||||
|
||||
const headerWords = [];
|
||||
for (let offset = 0; offset < headerSize; offset += 4) {
|
||||
headerWords.push(readU32LE(data, offset));
|
||||
}
|
||||
|
||||
const audioSize = headerWords[1];
|
||||
const sectionSizes = [];
|
||||
for (let offset = 0x08; offset < 0x38; offset += 4) {
|
||||
sectionSizes.push(readU32LE(data, offset));
|
||||
}
|
||||
|
||||
const sections = [];
|
||||
let cursor = headerSize + audioSize;
|
||||
for (let index = 0; index < sectionSizes.length; index += 1) {
|
||||
const size = sectionSizes[index];
|
||||
if (size <= 0 || cursor + size > data.length) {
|
||||
break;
|
||||
}
|
||||
sections.push({
|
||||
index,
|
||||
name: `post_audio_section_${String(index).padStart(2, "0")}`,
|
||||
offset: cursor,
|
||||
size
|
||||
});
|
||||
cursor += size;
|
||||
}
|
||||
|
||||
return { headerWords, sections };
|
||||
}
|
||||
|
||||
function readSectionBytes(data, section) {
|
||||
return data.subarray(section.offset, section.offset + section.size);
|
||||
}
|
||||
|
||||
function parseTypedSection8(data, section) {
|
||||
const bytes = readSectionBytes(data, section);
|
||||
if (bytes.length < 8) {
|
||||
return null;
|
||||
}
|
||||
const recordCount = readU32LE(bytes, 0);
|
||||
const payloadBytes = readU32LE(bytes, 4);
|
||||
const headerOffset = 8 + payloadBytes;
|
||||
if (recordCount <= 0 || recordCount > 0x400) {
|
||||
return null;
|
||||
}
|
||||
if (payloadBytes < 0 || headerOffset + recordCount * 8 > bytes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let payloadCursor = 8;
|
||||
const records = [];
|
||||
for (let index = 0; index < recordCount; index += 1) {
|
||||
const descriptorOffset = headerOffset + index * 8;
|
||||
const blockSize = readU32LE(bytes, descriptorOffset);
|
||||
const typeId = readU32LE(bytes, descriptorOffset + 4);
|
||||
if (blockSize < 0 || payloadCursor + blockSize > headerOffset) {
|
||||
return null;
|
||||
}
|
||||
const payload = bytes.subarray(payloadCursor, payloadCursor + blockSize);
|
||||
const payloadDwords = [];
|
||||
for (let offset = 0; offset + 4 <= payload.length; offset += 4) {
|
||||
payloadDwords.push(readU32LE(payload, offset));
|
||||
}
|
||||
records.push({ index, typeId, blockSize, payloadDwords });
|
||||
payloadCursor += blockSize;
|
||||
}
|
||||
|
||||
return { section, recordCount, payloadBytes, records };
|
||||
}
|
||||
|
||||
function parseTypedSection16(data, section) {
|
||||
const bytes = readSectionBytes(data, section);
|
||||
if (bytes.length < 8) {
|
||||
return null;
|
||||
}
|
||||
const recordCount = readU32LE(bytes, 0);
|
||||
const payloadBytes = readU32LE(bytes, 4);
|
||||
const headerOffset = 8 + payloadBytes;
|
||||
if (recordCount <= 0 || recordCount > 0x400) {
|
||||
return null;
|
||||
}
|
||||
if (payloadBytes < 0 || headerOffset + recordCount * 16 > bytes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let payloadCursor = 8;
|
||||
const records = [];
|
||||
for (let index = 0; index < recordCount; index += 1) {
|
||||
const descriptorOffset = headerOffset + index * 16;
|
||||
const d4Size = readU32LE(bytes, descriptorOffset);
|
||||
const ccSize = readU32LE(bytes, descriptorOffset + 4);
|
||||
const d0Size = readU32LE(bytes, descriptorOffset + 8);
|
||||
const typeId = readU16LE(bytes, descriptorOffset + 12);
|
||||
const variantTypeId = readU16LE(bytes, descriptorOffset + 14);
|
||||
const ccPayload = bytes.subarray(payloadCursor, payloadCursor + ccSize);
|
||||
const d0Payload = bytes.subarray(payloadCursor + ccSize, payloadCursor + ccSize + d0Size);
|
||||
const d4Payload = bytes.subarray(payloadCursor + ccSize + d0Size, payloadCursor + ccSize + d0Size + d4Size);
|
||||
if (payloadCursor + ccSize + d0Size + d4Size > headerOffset) {
|
||||
return null;
|
||||
}
|
||||
records.push({
|
||||
index,
|
||||
typeId,
|
||||
variantTypeId,
|
||||
ccSize,
|
||||
d0Size,
|
||||
d4Size,
|
||||
ccDwords: readDwords(ccPayload),
|
||||
d0Dwords: readDwords(d0Payload),
|
||||
d4Dwords: readDwords(d4Payload)
|
||||
});
|
||||
payloadCursor += ccSize + d0Size + d4Size;
|
||||
}
|
||||
|
||||
return { section, recordCount, payloadBytes, records };
|
||||
}
|
||||
|
||||
function readDwords(payload) {
|
||||
const values = [];
|
||||
for (let offset = 0; offset + 4 <= payload.length; offset += 4) {
|
||||
values.push(readU32LE(payload, offset));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function main() {
|
||||
const filePath = process.argv[2];
|
||||
const wantedTypes = new Set(process.argv.slice(3).map((value) => Number.parseInt(value, 16)));
|
||||
const data = fs.readFileSync(filePath);
|
||||
const parsed = parseLsetWdl(data);
|
||||
|
||||
const section8 = parsed.sections
|
||||
.map((section) => parseTypedSection8(data, section))
|
||||
.filter(Boolean)
|
||||
.sort((left, right) => right.recordCount - left.recordCount || right.payloadBytes - left.payloadBytes)[0];
|
||||
const section16 = parsed.sections
|
||||
.map((section) => parseTypedSection16(data, section))
|
||||
.filter(Boolean)
|
||||
.sort((left, right) => right.recordCount - left.recordCount || right.payloadBytes - left.payloadBytes)[0];
|
||||
|
||||
const summary = {
|
||||
filePath,
|
||||
section8: section8 ? {
|
||||
section: section8.section.name,
|
||||
offset: `0x${section8.section.offset.toString(16)}`,
|
||||
size: `0x${section8.section.size.toString(16)}`,
|
||||
recordCount: section8.recordCount,
|
||||
wanted: section8.records
|
||||
.filter((record) => wantedTypes.has(record.typeId))
|
||||
.map((record) => ({
|
||||
index: record.index,
|
||||
typeId: `0x${record.typeId.toString(16)}`,
|
||||
blockSize: `0x${record.blockSize.toString(16)}`,
|
||||
payloadDwords: record.payloadDwords.map((value) => `0x${value.toString(16)}`)
|
||||
}))
|
||||
} : null,
|
||||
section16: section16 ? {
|
||||
section: section16.section.name,
|
||||
offset: `0x${section16.section.offset.toString(16)}`,
|
||||
size: `0x${section16.section.size.toString(16)}`,
|
||||
recordCount: section16.recordCount,
|
||||
wanted: section16.records
|
||||
.filter((record) => wantedTypes.has(record.typeId) || wantedTypes.has(record.variantTypeId))
|
||||
.map((record) => ({
|
||||
index: record.index,
|
||||
typeId: `0x${record.typeId.toString(16)}`,
|
||||
variantTypeId: `0x${record.variantTypeId.toString(16)}`,
|
||||
ccSize: `0x${record.ccSize.toString(16)}`,
|
||||
d0Size: `0x${record.d0Size.toString(16)}`,
|
||||
d4Size: `0x${record.d4Size.toString(16)}`,
|
||||
ccDwords: record.ccDwords.map((value) => `0x${value.toString(16)}`),
|
||||
d0Dwords: record.d0Dwords.map((value) => `0x${value.toString(16)}`),
|
||||
d4Dwords: record.d4Dwords.map((value) => `0x${value.toString(16)}`)
|
||||
}))
|
||||
} : null
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
}
|
||||
|
||||
main();
|
||||
35
scripts/_tmp_map248_observers.js
Normal file
35
scripts/_tmp_map248_observers.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const fs = require('fs');
|
||||
|
||||
const path = 'k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-248/b27ea0d8d2a1a391/scene.json';
|
||||
const scene = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
const items = scene.items.filter((item) => item.shapeDefId === 'shape:1232');
|
||||
|
||||
function qlo(item) {
|
||||
return item.quality & 0xff;
|
||||
}
|
||||
|
||||
function dist(a, b) {
|
||||
return Math.hypot(a.world.x - b.world.x, a.world.y - b.world.y);
|
||||
}
|
||||
|
||||
for (const item of items.filter((entry) => entry.npcPreview?.name === 'Observer')) {
|
||||
const pairs = items.filter((candidate) => candidate.id !== item.id && candidate.frame !== item.frame && qlo(candidate) === qlo(item) && dist(candidate, item) <= 128);
|
||||
console.log(JSON.stringify({
|
||||
id: item.id,
|
||||
src: item.mapSourceIndex,
|
||||
frame: item.frame,
|
||||
mapNum: item.mapNum,
|
||||
npcNum: item.npcNum,
|
||||
qlo: qlo(item),
|
||||
world: item.world,
|
||||
pairs: pairs.map((candidate) => ({
|
||||
id: candidate.id,
|
||||
src: candidate.mapSourceIndex,
|
||||
frame: candidate.frame,
|
||||
mapNum: candidate.mapNum,
|
||||
npcNum: candidate.npcNum,
|
||||
name: candidate.npcPreview?.name || null,
|
||||
qlo: qlo(candidate)
|
||||
}))
|
||||
}));
|
||||
}
|
||||
166
scripts/_tmp_parse_combat_dat.py
Normal file
166
scripts/_tmp_parse_combat_dat.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
MAGIC_DATA_OFF = 33000
|
||||
|
||||
|
||||
OPCODE_NAMES = {
|
||||
0x81: "set_unused_field53",
|
||||
0x82: "clear_unused_field53",
|
||||
0x84: "set_target_objid",
|
||||
0x85: "anim_walk",
|
||||
0x86: "anim_run",
|
||||
0x87: "anim_retreat",
|
||||
0x88: "turn_left_90",
|
||||
0x89: "turn_right_90",
|
||||
0x8A: "fire_small_if_clear",
|
||||
0x8B: "fire_large_if_clear",
|
||||
0x8C: "anim_stand",
|
||||
0x8D: "pathfind_home",
|
||||
0x8E: "pathfind_target",
|
||||
0x8F: "pathfind_midpoint",
|
||||
0x92: "anim_kneel_and_fire",
|
||||
0x93: "sleep_scaled",
|
||||
0x94: "loiter",
|
||||
0x95: "face_target",
|
||||
0x96: "set_activity",
|
||||
0x97: "switch_tactic",
|
||||
0x98: "teleport_home",
|
||||
0x99: "terminate",
|
||||
0x9A: "jump_if_dist_lt_481",
|
||||
0x9B: "jump_if_dist_gt_160",
|
||||
0x9C: "jump_if_shot_blocked",
|
||||
0x9D: "jump_if_shot_clear",
|
||||
0x9E: "random_jump_nonzero",
|
||||
0x9F: "loop_begin",
|
||||
0xA6: "pathfind_marker_frame",
|
||||
0xA7: "face_north",
|
||||
0xA8: "face_south",
|
||||
0xA9: "face_east",
|
||||
0xAA: "face_west",
|
||||
0xAB: "face_northeast",
|
||||
0xAC: "face_southwest",
|
||||
0xAD: "face_southeast",
|
||||
0xAE: "face_northwest",
|
||||
0xAF: "var_set",
|
||||
0xB0: "var_add",
|
||||
0xB1: "var_sub",
|
||||
0xB2: "var_mul",
|
||||
0xB3: "var_div",
|
||||
0xB4: "var_store_curdir",
|
||||
0xB5: "set_dir_raw",
|
||||
0xB6: "var_store_curdir_again",
|
||||
0xB7: "anim_kneeling_retreat",
|
||||
0xB8: "anim_kneeling_advance",
|
||||
0xB9: "anim_kneeling_slow_retreat",
|
||||
0xC0: "jump",
|
||||
0xC1: "loop_end",
|
||||
0xFF: "flip_to_block1_restart",
|
||||
}
|
||||
|
||||
|
||||
def format_word(value: int) -> str:
|
||||
if value >= MAGIC_DATA_OFF:
|
||||
return f"var[{value - MAGIC_DATA_OFF}]"
|
||||
return f"0x{value:04x} ({value})"
|
||||
|
||||
|
||||
def decode_block(block: bytes, start_offset: int) -> list[str]:
|
||||
pc = 0
|
||||
lines: list[str] = []
|
||||
|
||||
def need_word(use_data: bool) -> int:
|
||||
nonlocal pc
|
||||
value = struct.unpack_from("<H", block, pc)[0]
|
||||
pc += 2
|
||||
return value
|
||||
|
||||
while pc < len(block):
|
||||
opcode_offset = start_offset + pc
|
||||
opcode = block[pc]
|
||||
pc += 1
|
||||
opname = OPCODE_NAMES.get(opcode, f"opcode_{opcode:02x}")
|
||||
operands: list[str] = []
|
||||
|
||||
if opcode in {0x81, 0x84, 0x93, 0x94, 0x96, 0x97, 0x9A, 0x9B, 0x9C, 0x9D, 0xB5, 0xC0, 0x9F}:
|
||||
operands.append(format_word(need_word(True)))
|
||||
elif opcode == 0x9E:
|
||||
operands.append(format_word(need_word(True)))
|
||||
operands.append(format_word(need_word(True)))
|
||||
elif opcode == 0xA6:
|
||||
operands.append(f"frame={format_word(need_word(True))}")
|
||||
elif opcode in {0xAF}:
|
||||
operands.append(format_word(need_word(True)))
|
||||
operands.append(format_word(need_word(False)))
|
||||
elif opcode in {0xB0, 0xB1, 0xB2, 0xB3}:
|
||||
operands.append(format_word(need_word(False)))
|
||||
operands.append(format_word(need_word(True)))
|
||||
elif opcode in {0xB4, 0xB6}:
|
||||
operands.append(format_word(need_word(False)))
|
||||
|
||||
text = opname
|
||||
if operands:
|
||||
text += " " + ", ".join(operands)
|
||||
lines.append(f"0x{opcode_offset:04x}: {text}")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def read_u32(data: bytes, offset: int) -> int:
|
||||
return struct.unpack_from("<I", data, offset)[0]
|
||||
|
||||
|
||||
def read_u16(data: bytes, offset: int) -> int:
|
||||
return struct.unpack_from("<H", data, offset)[0]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
path = Path("STATIC/COMBAT.DAT")
|
||||
data = path.read_bytes()
|
||||
|
||||
print(f"file={path} size={len(data)}")
|
||||
print(f"header_magic_fill={data[:0x56].hex()[:32]}...")
|
||||
|
||||
entries = []
|
||||
for index in range(64):
|
||||
off = 0x80 + index * 8
|
||||
rec_off = read_u32(data, off)
|
||||
rec_len = read_u32(data, off + 4)
|
||||
if rec_off == 0 and rec_len == 0:
|
||||
continue
|
||||
entries.append((index, rec_off, rec_len))
|
||||
|
||||
print(f"entry_count={len(entries)}")
|
||||
for index, rec_off, rec_len in entries:
|
||||
rec = data[rec_off:rec_off + rec_len]
|
||||
name = rec[:16].split(b"\0", 1)[0].decode("ascii")
|
||||
block_offsets = [read_u16(rec, 16 + i * 2) for i in range(4)]
|
||||
valid_blocks = [value for value in block_offsets if value and value < rec_len]
|
||||
print(
|
||||
f"[{index:02d}] off=0x{rec_off:04x} len=0x{rec_len:04x}"
|
||||
f" name={name:<16} block_offsets={block_offsets}"
|
||||
f" body_len={rec_len - min(valid_blocks) if valid_blocks else 0}"
|
||||
)
|
||||
|
||||
for block_no, block_off in enumerate(block_offsets[:2]):
|
||||
if not block_off or block_off >= rec_len:
|
||||
continue
|
||||
next_offsets = sorted(value for value in block_offsets[:2] if value > block_off and value < rec_len)
|
||||
block_end = next_offsets[0] if next_offsets else rec_len
|
||||
block = rec[block_off:block_end]
|
||||
print(
|
||||
f" block{block_no}: start=0x{block_off:04x} end=0x{block_end:04x}"
|
||||
f" size={block_end - block_off:02d} bytes={block.hex(' ')}"
|
||||
)
|
||||
for line in decode_block(block, block_off):
|
||||
print(f" {line}")
|
||||
|
||||
trailing_offsets = block_offsets[2:]
|
||||
print(f" extra_offsets_unused={trailing_offsets}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
scripts/_tmp_probe_broker_slot08.py
Normal file
11
scripts/_tmp_probe_broker_slot08.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
for address in ["1288:0fd1", "1278:06b1", "1320:15e9"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_cache_backend_object.py
Normal file
9
scripts/_tmp_probe_cache_backend_object.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["0009:5600", "0009:5601"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
23
scripts/_tmp_probe_cache_backend_object_callee.py
Normal file
23
scripts/_tmp_probe_cache_backend_object_callee.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["1250:0000", "1250:0001"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
|
||||
for address in ["1250:026c", "1250:0749"]:
|
||||
print(f"=== helper {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print(bridge.get_xrefs_to(address))
|
||||
print()
|
||||
|
||||
for address in ["1250:0910"]:
|
||||
print(f"=== helper {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print(bridge.get_xrefs_to(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_cache_backend_object_reanchor.py
Normal file
9
scripts/_tmp_probe_cache_backend_object_reanchor.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["12d8:03b4", "12d8:03d6", "12d8:0451"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_debugger_formatter_helpers.py
Normal file
9
scripts/_tmp_probe_debugger_formatter_helpers.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["13a0:0291", "13a0:045c"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
30
scripts/_tmp_probe_entity_dispatch_entry_live.py
Normal file
30
scripts/_tmp_probe_entity_dispatch_entry_live.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
ADDRESSES = [
|
||||
"11e0:14fb",
|
||||
"11e0:1814",
|
||||
"11e0:1913",
|
||||
"11e0:19e6",
|
||||
"11e0:1a33",
|
||||
"11e0:2000",
|
||||
"11e0:21a3",
|
||||
"11e0:21ec",
|
||||
"11e0:2238",
|
||||
"11e0:22ab",
|
||||
"11e0:23af",
|
||||
"11e0:24ea",
|
||||
"11e0:251b",
|
||||
"11e0:25a1",
|
||||
]
|
||||
|
||||
for address in ADDRESSES:
|
||||
print(f"=== {address} ===")
|
||||
try:
|
||||
print(bridge.get_function_containing(address))
|
||||
except Exception as exc:
|
||||
print(f"get_function_containing failed: {exc}")
|
||||
print()
|
||||
9
scripts/_tmp_probe_entity_dispatch_word_list_head.py
Normal file
9
scripts/_tmp_probe_entity_dispatch_word_list_head.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["11e8:0000", "11e8:01a3"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_entity_dispatch_word_list_reanchor.py
Normal file
9
scripts/_tmp_probe_entity_dispatch_word_list_reanchor.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["11e8:01fa", "11e8:0246", "11e8:02b9", "11e8:03bd", "11e8:04f8", "11e8:0529", "11e8:05af"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
38
scripts/_tmp_probe_owner_resource_live.py
Normal file
38
scripts/_tmp_probe_owner_resource_live.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
for address in ["1430:0000", "1430:00fd"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
|
||||
for address in ["1220:0000", "1478:67b6", "1478:1228"]:
|
||||
print(f"=== symbol {address} ===")
|
||||
if hasattr(bridge, "get_symbol_at"):
|
||||
print(bridge.get_symbol_at(address))
|
||||
else:
|
||||
print(bridge.get_function_by_address(address))
|
||||
print()
|
||||
|
||||
for address in ["1220:0000"]:
|
||||
print(f"=== decompile {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
|
||||
for address in ["1420:1866", "1420:19fd"]:
|
||||
print(f"=== runtime {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
|
||||
for address in ["1430:014c", "1430:0195"]:
|
||||
print(f"=== owner helper {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print(bridge.get_xrefs_to(address))
|
||||
print()
|
||||
132
scripts/_tmp_probe_psx_section16.js
Normal file
132
scripts/_tmp_probe_psx_section16.js
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
const fs = require("fs");
|
||||
|
||||
function readU32LE(buffer, offset) {
|
||||
return buffer.readUInt32LE(offset);
|
||||
}
|
||||
|
||||
function readU16LE(buffer, offset) {
|
||||
return buffer.readUInt16LE(offset);
|
||||
}
|
||||
|
||||
function parseLsetWdl(data) {
|
||||
const headerSize = readU32LE(data, 0);
|
||||
if (headerSize !== 0x34 || headerSize > data.length) {
|
||||
throw new Error(`unexpected header size ${headerSize}`);
|
||||
}
|
||||
|
||||
const headerWords = [];
|
||||
for (let offset = 0; offset < headerSize; offset += 4) {
|
||||
headerWords.push(readU32LE(data, offset));
|
||||
}
|
||||
|
||||
const audioSize = headerWords[1];
|
||||
const sectionSizes = [];
|
||||
for (let offset = 0x08; offset < 0x38; offset += 4) {
|
||||
sectionSizes.push(readU32LE(data, offset));
|
||||
}
|
||||
|
||||
const sections = [];
|
||||
let cursor = headerSize + audioSize;
|
||||
for (let index = 0; index < sectionSizes.length; index += 1) {
|
||||
const size = sectionSizes[index];
|
||||
if (size <= 0 || cursor + size > data.length) {
|
||||
break;
|
||||
}
|
||||
sections.push({
|
||||
index,
|
||||
name: `post_audio_section_${String(index).padStart(2, "0")}`,
|
||||
offset: cursor,
|
||||
size
|
||||
});
|
||||
cursor += size;
|
||||
}
|
||||
|
||||
return { sections };
|
||||
}
|
||||
|
||||
function parseTypedSection16(data, section, startOffset) {
|
||||
const bytes = data.subarray(section.offset + startOffset, section.offset + section.size);
|
||||
if (bytes.length < 8) {
|
||||
return null;
|
||||
}
|
||||
const recordCount = readU32LE(bytes, 0);
|
||||
const payloadBytes = readU32LE(bytes, 4);
|
||||
const headerOffset = 8 + payloadBytes;
|
||||
if (recordCount <= 0 || recordCount > 0x400) {
|
||||
return null;
|
||||
}
|
||||
if (payloadBytes < 0 || headerOffset + recordCount * 16 > bytes.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let payloadCursor = 8;
|
||||
const records = [];
|
||||
for (let index = 0; index < recordCount; index += 1) {
|
||||
const descriptorOffset = headerOffset + index * 16;
|
||||
const d4Size = readU32LE(bytes, descriptorOffset);
|
||||
const ccSize = readU32LE(bytes, descriptorOffset + 4);
|
||||
const d0Size = readU32LE(bytes, descriptorOffset + 8);
|
||||
const typeId = readU16LE(bytes, descriptorOffset + 12);
|
||||
const variantTypeId = readU16LE(bytes, descriptorOffset + 14);
|
||||
const endOffset = payloadCursor + ccSize + d0Size + d4Size;
|
||||
if (endOffset > headerOffset) {
|
||||
return null;
|
||||
}
|
||||
records.push({ index, typeId, variantTypeId, ccSize, d0Size, d4Size, payloadCursor });
|
||||
payloadCursor = endOffset;
|
||||
}
|
||||
|
||||
return {
|
||||
sectionName: section.name,
|
||||
startOffset,
|
||||
recordCount,
|
||||
payloadBytes,
|
||||
headerOffset,
|
||||
records
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeCandidate(candidate, wantedTypes) {
|
||||
return {
|
||||
sectionName: candidate.sectionName,
|
||||
startOffset: `0x${candidate.startOffset.toString(16)}`,
|
||||
recordCount: candidate.recordCount,
|
||||
payloadBytes: `0x${candidate.payloadBytes.toString(16)}`,
|
||||
wanted: candidate.records
|
||||
.filter((record) => wantedTypes.has(record.typeId) || wantedTypes.has(record.variantTypeId))
|
||||
.map((record) => ({
|
||||
index: record.index,
|
||||
typeId: `0x${record.typeId.toString(16)}`,
|
||||
variantTypeId: `0x${record.variantTypeId.toString(16)}`,
|
||||
ccSize: `0x${record.ccSize.toString(16)}`,
|
||||
d0Size: `0x${record.d0Size.toString(16)}`,
|
||||
d4Size: `0x${record.d4Size.toString(16)}`,
|
||||
payloadCursor: `0x${record.payloadCursor.toString(16)}`
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const filePath = process.argv[2];
|
||||
const wantedTypes = new Set(process.argv.slice(3).map((value) => Number.parseInt(value, 16)));
|
||||
const data = fs.readFileSync(filePath);
|
||||
const parsed = parseLsetWdl(data);
|
||||
const candidates = [];
|
||||
|
||||
for (const section of parsed.sections) {
|
||||
for (let startOffset = 0; startOffset < Math.min(section.size, 0x800); startOffset += 4) {
|
||||
const candidate = parseTypedSection16(data, section, startOffset);
|
||||
if (!candidate) {
|
||||
continue;
|
||||
}
|
||||
const summary = summarizeCandidate(candidate, wantedTypes);
|
||||
if (summary.wanted.length > 0) {
|
||||
candidates.push(summary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(candidates, null, 2));
|
||||
}
|
||||
|
||||
main();
|
||||
111
scripts/_tmp_probe_sections.js
Normal file
111
scripts/_tmp_probe_sections.js
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
const fs = require("fs");
|
||||
|
||||
const ALLOWED_U5 = new Set([0x20, 0x22, 0x30]);
|
||||
|
||||
function readU32LE(buffer, offset) {
|
||||
return buffer.readUInt32LE(offset);
|
||||
}
|
||||
|
||||
function readU16LE(buffer, offset) {
|
||||
return buffer.readUInt16LE(offset);
|
||||
}
|
||||
|
||||
function isStructuredCandidate(record) {
|
||||
if (record[0] >= 0x200) {
|
||||
return false;
|
||||
}
|
||||
if (record[1] === 0 && record[2] === 0) {
|
||||
return false;
|
||||
}
|
||||
if (record[1] >= 0x4000 || record[2] >= 0x4000) {
|
||||
return false;
|
||||
}
|
||||
if (record[3] > 0x20 || record[4] > 0x04) {
|
||||
return false;
|
||||
}
|
||||
return ALLOWED_U5.has(record[5]);
|
||||
}
|
||||
|
||||
function probeFile(filePath) {
|
||||
const data = fs.readFileSync(filePath);
|
||||
const headerSize = readU32LE(data, 0);
|
||||
const audioSize = readU32LE(data, 4);
|
||||
const sectionSizes = [];
|
||||
for (let offset = 8; offset < 0x38; offset += 4) {
|
||||
sectionSizes.push(readU32LE(data, offset));
|
||||
}
|
||||
|
||||
let cursor = headerSize + audioSize;
|
||||
const sections = [];
|
||||
for (let index = 0; index < sectionSizes.length; index += 1) {
|
||||
const size = sectionSizes[index];
|
||||
const start = cursor;
|
||||
const end = start + size;
|
||||
const bytes = data.subarray(start, end);
|
||||
cursor = end;
|
||||
|
||||
let rowCount = null;
|
||||
let rootHits = 0;
|
||||
let bulkHits = 0;
|
||||
|
||||
if (bytes.length >= 4) {
|
||||
rowCount = readU32LE(bytes, 0);
|
||||
for (let rowIndex = 0; rowIndex < Math.min(rowCount, 5000); rowIndex += 1) {
|
||||
const base = 4 + rowIndex * 24;
|
||||
if (base + 24 > bytes.length) {
|
||||
break;
|
||||
}
|
||||
const words = [];
|
||||
for (let wordIndex = 0; wordIndex < 12; wordIndex += 1) {
|
||||
words.push(readU16LE(bytes, base + wordIndex * 2));
|
||||
}
|
||||
const left = [words[4], words[5], words[0], words[1], words[2], words[3]];
|
||||
const right = [words[10], words[11], words[6], words[7], words[8], words[9]];
|
||||
if (isStructuredCandidate(left)) {
|
||||
rootHits += 1;
|
||||
}
|
||||
if (isStructuredCandidate(right)) {
|
||||
rootHits += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const usableSize = bytes.length - (bytes.length % 24);
|
||||
for (let offset = 0; offset < usableSize; offset += 24) {
|
||||
for (const sideOffset of [0, 12]) {
|
||||
const base = offset + sideOffset;
|
||||
const record = [
|
||||
readU16LE(bytes, base),
|
||||
readU16LE(bytes, base + 2),
|
||||
readU16LE(bytes, base + 4),
|
||||
readU16LE(bytes, base + 6),
|
||||
readU16LE(bytes, base + 8),
|
||||
readU16LE(bytes, base + 10)
|
||||
];
|
||||
if (isStructuredCandidate(record)) {
|
||||
bulkHits += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections.push({
|
||||
index,
|
||||
start,
|
||||
size,
|
||||
rowCount,
|
||||
rootHits,
|
||||
bulkHits
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
filePath,
|
||||
headerSize,
|
||||
audioSize,
|
||||
sections
|
||||
};
|
||||
}
|
||||
|
||||
for (const filePath of process.argv.slice(2)) {
|
||||
console.log(JSON.stringify(probeFile(filePath), null, 2));
|
||||
}
|
||||
9
scripts/_tmp_probe_sprite_get_or_traverse_candidate.py
Normal file
9
scripts/_tmp_probe_sprite_get_or_traverse_candidate.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["1320:0b18", "1320:0b29", "1320:0b44"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_sprite_get_or_traverse_lead.py
Normal file
9
scripts/_tmp_probe_sprite_get_or_traverse_lead.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["1360:0955", "1360:0790", "1360:10d8"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_sprite_node_followups.py
Normal file
9
scripts/_tmp_probe_sprite_node_followups.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["1360:0580", "1360:05a6", "1360:0cb2", "1360:12ee"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_sprite_node_live.py
Normal file
9
scripts/_tmp_probe_sprite_node_live.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["000a:b988", "000b:326e", "000b:3380", "000b:33a6", "000b:3ab2", "000b:40ee"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
9
scripts/_tmp_probe_sprite_node_reanchor.py
Normal file
9
scripts/_tmp_probe_sprite_node_reanchor.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import sys
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
for address in ["1360:0380", "1360:03af", "1360:0483", "1360:0cd7", "1360:0d79"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
11
scripts/_tmp_probe_tier2_candidates.py
Normal file
11
scripts/_tmp_probe_tier2_candidates.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
for address in ["1180:000f", "1180:00a8", "1130:3038", "1130:31cc", "1130:3269", "12d0:0516", "12d0:0668"]:
|
||||
print(f"=== {address} ===")
|
||||
print(bridge.get_function_containing(address))
|
||||
print(bridge.decompile_function_by_address(address))
|
||||
print()
|
||||
314
scripts/_tmp_psx_gpu_search.py
Normal file
314
scripts/_tmp_psx_gpu_search.py
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import bisect
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(r"k:/ghidra/Crusader_Decomp")
|
||||
BUNDLE_DIR = ROOT / "out/psx_wdl/L0/sprite_bundles/bundle_000A1B04"
|
||||
FRAME_PATH = BUNDLE_DIR / "frame_000.bin"
|
||||
BUNDLE_JSON = BUNDLE_DIR / "bundle.json"
|
||||
GPU_PATH = ROOT / "binary/Crusader - No Remorse (USA) GPU RAM.bin"
|
||||
L0_WDL_PATH = Path(r"e:/emu/psx/Crusader - No Remorse/LSET1/L0.WDL")
|
||||
ROW_BYTES = 2048
|
||||
GPU_ROWS = 512
|
||||
TOP_N = 10
|
||||
FRAMEBUFFER_WIDTH = 320
|
||||
FRAMEBUFFER_HEIGHT = 240
|
||||
MATCH_TOP_N = 12
|
||||
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from psx_extract_wdl import colorize_indexed_pixels, psx_555_to_rgba, write_overview_grid, write_psx_16bpp_png
|
||||
|
||||
|
||||
def find_all(haystack: bytes, needle: bytes):
|
||||
start = 0
|
||||
while True:
|
||||
index = haystack.find(needle, start)
|
||||
if index < 0:
|
||||
return
|
||||
yield index
|
||||
start = index + 1
|
||||
|
||||
|
||||
def count_row_mismatches(left: bytes, right: bytes) -> int:
|
||||
return sum(a != b for a, b in zip(left, right))
|
||||
|
||||
|
||||
def is_exact_at(rows: list[bytes], candidate_rows: list[bytes], x: int, y: int, width: int) -> bool:
|
||||
for dy, src in enumerate(candidate_rows):
|
||||
if rows[y + dy][x : x + width] != src:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def near_score(
|
||||
rows: list[bytes],
|
||||
candidate_rows: list[bytes],
|
||||
x: int,
|
||||
y: int,
|
||||
width: int,
|
||||
cutoff: int | None,
|
||||
) -> tuple[int, list[int], bool]:
|
||||
total = 0
|
||||
row_mismatches: list[int] = []
|
||||
for dy, src in enumerate(candidate_rows):
|
||||
seg = rows[y + dy][x : x + width]
|
||||
mismatch = 0 if seg == src else count_row_mismatches(seg, src)
|
||||
total += mismatch
|
||||
row_mismatches.append(mismatch)
|
||||
if cutoff is not None and total > cutoff:
|
||||
return total, row_mismatches, True
|
||||
return total, row_mismatches, False
|
||||
|
||||
|
||||
def rgba_from_words(words: tuple[int, ...]) -> list[tuple[int, int, int]]:
|
||||
return [psx_555_to_rgba(word)[:3] for word in words]
|
||||
|
||||
|
||||
def candidate_match_score(
|
||||
framebuffer_rgb: list[tuple[int, int, int]],
|
||||
framebuffer_width: int,
|
||||
framebuffer_height: int,
|
||||
rgba: bytes,
|
||||
width: int,
|
||||
height: int,
|
||||
guess_x: int,
|
||||
guess_y: int,
|
||||
radius: int = 12,
|
||||
step: int = 2,
|
||||
) -> tuple[int, int, int]:
|
||||
best_score: int | None = None
|
||||
best_x = -1
|
||||
best_y = -1
|
||||
x_min = max(0, guess_x - radius)
|
||||
x_max = min(framebuffer_width - width, guess_x + radius)
|
||||
y_min = max(0, guess_y - radius)
|
||||
y_max = min(framebuffer_height - height, guess_y + radius)
|
||||
for y in range(y_min, y_max + 1):
|
||||
for x in range(x_min, x_max + 1):
|
||||
score = 0
|
||||
samples = 0
|
||||
for sy in range(0, height, step):
|
||||
screen_row = (y + sy) * framebuffer_width
|
||||
sprite_row = sy * width * 4
|
||||
for sx in range(0, width, step):
|
||||
src = sprite_row + sx * 4
|
||||
if rgba[src + 3] == 0:
|
||||
continue
|
||||
screen_r, screen_g, screen_b = framebuffer_rgb[screen_row + x + sx]
|
||||
red = rgba[src]
|
||||
green = rgba[src + 1]
|
||||
blue = rgba[src + 2]
|
||||
score += abs(screen_r - red) + abs(screen_g - green) + abs(screen_b - blue)
|
||||
samples += 1
|
||||
if samples == 0:
|
||||
continue
|
||||
normalized = score // samples
|
||||
if best_score is None or normalized < best_score:
|
||||
best_score = normalized
|
||||
best_x = x
|
||||
best_y = y
|
||||
if best_score is None:
|
||||
return 1 << 30, -1, -1
|
||||
return best_score, best_x, best_y
|
||||
|
||||
|
||||
def main() -> None:
|
||||
bundle = json.loads(BUNDLE_JSON.read_text(encoding="ascii"))
|
||||
frame_meta = next(frame for frame in bundle["exported_frames"] if frame["index"] == 0)
|
||||
width = frame_meta["width"]
|
||||
height = frame_meta["height"]
|
||||
mode = bundle["mode"]
|
||||
frame = FRAME_PATH.read_bytes()
|
||||
expected = width * height
|
||||
if len(frame) != expected:
|
||||
raise SystemExit(f"frame byte size mismatch: got {len(frame)}, expected {expected}")
|
||||
if mode != 1:
|
||||
raise SystemExit(f"unexpected mode {mode}, expected 1 for 8bpp")
|
||||
|
||||
gpu = GPU_PATH.read_bytes()
|
||||
if len(gpu) != ROW_BYTES * GPU_ROWS:
|
||||
raise SystemExit(f"unexpected GPU dump size {len(gpu)}")
|
||||
|
||||
l0_data = L0_WDL_PATH.read_bytes()
|
||||
palette_offset = int.from_bytes(l0_data[8:12], "little")
|
||||
palette_size = int.from_bytes(l0_data[12:16], "little")
|
||||
if palette_size != 0x1000:
|
||||
raise SystemExit(f"unexpected palette size 0x{palette_size:X}")
|
||||
palette_blob = l0_data[palette_offset : palette_offset + palette_size]
|
||||
palettes_256 = [palette_blob[offset : offset + 0x200] for offset in range(0, len(palette_blob), 0x200)]
|
||||
|
||||
rows = [gpu[y * ROW_BYTES : (y + 1) * ROW_BYTES] for y in range(GPU_ROWS)]
|
||||
frame_rows = [frame[i * width : (i + 1) * width] for i in range(height)]
|
||||
flip_rows = [row[::-1] for row in frame_rows]
|
||||
|
||||
normal_hits: list[tuple[int, int]] = []
|
||||
flipped_hits: list[tuple[int, int]] = []
|
||||
for y in range(GPU_ROWS - height + 1):
|
||||
row = rows[y]
|
||||
normal_hits.extend((x, y) for x in find_all(row, frame_rows[0]))
|
||||
flipped_hits.extend((x, y) for x in find_all(row, flip_rows[0]))
|
||||
|
||||
exact_normal = [(x, y) for x, y in normal_hits if is_exact_at(rows, frame_rows, x, y, width)]
|
||||
exact_flipped = [(x, y) for x, y in flipped_hits if is_exact_at(rows, flip_rows, x, y, width)]
|
||||
|
||||
print(f"bundle_offset=0x{bundle['offset']:X} mode={mode} frame_count={bundle['frame_count']}")
|
||||
print(
|
||||
"frame0 "
|
||||
f"width={width} height={height} origin=({frame_meta['origin_x']},{frame_meta['origin_y']}) "
|
||||
f"data_start={frame_meta['data_start']} consumed={frame_meta['consumed']}"
|
||||
)
|
||||
print(f"frame_bytes={len(frame)} gpu_dump_bytes={len(gpu)}")
|
||||
print(f"row0_hits normal={len(normal_hits)} flipped={len(flipped_hits)}")
|
||||
print(f"exact_full_matches_normal={len(exact_normal)}")
|
||||
for x, y in exact_normal[:TOP_N]:
|
||||
print(f" normal x={x} y={y} page=({x // 256},{y // 256}) in_page=({x % 256},{y % 256})")
|
||||
print(f"exact_full_matches_flipped={len(exact_flipped)}")
|
||||
for x, y in exact_flipped[:TOP_N]:
|
||||
print(f" flipped x={x} y={y} page=({x // 256},{y // 256}) in_page=({x % 256},{y % 256})")
|
||||
|
||||
live_palette_entries: list[dict[str, object]] = []
|
||||
live_palette_labels: list[str] = []
|
||||
for clut_row in range(8):
|
||||
y = 0xF0 + clut_row
|
||||
row_words = struct.unpack("<1024H", rows[y])
|
||||
for column in range(16):
|
||||
x = column * 16
|
||||
palette = list(row_words[x : x + 256])
|
||||
rgba = colorize_indexed_pixels(frame, width, height, mode, palette)
|
||||
live_palette_entries.append(
|
||||
{
|
||||
"width": width,
|
||||
"height": height,
|
||||
"rgba": rgba,
|
||||
}
|
||||
)
|
||||
live_palette_labels.append(f"index={clut_row * 16 + column} x={x} y={y}")
|
||||
|
||||
atlas_path = BUNDLE_DIR / "live_vram_clut_atlas.png"
|
||||
labels_path = BUNDLE_DIR / "live_vram_clut_atlas.txt"
|
||||
write_overview_grid(atlas_path, live_palette_entries, columns=16)
|
||||
labels_path.write_text("\n".join(live_palette_labels) + "\n", encoding="ascii")
|
||||
print(f"live_vram_clut_atlas={atlas_path}")
|
||||
print(f"live_vram_clut_labels={labels_path}")
|
||||
|
||||
framebuffer_path = ROOT / "binary/psx_framebuffer_left.png"
|
||||
framebuffer_crop_path = ROOT / "binary/psx_framebuffer_console_crop.png"
|
||||
print(f"raw_palette_blocks_256={len(palettes_256)}")
|
||||
for palette_index, palette in enumerate(palettes_256):
|
||||
palette_hits: list[tuple[int, int]] = []
|
||||
for y in range(240, 256):
|
||||
row = rows[y]
|
||||
start = 0
|
||||
while True:
|
||||
x = row.find(palette, start)
|
||||
if x < 0:
|
||||
break
|
||||
palette_hits.append((x, y))
|
||||
start = x + 1
|
||||
print(f" palette_{palette_index}_hits={len(palette_hits)}")
|
||||
for x, y in palette_hits[:TOP_N]:
|
||||
print(f" palette_{palette_index} x={x} y={y} row_band={y - 240}")
|
||||
|
||||
framebuffer_bytes = bytearray(FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT * 2)
|
||||
for y in range(FRAMEBUFFER_HEIGHT):
|
||||
src_row = rows[y]
|
||||
start = y * FRAMEBUFFER_WIDTH * 2
|
||||
framebuffer_bytes[start : start + FRAMEBUFFER_WIDTH * 2] = src_row[: FRAMEBUFFER_WIDTH * 2]
|
||||
write_psx_16bpp_png(framebuffer_path, bytes(framebuffer_bytes), FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT)
|
||||
framebuffer_words = struct.unpack(f"<{FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT}H", bytes(framebuffer_bytes))
|
||||
framebuffer_rgb = rgba_from_words(framebuffer_words)
|
||||
|
||||
crop_x = 70
|
||||
crop_y = 0
|
||||
crop_width = 210
|
||||
crop_height = 110
|
||||
crop_bytes = bytearray(crop_width * crop_height * 2)
|
||||
for y in range(crop_height):
|
||||
src = rows[crop_y + y]
|
||||
src_start = crop_x * 2
|
||||
src_end = src_start + crop_width * 2
|
||||
dst_start = y * crop_width * 2
|
||||
crop_bytes[dst_start : dst_start + crop_width * 2] = src[src_start:src_end]
|
||||
write_psx_16bpp_png(framebuffer_crop_path, bytes(crop_bytes), crop_width, crop_height)
|
||||
print(f"framebuffer_left={framebuffer_path}")
|
||||
print(f"framebuffer_console_crop={framebuffer_crop_path}")
|
||||
|
||||
palette_rankings: list[tuple[int, int, int, int]] = []
|
||||
for palette_index, entry in enumerate(live_palette_entries):
|
||||
score, best_x, best_y = candidate_match_score(
|
||||
framebuffer_rgb,
|
||||
FRAMEBUFFER_WIDTH,
|
||||
FRAMEBUFFER_HEIGHT,
|
||||
entry["rgba"],
|
||||
width,
|
||||
height,
|
||||
guess_x=107,
|
||||
guess_y=12,
|
||||
)
|
||||
palette_rankings.append((score, palette_index, best_x, best_y))
|
||||
palette_rankings.sort()
|
||||
ranking_path = BUNDLE_DIR / "live_vram_clut_rank.txt"
|
||||
top_atlas_path = BUNDLE_DIR / "live_vram_clut_top_matches.png"
|
||||
best_candidate_path = BUNDLE_DIR / "live_vram_clut_best.png"
|
||||
ranking_lines = []
|
||||
print(f"best_live_vram_clut_matches_top_{MATCH_TOP_N}={min(MATCH_TOP_N, len(palette_rankings))}")
|
||||
top_entries: list[dict[str, object]] = []
|
||||
for score, palette_index, best_x, best_y in palette_rankings[:MATCH_TOP_N]:
|
||||
label = live_palette_labels[palette_index]
|
||||
line = f"score={score} {label} screen=({best_x},{best_y})"
|
||||
ranking_lines.append(line)
|
||||
print(f" {line}")
|
||||
top_entries.append(live_palette_entries[palette_index])
|
||||
ranking_path.write_text("\n".join(ranking_lines) + "\n", encoding="ascii")
|
||||
print(f"live_vram_clut_rank={ranking_path}")
|
||||
write_overview_grid(top_atlas_path, top_entries, columns=4)
|
||||
print(f"live_vram_clut_top_matches={top_atlas_path}")
|
||||
if palette_rankings:
|
||||
best_palette_index = palette_rankings[0][1]
|
||||
best_entry = live_palette_entries[best_palette_index]
|
||||
write_overview_grid(best_candidate_path, [best_entry], columns=1)
|
||||
print(f"live_vram_clut_best={best_candidate_path}")
|
||||
|
||||
if exact_normal or exact_flipped:
|
||||
return
|
||||
|
||||
ranked: list[tuple[int, int, int, str, list[int]]] = []
|
||||
cutoff: int | None = None
|
||||
for orientation, hits, candidate_rows in (
|
||||
("normal", normal_hits, frame_rows),
|
||||
("flipped", flipped_hits, flip_rows),
|
||||
):
|
||||
for x, y in hits:
|
||||
total, row_mismatches, pruned = near_score(rows, candidate_rows, x, y, width, cutoff)
|
||||
if pruned and len(ranked) >= TOP_N and total > ranked[-1][0]:
|
||||
continue
|
||||
entry = (total, y, x, orientation, row_mismatches)
|
||||
insert_at = bisect.bisect_left(ranked, entry)
|
||||
ranked.insert(insert_at, entry)
|
||||
if len(ranked) > TOP_N:
|
||||
ranked.pop()
|
||||
if len(ranked) == TOP_N:
|
||||
cutoff = ranked[-1][0]
|
||||
|
||||
print(f"best_near_matches_top_{TOP_N}={len(ranked)}")
|
||||
for total, y, x, orientation, row_mismatches in ranked:
|
||||
nonzero_rows = [(index, mismatch) for index, mismatch in enumerate(row_mismatches) if mismatch]
|
||||
sample = ", ".join(f"r{index}={mismatch}" for index, mismatch in nonzero_rows[:8])
|
||||
if len(nonzero_rows) > 8:
|
||||
sample += ", ..."
|
||||
if not sample:
|
||||
sample = "all rows exact"
|
||||
print(
|
||||
f" {orientation} x={x} y={y} page=({x // 256},{y // 256}) in_page=({x % 256},{y % 256}) "
|
||||
f"mismatches={total} details=[{sample}]"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
134
scripts/_tmp_psx_mode1_live_row0_batch.py
Normal file
134
scripts/_tmp_psx_mode1_live_row0_batch.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(r"k:/ghidra/Crusader_Decomp")
|
||||
L0_WDL_PATH = Path(r"e:/emu/psx/Crusader - No Remorse/LSET1/L0.WDL")
|
||||
GPU_PATH = ROOT / "binary/Crusader - No Remorse (USA) GPU RAM.bin"
|
||||
OUTPUT_DIR = ROOT / "out/psx_wdl/L0/mode1_live_clut_row_f0_x0"
|
||||
ROW_BYTES = 2048
|
||||
LIVE_CLUT_Y = 0xF0
|
||||
LIVE_CLUT_X = 0
|
||||
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from psx_extract_wdl import (
|
||||
colorize_indexed_pixels,
|
||||
parse_lset_wdl,
|
||||
scan_sprite_bundles,
|
||||
write_bundle_atlas,
|
||||
write_overview_grid,
|
||||
write_png_rgba,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
l0_data = L0_WDL_PATH.read_bytes()
|
||||
gpu = GPU_PATH.read_bytes()
|
||||
summary = parse_lset_wdl(l0_data)
|
||||
if summary is None:
|
||||
raise SystemExit("failed to parse L0.WDL")
|
||||
|
||||
region = next(region for region in summary["regions"] if region["name"] == "post_audio_region_04")
|
||||
region_data = l0_data[region["offset"] : region["offset"] + region["size"]]
|
||||
bundles = scan_sprite_bundles(region_data, max_candidates=160)
|
||||
|
||||
row = gpu[LIVE_CLUT_Y * ROW_BYTES : (LIVE_CLUT_Y + 1) * ROW_BYTES]
|
||||
row_words = struct.unpack("<1024H", row)
|
||||
palette = list(row_words[LIVE_CLUT_X : LIVE_CLUT_X + 256])
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
entries: list[dict[str, object]] = []
|
||||
summary_rows: list[dict[str, object]] = []
|
||||
mode1_count = 0
|
||||
|
||||
for bundle in bundles:
|
||||
if bundle["mode"] != 1 or not bundle["frames"]:
|
||||
continue
|
||||
mode1_count += 1
|
||||
bundle_dir = OUTPUT_DIR / f"bundle_{bundle['offset']:08X}"
|
||||
bundle_dir.mkdir(parents=True, exist_ok=True)
|
||||
rendered_frames: list[dict[str, object]] = []
|
||||
frame_rows: list[dict[str, object]] = []
|
||||
for frame in bundle["frames"]:
|
||||
rgba = colorize_indexed_pixels(frame["pixels"], frame["width"], frame["height"], bundle["mode"], palette)
|
||||
write_png_rgba(bundle_dir / f"frame_{frame['index']:03d}_live_row_f0_x0.png", rgba, frame["width"], frame["height"])
|
||||
rendered_frames.append(
|
||||
{
|
||||
"width": frame["width"],
|
||||
"height": frame["height"],
|
||||
"rgba": rgba,
|
||||
}
|
||||
)
|
||||
frame_rows.append(
|
||||
{
|
||||
"index": frame["index"],
|
||||
"width": frame["width"],
|
||||
"height": frame["height"],
|
||||
"origin_x": frame["origin_x"],
|
||||
"origin_y": frame["origin_y"],
|
||||
"data_start": frame["data_start"],
|
||||
"consumed": frame["consumed"],
|
||||
}
|
||||
)
|
||||
write_bundle_atlas(bundle_dir / "atlas_live_row_f0_x0.png", rendered_frames)
|
||||
metadata = {
|
||||
"offset": bundle["offset"],
|
||||
"mode": bundle["mode"],
|
||||
"palette_formula": "live_gpu_row_0xF0_x0_contiguous_256",
|
||||
"palette_source": {
|
||||
"gpu_dump": str(GPU_PATH),
|
||||
"x": LIVE_CLUT_X,
|
||||
"y": LIVE_CLUT_Y,
|
||||
},
|
||||
"frame_count": bundle["frame_count"],
|
||||
"exported_frames": frame_rows,
|
||||
}
|
||||
(bundle_dir / "palette_formula.json").write_text(json.dumps(metadata, indent=2), encoding="ascii")
|
||||
first_frame = bundle["frames"][0]
|
||||
first_rgba = rendered_frames[0]["rgba"]
|
||||
entries.append(
|
||||
{
|
||||
"width": first_frame["width"],
|
||||
"height": first_frame["height"],
|
||||
"rgba": first_rgba,
|
||||
"offset": bundle["offset"],
|
||||
"area": first_frame["width"] * first_frame["height"],
|
||||
}
|
||||
)
|
||||
summary_rows.append(
|
||||
{
|
||||
"offset": bundle["offset"],
|
||||
"width": first_frame["width"],
|
||||
"height": first_frame["height"],
|
||||
"frame_count": bundle["frame_count"],
|
||||
}
|
||||
)
|
||||
|
||||
entries.sort(key=lambda entry: entry["area"], reverse=True)
|
||||
overview_entries = [{"width": entry["width"], "height": entry["height"], "rgba": entry["rgba"]} for entry in entries]
|
||||
write_overview_grid(OUTPUT_DIR / "overview_live_row_f0_x0.png", overview_entries, columns=4)
|
||||
summary_rows.sort(key=lambda row: row["width"] * row["height"], reverse=True)
|
||||
(OUTPUT_DIR / "summary.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"palette_formula": "live_gpu_row_0xF0_x0_contiguous_256",
|
||||
"mode1_bundle_count": mode1_count,
|
||||
"bundles": summary_rows,
|
||||
},
|
||||
indent=2,
|
||||
),
|
||||
encoding="ascii",
|
||||
)
|
||||
|
||||
print(f"mode1_bundles={mode1_count}")
|
||||
print(f"overview={OUTPUT_DIR / 'overview_live_row_f0_x0.png'}")
|
||||
print(f"summary={OUTPUT_DIR / 'summary.json'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
27
scripts/_tmp_rebind_entity_vm_runtime_create.py
Normal file
27
scripts/_tmp_rebind_entity_vm_runtime_create.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from ghidra.program.model.symbol import SourceType, SymbolType
|
||||
|
||||
|
||||
function = helpers["get_function"](program, "1420:1499")
|
||||
if function is None:
|
||||
raise RuntimeError("Function 1420:1499 not found")
|
||||
|
||||
symbol_table = program.getSymbolTable()
|
||||
global_namespace = program.getGlobalNamespace()
|
||||
remorse_namespace = symbol_table.getNamespace("Remorse", global_namespace)
|
||||
if remorse_namespace is None:
|
||||
raise RuntimeError("Namespace Remorse not found")
|
||||
|
||||
runtime_namespace = symbol_table.getNamespace("EntityVmRuntime", remorse_namespace)
|
||||
if runtime_namespace is None:
|
||||
raise RuntimeError("Namespace Remorse::EntityVmRuntime not found")
|
||||
|
||||
for symbol in list(symbol_table.getSymbols(function.getEntryPoint())):
|
||||
if symbol.getSymbolType() == SymbolType.LABEL and symbol.getName() == "Create" and symbol.getParentNamespace() == runtime_namespace:
|
||||
symbol.delete()
|
||||
|
||||
function.setParentNamespace(runtime_namespace)
|
||||
function.setName("Create", SourceType.USER_DEFINED)
|
||||
|
||||
print("PARENT", function.getParentNamespace())
|
||||
for symbol in symbol_table.getSymbols(function.getEntryPoint()):
|
||||
print("SYMBOL", symbol.getName(True), symbol.getSymbolType(), symbol.getSource())
|
||||
69
scripts/_tmp_scene_link_scan.ps1
Normal file
69
scripts/_tmp_scene_link_scan.ps1
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
Set-Location 'k:\ghidra\crusader_map_viewer\map_renderer'
|
||||
|
||||
function Get-Shape([object]$item) {
|
||||
if ($null -eq $item -or $null -eq $item.shapeDefId) { return $null }
|
||||
if ($item.shapeDefId -match '^shape:(\d+)$') { return [int]$Matches[1] }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-Qlo([object]$item) {
|
||||
if ($null -eq $item -or $null -eq $item.quality) { return $null }
|
||||
return ([int]$item.quality) -band 0xff
|
||||
}
|
||||
|
||||
function Get-Distance([object]$a, [object]$b) {
|
||||
$dx = [double]$a.world.x - [double]$b.world.x
|
||||
$dy = [double]$a.world.y - [double]$b.world.y
|
||||
return [math]::Sqrt(($dx * $dx) + ($dy * $dy))
|
||||
}
|
||||
|
||||
$pairs = @(
|
||||
@{ Name = 'BRO_BOOT->SPANEL'; Source = 0x04fe; Target = 0x03aa; Distance = 768 },
|
||||
@{ Name = 'NPC_ONLY->CMD_LINK'; Source = 0x0366; Target = 0x04b1; Distance = 768 },
|
||||
@{ Name = 'DEATHBOX->CMD_LINK'; Source = 0x04e7; Target = 0x04b1; Distance = 768 },
|
||||
@{ Name = 'NPC_ONLY->TRIGGERISH'; Source = 0x0366; Target = 0x0361; Distance = 768 }
|
||||
)
|
||||
|
||||
$sceneFiles = Get-ChildItem '.\site\data\maps' -Recurse -Filter 'scene.json' | Sort-Object FullName
|
||||
$lines = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($pair in $pairs) {
|
||||
$matchedSources = 0
|
||||
$sourceCount = 0
|
||||
$linkCount = 0
|
||||
$examples = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($file in $sceneFiles) {
|
||||
$scene = Get-Content $file.FullName -Raw | ConvertFrom-Json
|
||||
$items = @($scene.items)
|
||||
$sources = @($items | Where-Object { (Get-Shape $_) -eq $pair.Source })
|
||||
if ($sources.Count -eq 0) { continue }
|
||||
$targets = @($items | Where-Object { (Get-Shape $_) -eq $pair.Target })
|
||||
|
||||
foreach ($source in $sources) {
|
||||
$sourceCount += 1
|
||||
$qlo = Get-Qlo $source
|
||||
if ($null -eq $qlo) { continue }
|
||||
$matches = @($targets | Where-Object {
|
||||
(Get-Qlo $_) -eq $qlo -and (Get-Distance $source $_) -le $pair.Distance
|
||||
})
|
||||
if ($matches.Count -gt 0) {
|
||||
$matchedSources += 1
|
||||
$linkCount += $matches.Count
|
||||
if ($examples.Count -lt 6) {
|
||||
$examples.Add(('{0}/{1}: source={2} qlo={3} targetIds={4}' -f $file.Directory.Parent.Name, $file.Directory.Name, $source.id, $qlo, (($matches | ForEach-Object { $_.id }) -join ',')))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rate = if ($sourceCount -gt 0) { [math]::Round(($matchedSources / $sourceCount) * 100, 1) } else { 0 }
|
||||
$lines.Add(('PAIR {0}' -f $pair.Name))
|
||||
$lines.Add(('sources={0} matched={1} rate={2}% links={3}' -f $sourceCount, $matchedSources, $rate, $linkCount))
|
||||
foreach ($example in $examples) {
|
||||
$lines.Add($example)
|
||||
}
|
||||
$lines.Add('')
|
||||
}
|
||||
|
||||
$lines | Set-Content 'k:\ghidra\Crusader_Decomp\_tmp_scene_link_scan.txt'
|
||||
1203
scripts/_tmp_scummvm_attack_process.cpp
Normal file
1203
scripts/_tmp_scummvm_attack_process.cpp
Normal file
File diff suppressed because it is too large
Load diff
75
scripts/_tmp_scummvm_combat_dat.h
Normal file
75
scripts/_tmp_scummvm_combat_dat.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef WORLD_ACTORS_COMBAT_DAT_H
|
||||
#define WORLD_ACTORS_COMBAT_DAT_H
|
||||
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "common/str.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima8 {
|
||||
|
||||
/**
|
||||
* A single entry in the Crusader combat.dat flex. The files consist of 3 parts:
|
||||
* 1. human-readable name (zero-padded 16 bytes)
|
||||
* 2. offset table (10x2-byte offsets, in practice only the first 2 offsets are ever used)
|
||||
* 3. tactic blocks starting at the offsets given in the offset (in practice only 2 blocks are used)
|
||||
*
|
||||
* The tactic blocks are a sequence of opcodes of things the NPC should
|
||||
* do - eg, turn towards direction X.
|
||||
*/
|
||||
class CombatDat {
|
||||
public:
|
||||
CombatDat(Common::SeekableReadStream &rs);
|
||||
|
||||
~CombatDat();
|
||||
|
||||
const Common::String &getName() const {
|
||||
return _name;
|
||||
};
|
||||
|
||||
const uint8 *getData() const {
|
||||
return _data;
|
||||
}
|
||||
|
||||
uint16 getOffset(int block) const {
|
||||
assert(block < ARRAYSIZE(_offsets));
|
||||
return _offsets[block];
|
||||
}
|
||||
|
||||
uint16 getDataLen() const {
|
||||
return _dataLen;
|
||||
}
|
||||
|
||||
private:
|
||||
Common::String _name;
|
||||
|
||||
uint16 _offsets[4];
|
||||
uint8 *_data;
|
||||
uint16 _dataLen;
|
||||
};
|
||||
|
||||
} // End of namespace Ultima8
|
||||
} // End of namespace Ultima
|
||||
|
||||
#endif
|
||||
35
scripts/_tmp_spawner_compare.js
Normal file
35
scripts/_tmp_spawner_compare.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const fs = require('fs');
|
||||
|
||||
function load(path) {
|
||||
return JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
}
|
||||
|
||||
function dist(a, b) {
|
||||
return Math.hypot(a.world.x - b.world.x, a.world.y - b.world.y);
|
||||
}
|
||||
|
||||
function qlo(item) {
|
||||
return item.quality & 0xff;
|
||||
}
|
||||
|
||||
function isSpawner(item) {
|
||||
return item.shapeDefId === 'shape:1232';
|
||||
}
|
||||
|
||||
for (const [label, path] of [
|
||||
['map1', 'k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-1/9ccaa5dabe08947e/scene.json'],
|
||||
['map248', 'k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-248/b27ea0d8d2a1a391/scene.json']
|
||||
]) {
|
||||
const scene = load(path);
|
||||
const items = scene.items.filter(isSpawner);
|
||||
const interesting = items.filter((item) => item.npcPreview?.name === 'Observer' || item.npcPreview?.name === 'RoamingSusan');
|
||||
console.log(`\n### ${label} interesting spawners ${interesting.length}`);
|
||||
for (const item of interesting) {
|
||||
const pairs = items.filter((candidate) => candidate.id !== item.id && candidate.frame !== item.frame && qlo(candidate) === qlo(item) && dist(candidate, item) <= 128);
|
||||
const pairText = pairs.map((candidate) => `${candidate.id} src=${candidate.mapSourceIndex} f=${candidate.frame} npc=${candidate.npcNum} ${candidate.npcPreview?.name || '?'} qlo=${qlo(candidate)} d=${dist(candidate, item).toFixed(1)} map=${candidate.mapNum}`).join(' || ');
|
||||
console.log(`${item.id} src=${item.mapSourceIndex} f=${item.frame} npc=${item.npcNum} ${item.npcPreview?.name || '?'} qlo=${qlo(item)} map=${item.mapNum} world=${item.world.x},${item.world.y},${item.world.z}`);
|
||||
if (pairText) {
|
||||
console.log(` pairs: ${pairText}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
scripts/_tmp_spawner_truth_pass.js
Normal file
61
scripts/_tmp_spawner_truth_pass.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import fs from "node:fs";
|
||||
|
||||
const scenePaths = [
|
||||
["map1", "k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-1/9ccaa5dabe08947e/scene.json"],
|
||||
["map248", "k:/ghidra/crusader_map_viewer/map_renderer/.cache/scene-cache/remorse/map-248/b27ea0d8d2a1a391/scene.json"]
|
||||
];
|
||||
|
||||
function distance(left, right) {
|
||||
return Math.hypot(left.world.x - right.world.x, left.world.y - right.world.y);
|
||||
}
|
||||
|
||||
for (const [label, scenePath] of scenePaths) {
|
||||
const scene = JSON.parse(fs.readFileSync(scenePath, "utf8"));
|
||||
const spawners = scene.items.filter((item) => item.shapeDefId === "shape:1232");
|
||||
const rows = [];
|
||||
|
||||
for (const item of spawners) {
|
||||
if (item.frame !== 0) {
|
||||
continue;
|
||||
}
|
||||
const qlo = item.quality & 0xff;
|
||||
const pairCandidates = spawners
|
||||
.filter((candidate) => candidate.id !== item.id && candidate.frame === 1 && ((candidate.quality & 0xff) === qlo) && distance(item, candidate) <= 512)
|
||||
.sort((left, right) => distance(item, left) - distance(item, right));
|
||||
const pair = pairCandidates[0] ?? null;
|
||||
if (!pair) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
controllerId: item.id,
|
||||
pairId: pair.id,
|
||||
auto: (item.mapNum & 0x08) === 0,
|
||||
controllerNpc: item.npcPreview?.name ?? null,
|
||||
controllerNpcNum: item.npcNum,
|
||||
pairNpc: pair.npcPreview?.name ?? null,
|
||||
pairNpcNum: pair.npcNum,
|
||||
qlo,
|
||||
distance: Math.round(distance(item, pair)),
|
||||
pairCount: pairCandidates.length
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`\n=== ${label} ===`);
|
||||
const autoMismatched = rows.filter((row) => row.auto && row.controllerNpc && row.pairNpc && row.controllerNpc !== row.pairNpc);
|
||||
const blockedMismatched = rows.filter((row) => !row.auto && row.controllerNpc && row.pairNpc && row.controllerNpc !== row.pairNpc);
|
||||
const autoControllerMissingPairResolved = rows.filter((row) => row.auto && !row.controllerNpc && row.pairNpc);
|
||||
console.log(`auto mismatched valid pairs: ${autoMismatched.length}`);
|
||||
console.log(`blocked mismatched valid pairs: ${blockedMismatched.length}`);
|
||||
console.log(`auto unresolved-controller / resolved-pair: ${autoControllerMissingPairResolved.length}`);
|
||||
|
||||
for (const row of autoMismatched.slice(0, 12)) {
|
||||
console.log(`AUTO ${row.controllerId} ${row.controllerNpcNum}:${row.controllerNpc} -> ${row.pairId} ${row.pairNpcNum}:${row.pairNpc} qlo=${row.qlo} d=${row.distance} c=${row.pairCount}`);
|
||||
}
|
||||
for (const row of autoControllerMissingPairResolved.slice(0, 12)) {
|
||||
console.log(`AUTO-UNRESOLVED ${row.controllerId} ${row.controllerNpcNum}:${row.controllerNpc} -> ${row.pairId} ${row.pairNpcNum}:${row.pairNpc} qlo=${row.qlo} d=${row.distance} c=${row.pairCount}`);
|
||||
}
|
||||
for (const row of blockedMismatched.slice(0, 12)) {
|
||||
console.log(`BLOCKED ${row.controllerId} ${row.controllerNpcNum}:${row.controllerNpc} -> ${row.pairId} ${row.pairNpcNum}:${row.pairNpc} qlo=${row.qlo} d=${row.distance} c=${row.pairCount}`);
|
||||
}
|
||||
}
|
||||
19
scripts/_tmp_targets.py
Normal file
19
scripts/_tmp_targets.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import csv, pathlib
|
||||
p = pathlib.Path('USECODE/EUSECODE_extracted/class_event_index.tsv')
|
||||
targets = {189,190,191,272,273,283,285}
|
||||
rows = [r for r in csv.DictReader(p.open('r', encoding='utf-8'), delimiter='\t') if int(r['entry_index'], 0) in targets]
|
||||
for eid in sorted(targets):
|
||||
print(f'ENTRY {eid}')
|
||||
for r in rows:
|
||||
if int(r['entry_index'], 0) != eid:
|
||||
continue
|
||||
length = (r.get('derived_body_length') or '').strip()
|
||||
if not length:
|
||||
continue
|
||||
try:
|
||||
n = int(length, 0)
|
||||
except Exception:
|
||||
continue
|
||||
if n == 0:
|
||||
continue
|
||||
print(f" slot {r['slot']} {r['event_name_hint']}: start={r['derived_body_start']} end={r['derived_body_end']} len={r['derived_body_length']} raw={r['raw_event_entry_word']} template={r.get('repeated_template_status','')}")
|
||||
15
scripts/_tmp_update_sprite_create_comment.py
Normal file
15
scripts/_tmp_update_sprite_create_comment.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
comment = (
|
||||
"Current best live read: compact shared SpriteNode constructor. "
|
||||
"The body allocates 0x34 bytes, stamps the 0x501a node vtable, and initializes the core link/offset lanes; "
|
||||
"current direct callers are overwhelmingly GumpCreate_* wrappers, which supports treating this as the base node "
|
||||
"constructor used by higher-level UI/gump objects rather than a one-off derived leaf."
|
||||
)
|
||||
|
||||
print(bridge.set_decompiler_comment("1360:036a", comment))
|
||||
print(bridge.get_function_by_address("1360:036a"))
|
||||
15
scripts/_tmp_update_sprite_dispatch_comment.py
Normal file
15
scripts/_tmp_update_sprite_dispatch_comment.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import sys
|
||||
|
||||
sys.path.insert(0, r"k:\mcp\GhidraMCP")
|
||||
import bridge_mcp_ghidra as bridge
|
||||
|
||||
|
||||
comment = (
|
||||
"Old 000b:3ab2 by preserved offset delta from live 1360:046e. "
|
||||
"DispatchEvent maps event codes 1/2/4/8/0x10/0x20/0x40/0x100 onto vtable slots "
|
||||
"+0x04/+0x08/+0x0c/+0x10/+0x14/+0x18/+0x1c/+0x24; the 0x40 path also walks child nodes "
|
||||
"and dispatches through child slot +0x34 before optional self-slot +0x1c handling."
|
||||
)
|
||||
|
||||
print(bridge.set_decompiler_comment("1360:0cb2", comment))
|
||||
print(bridge.get_function_by_address("1360:0cb2"))
|
||||
227
scripts/_tmp_valuebox_cache_scan.py
Normal file
227
scripts/_tmp_valuebox_cache_scan.py
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
import json
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(r"e:\disasm\Crusader-Map-Viewer\map_renderer\.cache")
|
||||
SCENE_ROOT = ROOT / "scene-cache"
|
||||
REF_ROOT = ROOT / "reference-data"
|
||||
TARGET_SHAPE = "shape:251"
|
||||
MAX_DISTANCE = 1600
|
||||
MAX_NEIGHBORS_PER_ITEM = 8
|
||||
INTERESTING_SHAPES = {
|
||||
"shape:251": "VALUEBOX",
|
||||
"shape:258": "MONITNS",
|
||||
"shape:357": "MONITEW",
|
||||
"shape:871": "WALLMNS",
|
||||
"shape:1086": "WALLMEW",
|
||||
"shape:1019": "SECURNS",
|
||||
"shape:1085": "SECUREW",
|
||||
"shape:1214": "WATCHNS",
|
||||
"shape:1246": "WATCHEW",
|
||||
"shape:2573": "KEYPAD",
|
||||
"shape:2574": "KEYPAD?",
|
||||
}
|
||||
|
||||
|
||||
def load_shape_names(game: str) -> dict[str, str]:
|
||||
ref_path = REF_ROOT / game / "reference-data.json"
|
||||
with ref_path.open("r", encoding="utf-8") as handle:
|
||||
data = json.load(handle)
|
||||
shape_names = {}
|
||||
for entry in data.get("shapeDefinitions", []):
|
||||
shape_id = entry.get("id")
|
||||
if not shape_id:
|
||||
continue
|
||||
name = (
|
||||
entry.get("catalog", {}).get("label")
|
||||
or entry.get("displayName")
|
||||
or shape_id
|
||||
)
|
||||
shape_names[shape_id] = name
|
||||
return shape_names
|
||||
|
||||
|
||||
def squared_distance(a: dict, b: dict) -> int:
|
||||
a_world = a.get("world") or {}
|
||||
b_world = b.get("world") or {}
|
||||
dx = int(a_world.get("x", 0)) - int(b_world.get("x", 0))
|
||||
dy = int(a_world.get("y", 0)) - int(b_world.get("y", 0))
|
||||
dz = int(a_world.get("z", 0)) - int(b_world.get("z", 0))
|
||||
return dx * dx + dy * dy + dz * dz
|
||||
|
||||
|
||||
def main() -> None:
|
||||
shape_names_by_game = {
|
||||
"remorse": load_shape_names("remorse"),
|
||||
"regret": load_shape_names("regret"),
|
||||
}
|
||||
summary = []
|
||||
per_game_shape_counts = defaultdict(Counter)
|
||||
per_game_qlo = defaultdict(Counter)
|
||||
per_game_qhi = defaultdict(Counter)
|
||||
interesting_links = defaultdict(Counter)
|
||||
examples = defaultdict(list)
|
||||
nonzero_examples = defaultdict(list)
|
||||
|
||||
for game_dir in sorted(SCENE_ROOT.iterdir()):
|
||||
if not game_dir.is_dir():
|
||||
continue
|
||||
game_name = game_dir.name
|
||||
base_game = "regret" if game_name.startswith("regret") else "remorse"
|
||||
shape_names = shape_names_by_game[base_game]
|
||||
for map_dir in sorted(game_dir.iterdir()):
|
||||
if not map_dir.is_dir() or not map_dir.name.startswith("map-"):
|
||||
continue
|
||||
map_name = map_dir.name
|
||||
for hash_dir in sorted(map_dir.iterdir()):
|
||||
scene_path = hash_dir / "scene.json"
|
||||
if not scene_path.exists():
|
||||
continue
|
||||
with scene_path.open("r", encoding="utf-8") as handle:
|
||||
scene = json.load(handle)
|
||||
items = scene.get("items", [])
|
||||
valueboxes = [item for item in items if item.get("shapeDefId") == TARGET_SHAPE and item.get("frame") == 0]
|
||||
if not valueboxes:
|
||||
continue
|
||||
|
||||
for item in valueboxes:
|
||||
quality = int(item.get("quality", 0))
|
||||
qlo = quality & 0xFF
|
||||
qhi = (quality >> 8) & 0xFF
|
||||
per_game_qlo[game_name][qlo] += 1
|
||||
per_game_qhi[game_name][qhi] += 1
|
||||
|
||||
nearby = []
|
||||
for other in items:
|
||||
if other is item:
|
||||
continue
|
||||
dist2 = squared_distance(item, other)
|
||||
if dist2 <= MAX_DISTANCE * MAX_DISTANCE:
|
||||
nearby.append((dist2, other))
|
||||
nearby.sort(key=lambda pair: pair[0])
|
||||
|
||||
for _, other in nearby[:MAX_NEIGHBORS_PER_ITEM]:
|
||||
per_game_shape_counts[game_name][other.get("shapeDefId", "<none>")] += 1
|
||||
|
||||
for dist2, other in nearby:
|
||||
shape_id = other.get("shapeDefId", "<none>")
|
||||
if shape_id in INTERESTING_SHAPES:
|
||||
interesting_links[game_name][shape_id] += 1
|
||||
|
||||
if len(examples[game_name]) < 12:
|
||||
world = item.get("world") or {}
|
||||
example_row = {
|
||||
"map": map_name,
|
||||
"id": item.get("id"),
|
||||
"coords": [world.get("x"), world.get("y"), world.get("z")],
|
||||
"quality": quality,
|
||||
"qlo": qlo,
|
||||
"qhi": qhi,
|
||||
"mapNum": item.get("mapNum"),
|
||||
"npcNum": item.get("npcNum"),
|
||||
"nextItem": item.get("nextItem"),
|
||||
"nearby": [
|
||||
{
|
||||
"shape": other.get("shapeDefId"),
|
||||
"name": shape_names.get(other.get("shapeDefId", ""), other.get("shapeDefId", "")),
|
||||
"frame": other.get("frame"),
|
||||
"quality": other.get("quality"),
|
||||
"mapNum": other.get("mapNum"),
|
||||
"npcNum": other.get("npcNum"),
|
||||
"nextItem": other.get("nextItem"),
|
||||
"coords": [
|
||||
(other.get("world") or {}).get("x"),
|
||||
(other.get("world") or {}).get("y"),
|
||||
(other.get("world") or {}).get("z"),
|
||||
],
|
||||
"dist2": dist2,
|
||||
}
|
||||
for dist2, other in nearby
|
||||
if other.get("shapeDefId") in INTERESTING_SHAPES
|
||||
][:MAX_NEIGHBORS_PER_ITEM]
|
||||
or [
|
||||
{
|
||||
"shape": other.get("shapeDefId"),
|
||||
"name": shape_names.get(other.get("shapeDefId", ""), other.get("shapeDefId", "")),
|
||||
"frame": other.get("frame"),
|
||||
"quality": other.get("quality"),
|
||||
"mapNum": other.get("mapNum"),
|
||||
"npcNum": other.get("npcNum"),
|
||||
"nextItem": other.get("nextItem"),
|
||||
"coords": [
|
||||
(other.get("world") or {}).get("x"),
|
||||
(other.get("world") or {}).get("y"),
|
||||
(other.get("world") or {}).get("z"),
|
||||
],
|
||||
"dist2": dist2,
|
||||
}
|
||||
for dist2, other in nearby[:MAX_NEIGHBORS_PER_ITEM]
|
||||
],
|
||||
}
|
||||
examples[game_name].append(example_row)
|
||||
if (qlo or qhi or item.get("mapNum") or item.get("npcNum")) and len(nonzero_examples[game_name]) < 12:
|
||||
nonzero_examples[game_name].append(example_row)
|
||||
elif (qlo or qhi or item.get("mapNum") or item.get("npcNum")) and len(nonzero_examples[game_name]) < 12:
|
||||
world = item.get("world") or {}
|
||||
nonzero_examples[game_name].append(
|
||||
{
|
||||
"map": map_name,
|
||||
"id": item.get("id"),
|
||||
"coords": [world.get("x"), world.get("y"), world.get("z")],
|
||||
"quality": quality,
|
||||
"qlo": qlo,
|
||||
"qhi": qhi,
|
||||
"mapNum": item.get("mapNum"),
|
||||
"npcNum": item.get("npcNum"),
|
||||
"nextItem": item.get("nextItem"),
|
||||
}
|
||||
)
|
||||
|
||||
summary.append({"game": game_name, "map": map_name, "count": len(valueboxes)})
|
||||
|
||||
print("VALUEBOX frame-0 counts by map")
|
||||
for row in sorted(summary, key=lambda entry: (-entry["count"], entry["game"], entry["map"]))[:40]:
|
||||
print(f"{row['game']:12} {row['map']:8} count={row['count']}")
|
||||
|
||||
print("\nTop nearby shapes per game")
|
||||
for game_name, counter in sorted(per_game_shape_counts.items()):
|
||||
print(f"\n[{game_name}]")
|
||||
for shape_id, count in counter.most_common(20):
|
||||
base_game = "regret" if game_name.startswith("regret") else "remorse"
|
||||
shape_name = shape_names_by_game[base_game].get(shape_id, shape_id)
|
||||
print(f"{shape_id:10} {count:5} {shape_name}")
|
||||
|
||||
print("\nInteresting nearby controller families")
|
||||
for game_name, counter in sorted(interesting_links.items()):
|
||||
print(f"\n[{game_name}]")
|
||||
for shape_id, count in counter.most_common():
|
||||
print(f"{shape_id:10} {count:5} {INTERESTING_SHAPES[shape_id]}")
|
||||
|
||||
print("\nTop QLo values per game")
|
||||
for game_name, counter in sorted(per_game_qlo.items()):
|
||||
print(f"\n[{game_name}]")
|
||||
for value, count in counter.most_common(20):
|
||||
print(f"QLo {value:3} -> {count}")
|
||||
|
||||
print("\nTop QHi values per game")
|
||||
for game_name, counter in sorted(per_game_qhi.items()):
|
||||
print(f"\n[{game_name}]")
|
||||
for value, count in counter.most_common(20):
|
||||
print(f"QHi {value:3} -> {count}")
|
||||
|
||||
print("\nRepresentative examples")
|
||||
for game_name, rows in sorted(examples.items()):
|
||||
print(f"\n[{game_name}]")
|
||||
for row in rows:
|
||||
print(json.dumps(row, sort_keys=True))
|
||||
|
||||
print("\nNonzero payload examples")
|
||||
for game_name, rows in sorted(nonzero_examples.items()):
|
||||
print(f"\n[{game_name}]")
|
||||
for row in rows:
|
||||
print(json.dumps(row, sort_keys=True))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
329
scripts/_tmp_valuebox_cache_scan_output.txt
Normal file
329
scripts/_tmp_valuebox_cache_scan_output.txt
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
VALUEBOX frame-0 counts by map
|
||||
remorse map-19 count=57
|
||||
remorse-101 map-19 count=57
|
||||
remorse-jp map-19 count=57
|
||||
remorse map-29 count=32
|
||||
remorse-101 map-29 count=32
|
||||
remorse-jp map-29 count=32
|
||||
remorse map-62 count=28
|
||||
remorse-101 map-62 count=28
|
||||
remorse-jp map-62 count=28
|
||||
regret map-15 count=27
|
||||
remorse map-47 count=27
|
||||
remorse-101 map-47 count=27
|
||||
remorse-jp map-47 count=27
|
||||
remorse map-25 count=25
|
||||
remorse map-69 count=25
|
||||
remorse-101 map-25 count=25
|
||||
remorse-101 map-69 count=25
|
||||
remorse-jp map-25 count=25
|
||||
remorse-jp map-69 count=25
|
||||
remorse map-141 count=24
|
||||
remorse-101 map-141 count=24
|
||||
remorse-jp map-141 count=24
|
||||
remorse map-10 count=22
|
||||
remorse map-26 count=22
|
||||
remorse-101 map-10 count=22
|
||||
remorse-101 map-26 count=22
|
||||
remorse-jp map-10 count=22
|
||||
remorse-jp map-26 count=22
|
||||
regret map-16 count=16
|
||||
regret map-13 count=14
|
||||
regret map-14 count=14
|
||||
regret map-8 count=11
|
||||
regret map-28 count=10
|
||||
regret map-5 count=10
|
||||
regret map-7 count=10
|
||||
regret map-200 count=9
|
||||
regret map-201 count=9
|
||||
remorse map-15 count=9
|
||||
remorse-101 map-15 count=9
|
||||
remorse-jp map-15 count=9
|
||||
|
||||
Top nearby shapes per game
|
||||
|
||||
[regret]
|
||||
shape:248 226 shape_00f8
|
||||
shape:249 76 shape_00f9
|
||||
shape:1232 71 shape_04d0
|
||||
shape:253 49 shape_00fd
|
||||
shape:573 47 shape_023d
|
||||
shape:16 42 shape_0010
|
||||
shape:1061 42 shape_0425
|
||||
shape:534 38 shape_0216
|
||||
shape:1365 28 shape_0555
|
||||
shape:246 22 shape_00f6
|
||||
shape:1278 21 shape_04fe
|
||||
shape:567 20 shape_0237
|
||||
shape:1201 19 CMD_LINK
|
||||
shape:1142 19 shape_0476
|
||||
shape:1364 19 shape_0554
|
||||
shape:247 18 shape_00f7
|
||||
shape:1363 16 shape_0553
|
||||
shape:1369 15 shape_0559
|
||||
shape:1347 15 shape_0543
|
||||
shape:1366 14 shape_0556
|
||||
|
||||
[regret-demo]
|
||||
shape:248 8 shape_00f8
|
||||
shape:99 6 shape_0063
|
||||
shape:249 6 shape_00f9
|
||||
shape:574 5 shape_023e
|
||||
shape:1232 5 shape_04d0
|
||||
shape:431 5 shape_01af
|
||||
shape:250 4 shape_00fa
|
||||
shape:1142 3 shape_0476
|
||||
shape:16 3 shape_0010
|
||||
shape:246 2 shape_00f6
|
||||
shape:252 2 shape_00fc
|
||||
shape:534 1 shape_0216
|
||||
shape:949 1 shape_03b5
|
||||
shape:593 1 shape_0251
|
||||
shape:1377 1 shape_0561
|
||||
shape:1593 1 Roof_Regret_Level1
|
||||
shape:253 1 shape_00fd
|
||||
shape:1201 1 CMD_LINK
|
||||
|
||||
[remorse]
|
||||
shape:248 444 shape_00f8
|
||||
shape:249 132 shape_00f9
|
||||
shape:569 77 shape_0239
|
||||
shape:534 62 shape_0216
|
||||
shape:16 60 shape_0010
|
||||
shape:253 58 shape_00fd
|
||||
shape:563 49 shape_0233
|
||||
shape:564 49 shape_0234
|
||||
shape:1253 49 shape_04e5
|
||||
shape:572 46 shape_023c
|
||||
shape:486 42 shape_01e6
|
||||
shape:250 40 shape_00fa
|
||||
shape:43 40 shape_002b
|
||||
shape:573 39 shape_023d
|
||||
shape:716 36 shape_02cc
|
||||
shape:252 35 shape_00fc
|
||||
shape:631 35 shape_0277
|
||||
shape:246 33 shape_00f6
|
||||
shape:15 33 shape_000f
|
||||
shape:547 30 shape_0223
|
||||
|
||||
[remorse-101]
|
||||
shape:248 444 shape_00f8
|
||||
shape:249 132 shape_00f9
|
||||
shape:569 77 shape_0239
|
||||
shape:534 62 shape_0216
|
||||
shape:16 60 shape_0010
|
||||
shape:253 58 shape_00fd
|
||||
shape:563 49 shape_0233
|
||||
shape:564 49 shape_0234
|
||||
shape:1253 49 shape_04e5
|
||||
shape:572 46 shape_023c
|
||||
shape:486 42 shape_01e6
|
||||
shape:250 40 shape_00fa
|
||||
shape:43 40 shape_002b
|
||||
shape:573 39 shape_023d
|
||||
shape:716 36 shape_02cc
|
||||
shape:252 35 shape_00fc
|
||||
shape:631 35 shape_0277
|
||||
shape:246 33 shape_00f6
|
||||
shape:15 33 shape_000f
|
||||
shape:547 30 shape_0223
|
||||
|
||||
[remorse-jp]
|
||||
shape:248 444 shape_00f8
|
||||
shape:249 132 shape_00f9
|
||||
shape:569 77 shape_0239
|
||||
shape:534 62 shape_0216
|
||||
shape:16 60 shape_0010
|
||||
shape:253 58 shape_00fd
|
||||
shape:563 49 shape_0233
|
||||
shape:564 49 shape_0234
|
||||
shape:1253 49 shape_04e5
|
||||
shape:572 46 shape_023c
|
||||
shape:486 42 shape_01e6
|
||||
shape:250 40 shape_00fa
|
||||
shape:43 40 shape_002b
|
||||
shape:573 39 shape_023d
|
||||
shape:716 36 shape_02cc
|
||||
shape:252 35 shape_00fc
|
||||
shape:631 35 shape_0277
|
||||
shape:246 33 shape_00f6
|
||||
shape:15 33 shape_000f
|
||||
shape:547 30 shape_0223
|
||||
|
||||
Interesting nearby controller families
|
||||
|
||||
[regret]
|
||||
shape:251 226 VALUEBOX
|
||||
shape:357 32 MONITEW
|
||||
shape:258 21 MONITNS
|
||||
shape:1246 20 WATCHEW
|
||||
shape:1019 11 SECURNS
|
||||
shape:1085 6 SECUREW
|
||||
shape:871 6 WALLMNS
|
||||
shape:1086 1 WALLMEW
|
||||
|
||||
[regret-demo]
|
||||
shape:251 6 VALUEBOX
|
||||
shape:258 2 MONITNS
|
||||
shape:1246 2 WATCHEW
|
||||
|
||||
[remorse]
|
||||
shape:251 456 VALUEBOX
|
||||
shape:357 90 MONITEW
|
||||
shape:258 65 MONITNS
|
||||
shape:1246 18 WATCHEW
|
||||
shape:1019 1 SECURNS
|
||||
|
||||
[remorse-101]
|
||||
shape:251 456 VALUEBOX
|
||||
shape:357 90 MONITEW
|
||||
shape:258 65 MONITNS
|
||||
shape:1246 18 WATCHEW
|
||||
shape:1019 1 SECURNS
|
||||
|
||||
[remorse-jp]
|
||||
shape:251 456 VALUEBOX
|
||||
shape:357 90 MONITEW
|
||||
shape:258 65 MONITNS
|
||||
shape:1246 18 WATCHEW
|
||||
shape:1019 1 SECURNS
|
||||
|
||||
Top QLo values per game
|
||||
|
||||
[regret]
|
||||
QLo 0 -> 170
|
||||
QLo 23 -> 1
|
||||
|
||||
[regret-demo]
|
||||
QLo 0 -> 7
|
||||
|
||||
[remorse]
|
||||
QLo 0 -> 299
|
||||
QLo 60 -> 3
|
||||
|
||||
[remorse-101]
|
||||
QLo 0 -> 299
|
||||
QLo 60 -> 3
|
||||
|
||||
[remorse-jp]
|
||||
QLo 0 -> 299
|
||||
QLo 60 -> 3
|
||||
|
||||
Top QHi values per game
|
||||
|
||||
[regret]
|
||||
QHi 0 -> 171
|
||||
|
||||
[regret-demo]
|
||||
QHi 0 -> 7
|
||||
|
||||
[remorse]
|
||||
QHi 0 -> 299
|
||||
QHi 3 -> 3
|
||||
|
||||
[remorse-101]
|
||||
QHi 0 -> 299
|
||||
QHi 3 -> 3
|
||||
|
||||
[remorse-jp]
|
||||
QHi 0 -> 299
|
||||
QHi 3 -> 3
|
||||
|
||||
Representative examples
|
||||
|
||||
[regret]
|
||||
{"coords": [57662, 5790, 0], "id": "item:1425:fixed:251:0:57662:5790:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58302, 5790, 0], "dist2": 409600, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4501, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58590, 7070, 0], "dist2": 2499584, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4229, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4523, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [58302, 5790, 0], "id": "item:1430:fixed:251:0:58302:5790:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57662, 5790, 0], "dist2": 409600, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4523, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58590, 7070, 0], "dist2": 1721344, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4229, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58590, 7358, 0], "dist2": 2541568, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4080, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4501, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [58590, 7070, 0], "id": "item:1621:fixed:251:0:58590:7070:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58590, 7358, 0], "dist2": 82944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4080, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58302, 5790, 0], "dist2": 1721344, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4501, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57662, 5790, 0], "dist2": 2499584, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4523, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4229, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [58590, 7358, 0], "id": "item:1710:fixed:251:0:58590:7358:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58590, 7070, 0], "dist2": 82944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4229, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58302, 5790, 0], "dist2": 2541568, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 4501, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 4080, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [59358, 11486, 0], "id": "item:1991:fixed:251:0:59358:11486:0", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [59230, 11486, 0], "dist2": 16384, "frame": 0, "mapNum": 0, "name": "shape_00fd", "nextItem": 5364, "npcNum": 0, "quality": 0, "shape": "shape:253"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 4, "mapNum": 0, "name": "shape_023d", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:573"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 0, "mapNum": 0, "name": "shape_0010", "nextItem": 3701, "npcNum": 0, "quality": 2651, "shape": "shape:16"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 2, "mapNum": 0, "name": "shape_0237", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:567"}, {"coords": [59390, 11262, 0], "dist2": 51200, "frame": 2, "mapNum": 0, "name": "shape_0239", "nextItem": 3719, "npcNum": 0, "quality": 0, "shape": "shape:569"}, {"coords": [59422, 11262, 0], "dist2": 54272, "frame": 1, "mapNum": 0, "name": "shape_0239", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:569"}, {"coords": [59486, 11262, 0], "dist2": 66560, "frame": 1, "mapNum": 0, "name": "shape_023c", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:572"}, {"coords": [59390, 11774, 0], "dist2": 83968, "frame": 4, "mapNum": 0, "name": "shape_023d", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:573"}], "nextItem": 5365, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [58334, 9214, 96], "id": "item:5998:fixed:251:0:58334:9214:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57758, 9022, 96], "dist2": 368640, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3949, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9630, 96], "dist2": 504832, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3819, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 10110, 96], "dist2": 1134592, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 3957, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [57758, 9022, 96], "id": "item:6001:fixed:251:0:57758:9022:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [58334, 9214, 96], "dist2": 368640, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3957, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9630, 96], "dist2": 369664, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3819, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 10110, 96], "dist2": 1183744, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 3949, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [57758, 10110, 96], "id": "item:6046:fixed:251:0:57758:10110:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57758, 9630, 96], "dist2": 230400, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3819, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58334, 9214, 96], "dist2": 1134592, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3957, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9022, 96], "dist2": 1183744, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3949, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [57758, 9630, 96], "id": "item:6051:fixed:251:0:57758:9630:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [57758, 10110, 96], "dist2": 230400, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [57758, 9022, 96], "dist2": 369664, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3949, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [58334, 9214, 96], "dist2": 504832, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3957, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 3819, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [37406, 28222, 96], "id": "item:6414:fixed:251:0:37406:28222:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [36958, 28222, 96], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2040, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [37144, 29232, 128], "dist2": 1089768, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1983, "npcNum": 0, "quality": 18972, "shape": "shape:357"}, {"coords": [35902, 28478, 128], "dist2": 2328576, "frame": 0, "mapNum": 22, "name": "shape_043d", "nextItem": 2071, "npcNum": 164, "quality": 18, "shape": "shape:1085"}], "nextItem": 2043, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36958, 28222, 96], "id": "item:6416:fixed:251:0:36958:28222:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [37406, 28222, 96], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2043, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [37144, 29232, 128], "dist2": 1055720, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1983, "npcNum": 0, "quality": 18972, "shape": "shape:357"}, {"coords": [35902, 28478, 128], "dist2": 1181696, "frame": 0, "mapNum": 22, "name": "shape_043d", "nextItem": 2071, "npcNum": 164, "quality": 18, "shape": "shape:1085"}], "nextItem": 2040, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [32414, 33310, 96], "id": "item:6666:fixed:251:0:32414:33310:96", "map": "map-13", "mapNum": 0, "nearby": [{"coords": [33694, 33310, 96], "dist2": 1638400, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1605, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1660, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
|
||||
[regret-demo]
|
||||
{"coords": [48158, 54750, 8], "id": "item:2223:fixed:251:0:48158:54750:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [48158, 55486, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 475, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 642, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [50206, 54750, 8], "id": "item:2250:fixed:251:0:50206:54750:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [50652, 54868, 56], "dist2": 215144, "frame": 0, "mapNum": 167, "name": "MONITNS", "nextItem": 564, "npcNum": 139, "quality": 43, "shape": "shape:258"}, {"coords": [50206, 55486, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 442, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 638, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [54430, 55230, 8], "id": "item:2280:fixed:251:0:54430:55230:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [53982, 55070, 8], "dist2": 226304, "frame": 0, "mapNum": 102, "name": "WATCHEW", "nextItem": 492, "npcNum": 24, "quality": 282, "shape": "shape:1246"}, {"coords": [54366, 54750, 8], "dist2": 234496, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 3670, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 534, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [54366, 54750, 8], "id": "item:2285:fixed:251:0:54366:54750:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [54430, 55230, 8], "dist2": 234496, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 534, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [53982, 55070, 8], "dist2": 249856, "frame": 0, "mapNum": 102, "name": "WATCHEW", "nextItem": 492, "npcNum": 24, "quality": 282, "shape": "shape:1246"}], "nextItem": 3670, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [48158, 55486, 8], "id": "item:2295:fixed:251:0:48158:55486:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [48158, 54750, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 642, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 475, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [50206, 55486, 8], "id": "item:2306:fixed:251:0:50206:55486:8", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [50206, 54750, 8], "dist2": 541696, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 638, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [50652, 54868, 56], "dist2": 583144, "frame": 0, "mapNum": 167, "name": "MONITNS", "nextItem": 564, "npcNum": 139, "quality": 43, "shape": "shape:258"}], "nextItem": 442, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [26526, 28670, 96], "id": "item:3506:fixed:251:0:26526:28670:96", "map": "map-2", "mapNum": 0, "nearby": [{"coords": [26590, 28670, 96], "dist2": 4096, "frame": 0, "mapNum": 0, "name": "shape_00f9", "nextItem": 4941, "npcNum": 0, "quality": 0, "shape": "shape:249"}, {"coords": [26622, 28670, 96], "dist2": 9216, "frame": 2, "mapNum": 0, "name": "shape_0063", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:99"}, {"coords": [26622, 28670, 96], "dist2": 9216, "frame": 0, "mapNum": 0, "name": "shape_0010", "nextItem": 4933, "npcNum": 0, "quality": 347, "shape": "shape:16"}, {"coords": [26622, 28670, 96], "dist2": 9216, "frame": 1, "mapNum": 0, "name": "shape_00f8", "nextItem": 4940, "npcNum": 0, "quality": 0, "shape": "shape:248"}, {"coords": [26398, 28670, 96], "dist2": 16384, "frame": 0, "mapNum": 0, "name": "shape_00fd", "nextItem": 4943, "npcNum": 0, "quality": 0, "shape": "shape:253"}, {"coords": [26494, 28894, 136], "dist2": 52800, "frame": 0, "mapNum": 200, "name": "shape_0476", "nextItem": 4827, "npcNum": 128, "quality": 28425, "shape": "shape:1142"}, {"coords": [26558, 28958, 96], "dist2": 83968, "frame": 0, "mapNum": 239, "name": "shape_0476", "nextItem": 4824, "npcNum": 64, "quality": 9, "shape": "shape:1142"}, {"coords": [26462, 28382, 96], "dist2": 87040, "frame": 8, "mapNum": 111, "name": "CMD_LINK", "nextItem": 4935, "npcNum": 97, "quality": 3334, "shape": "shape:1201"}], "nextItem": 4942, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
|
||||
[remorse]
|
||||
{"coords": [22782, 11326, 0], "id": "item:2327:fixed:251:0:22782:11326:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [23262, 10302, 112], "dist2": 1291520, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2988, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 1676864, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2839, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [25534, 12254, 0], "id": "item:2408:fixed:251:0:25534:12254:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 11710, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2753, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1857792, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2754, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [25534, 11710, 0], "id": "item:2411:fixed:251:0:25534:11710:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 12254, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2754, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1074432, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25534, 10302, 112], "dist2": 1995008, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2898, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25790, 10270, 112], "dist2": 2151680, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2800, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 2205248, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2753, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [22398, 13374, 0], "id": "item:2550:fixed:251:0:22398:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [21950, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2542, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2545, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [21950, 13374, 0], "id": "item:2554:fixed:251:0:21950:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [22398, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2545, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2542, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [20670, 15422, 0], "id": "item:2768:fixed:251:0:20670:15422:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [20350, 15294, 24], "dist2": 119360, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2530, "npcNum": 0, "quality": 14906, "shape": "shape:357"}, {"coords": [19998, 14558, 72], "dist2": 1203264, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2531, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2424, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [30910, 17502, 0], "id": "item:2988:fixed:251:0:30910:17502:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [31070, 16734, 40], "dist2": 617024, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2224, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2049, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [38462, 19518, 0], "id": "item:3216:fixed:251:0:38462:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38078, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1738, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 217152, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 293952, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38014, 19166, 136], "dist2": 343104, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38704, 18592, 152], "dist2": 939144, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1208576, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [39742, 20382, 24], "dist2": 2385472, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 3339, "npcNum": 0, "quality": 0, "shape": "shape:258"}], "nextItem": 1745, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [38078, 19518, 0], "id": "item:3225:fixed:251:0:38078:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38014, 19166, 136], "dist2": 146496, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38462, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1745, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 684096, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 760896, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38704, 18592, 152], "dist2": 1272456, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1650944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1738, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [32414, 20542, 0], "id": "item:3354:fixed:251:0:32414:20542:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [32926, 19998, 0], "dist2": 558080, "frame": 0, "mapNum": 0, "name": "shape_04de", "nextItem": 1767, "npcNum": 0, "quality": 14362, "shape": "shape:1246"}], "nextItem": 3361, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36062, 23614, 0], "id": "item:3782:fixed:251:0:36062:23614:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [36478, 23710, 32], "dist2": 183296, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [35966, 24030, 32], "dist2": 183296, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36926, 23710, 16], "dist2": 755968, "frame": 2, "mapNum": 0, "name": "MONITNS", "nextItem": 1099, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36030, 25054, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 962, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1130, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36030, 25054, 0], "id": "item:3932:fixed:251:0:36030:25054:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 1053696, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 2008064, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36062, 23614, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1130, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 962, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
|
||||
[remorse-101]
|
||||
{"coords": [22782, 11326, 0], "id": "item:2326:fixed:251:0:22782:11326:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [23262, 10302, 112], "dist2": 1291520, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 533, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 1676864, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 752, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 707, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [25534, 11710, 0], "id": "item:2402:fixed:251:0:25534:11710:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 12254, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 782, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1074432, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 664, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25534, 10302, 112], "dist2": 1995008, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 622, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25790, 10270, 112], "dist2": 2151680, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 639, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 2205248, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 752, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 783, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [25534, 12254, 0], "id": "item:2410:fixed:251:0:25534:12254:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 11710, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 783, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1857792, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 664, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 782, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [22398, 13374, 0], "id": "item:2536:fixed:251:0:22398:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [21950, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 940, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 937, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [21950, 13374, 0], "id": "item:2545:fixed:251:0:21950:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [22398, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 937, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 940, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [20670, 15422, 0], "id": "item:2753:fixed:251:0:20670:15422:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [20350, 15294, 24], "dist2": 119360, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1039, "npcNum": 0, "quality": 14906, "shape": "shape:357"}, {"coords": [19998, 14558, 72], "dist2": 1203264, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1038, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 1217, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [30910, 17502, 0], "id": "item:2982:fixed:251:0:30910:17502:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [31070, 16734, 40], "dist2": 617024, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1396, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 1566, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [38462, 19518, 0], "id": "item:3225:fixed:251:0:38462:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38078, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1987, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 217152, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1810, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 293952, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1809, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38014, 19166, 136], "dist2": 343104, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1811, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38638, 18602, 152], "dist2": 893136, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1759, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1208576, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1778, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [39742, 20382, 24], "dist2": 2385472, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 2104, "npcNum": 0, "quality": 0, "shape": "shape:258"}], "nextItem": 1980, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [38078, 19518, 0], "id": "item:3229:fixed:251:0:38078:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38014, 19166, 136], "dist2": 146496, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1811, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38462, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1980, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 684096, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1810, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 760896, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1809, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38638, 18602, 152], "dist2": 1175760, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1759, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1650944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1778, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1987, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [32414, 20542, 0], "id": "item:3347:fixed:251:0:32414:20542:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [32926, 19998, 0], "dist2": 558080, "frame": 0, "mapNum": 0, "name": "shape_04de", "nextItem": 1924, "npcNum": 0, "quality": 14362, "shape": "shape:1246"}], "nextItem": 2182, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36062, 23614, 0], "id": "item:3771:fixed:251:0:36062:23614:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 183296, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2765, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 183296, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 2764, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36926, 23710, 16], "dist2": 755968, "frame": 2, "mapNum": 0, "name": "MONITNS", "nextItem": 2844, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36030, 25054, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2960, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2748, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36030, 25054, 0], "id": "item:3950:fixed:251:0:36030:25054:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 1053696, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2765, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 2008064, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 2764, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36062, 23614, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2748, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2960, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
|
||||
[remorse-jp]
|
||||
{"coords": [22782, 11326, 0], "id": "item:2327:fixed:251:0:22782:11326:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [23262, 10302, 112], "dist2": 1291520, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2988, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 1676864, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2839, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [25534, 12254, 0], "id": "item:2408:fixed:251:0:25534:12254:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 11710, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2753, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1857792, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2754, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [25534, 11710, 0], "id": "item:2411:fixed:251:0:25534:11710:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [25534, 12254, 0], "dist2": 295936, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2754, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [26462, 11262, 112], "dist2": 1074432, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2883, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25534, 10302, 112], "dist2": 1995008, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2898, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [25790, 10270, 112], "dist2": 2151680, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2800, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [24062, 11518, 40], "dist2": 2205248, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 2726, "npcNum": 0, "quality": 50, "shape": "shape:357"}], "nextItem": 2753, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [22398, 13374, 0], "id": "item:2550:fixed:251:0:22398:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [21950, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2542, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2545, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [21950, 13374, 0], "id": "item:2554:fixed:251:0:21950:13374:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [22398, 13374, 0], "dist2": 200704, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 2545, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 2542, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [20670, 15422, 0], "id": "item:2768:fixed:251:0:20670:15422:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [20350, 15294, 24], "dist2": 119360, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2530, "npcNum": 0, "quality": 14906, "shape": "shape:357"}, {"coords": [19998, 14558, 72], "dist2": 1203264, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 2531, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2424, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [30910, 17502, 0], "id": "item:2988:fixed:251:0:30910:17502:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [31070, 16734, 40], "dist2": 617024, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 2224, "npcNum": 0, "quality": 0, "shape": "shape:357"}], "nextItem": 2049, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [38462, 19518, 0], "id": "item:3216:fixed:251:0:38462:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38078, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1738, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 217152, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 293952, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38014, 19166, 136], "dist2": 343104, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38704, 18592, 152], "dist2": 939144, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1208576, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [39742, 20382, 24], "dist2": 2385472, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 3339, "npcNum": 0, "quality": 0, "shape": "shape:258"}], "nextItem": 1745, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [38078, 19518, 0], "id": "item:3225:fixed:251:0:38078:19518:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [38014, 19166, 136], "dist2": 146496, "frame": 0, "mapNum": 0, "name": "MONITEW", "nextItem": 1923, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38462, 19518, 0], "dist2": 147456, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1745, "npcNum": 0, "quality": 0, "shape": "shape:251"}, {"coords": [38878, 19358, 136], "dist2": 684096, "frame": 1, "mapNum": 0, "name": "MONITEW", "nextItem": 1924, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [38878, 19198, 136], "dist2": 760896, "frame": 1, "mapNum": 0, "name": "MONITNS", "nextItem": 1925, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38704, 18592, 152], "dist2": 1272456, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 0, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [38846, 18494, 112], "dist2": 1650944, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1879, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1738, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [32414, 20542, 0], "id": "item:3354:fixed:251:0:32414:20542:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [32926, 19998, 0], "dist2": 558080, "frame": 0, "mapNum": 0, "name": "shape_04de", "nextItem": 1767, "npcNum": 0, "quality": 14362, "shape": "shape:1246"}], "nextItem": 3361, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36062, 23614, 0], "id": "item:3782:fixed:251:0:36062:23614:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [36478, 23710, 32], "dist2": 183296, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [35966, 24030, 32], "dist2": 183296, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36926, 23710, 16], "dist2": 755968, "frame": 2, "mapNum": 0, "name": "MONITNS", "nextItem": 1099, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36030, 25054, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 962, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 1130, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [36030, 25054, 0], "id": "item:3932:fixed:251:0:36030:25054:0", "map": "map-10", "mapNum": 0, "nearby": [{"coords": [35966, 24030, 32], "dist2": 1053696, "frame": 2, "mapNum": 0, "name": "MONITEW", "nextItem": 1176, "npcNum": 0, "quality": 0, "shape": "shape:357"}, {"coords": [36478, 23710, 32], "dist2": 2008064, "frame": 0, "mapNum": 0, "name": "MONITNS", "nextItem": 1177, "npcNum": 0, "quality": 0, "shape": "shape:258"}, {"coords": [36062, 23614, 0], "dist2": 2074624, "frame": 0, "mapNum": 0, "name": "shape_00fb", "nextItem": 1130, "npcNum": 0, "quality": 0, "shape": "shape:251"}], "nextItem": 962, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
|
||||
Nonzero payload examples
|
||||
|
||||
[regret]
|
||||
{"coords": [4926, 14302, 96], "id": "item:5149:fixed:251:0:4926:14302:96", "map": "map-29", "mapNum": 0, "nextItem": 1596, "npcNum": 0, "qhi": 0, "qlo": 23, "quality": 23}
|
||||
|
||||
[remorse]
|
||||
{"coords": [53118, 30558, 96], "id": "item:10711:fixed:251:0:53118:30558:96", "map": "map-141", "mapNum": 0, "nextItem": 799, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
{"coords": [64702, 34814, 96], "id": "item:12590:glob:251:0:64702:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [65438, 34814, 96], "id": "item:12594:glob:251:0:65438:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [53118, 30558, 96], "id": "item:11657:fixed:251:0:53118:30558:96", "map": "map-25", "mapNum": 0, "nextItem": 1078, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
{"coords": [64702, 34814, 96], "id": "item:13546:glob:251:0:64702:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [65438, 34814, 96], "id": "item:13550:glob:251:0:65438:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [53118, 30558, 96], "id": "item:10397:fixed:251:0:53118:30558:96", "map": "map-26", "mapNum": 0, "nextItem": 279, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
|
||||
[remorse-101]
|
||||
{"coords": [53118, 30558, 96], "id": "item:10711:fixed:251:0:53118:30558:96", "map": "map-141", "mapNum": 0, "nextItem": 799, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
{"coords": [64702, 34814, 96], "id": "item:12590:glob:251:0:64702:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [65438, 34814, 96], "id": "item:12594:glob:251:0:65438:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [53118, 30558, 96], "id": "item:11664:fixed:251:0:53118:30558:96", "map": "map-25", "mapNum": 0, "nextItem": 1561, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
{"coords": [64702, 34814, 96], "id": "item:13546:glob:251:0:64702:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [65438, 34814, 96], "id": "item:13550:glob:251:0:65438:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [53118, 30558, 96], "id": "item:10397:fixed:251:0:53118:30558:96", "map": "map-26", "mapNum": 0, "nextItem": 279, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
|
||||
[remorse-jp]
|
||||
{"coords": [53118, 30558, 96], "id": "item:10711:fixed:251:0:53118:30558:96", "map": "map-141", "mapNum": 0, "nextItem": 799, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
{"coords": [64702, 34814, 96], "id": "item:12590:glob:251:0:64702:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [65438, 34814, 96], "id": "item:12594:glob:251:0:65438:34814:96", "map": "map-141", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [53118, 30558, 96], "id": "item:11657:fixed:251:0:53118:30558:96", "map": "map-25", "mapNum": 0, "nextItem": 1078, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
{"coords": [64702, 34814, 96], "id": "item:13546:glob:251:0:64702:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [65438, 34814, 96], "id": "item:13550:glob:251:0:65438:34814:96", "map": "map-25", "mapNum": 8, "nextItem": 0, "npcNum": 0, "qhi": 0, "qlo": 0, "quality": 0}
|
||||
{"coords": [53118, 30558, 96], "id": "item:10397:fixed:251:0:53118:30558:96", "map": "map-26", "mapNum": 0, "nextItem": 279, "npcNum": 2, "qhi": 3, "qlo": 60, "quality": 828}
|
||||
Loading…
Add table
Add a link
Reference in a new issue