port 2D step 2: 40250 item_proto/mob_proto through CLZO and pack://locale/<lang>/
proto.cpp reads the 40250 MIPX/MMPT tables with the ported CLZO/TEA (156-byte TItemTable, 255-byte SMobTable), replacing the m2dev XChaCha20 236/335 reader. Names follow CItemData::GetName / GetMonsterName (szLocaleName, cp1252). GDScript callers load AssetRoot.locale_file() = pack://locale/en/<name>, which makes the EterPack chain runtime-reachable; port-map entries for the functions a coverage run hit move to PORTED/ADAPTED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -490,3 +490,4 @@
|
|||||||
{"time":"2026-09-22T16:00:00Z","event":"pack_inventory","scope":"Client/pack (batch 2R)","tool":"tools/epk_scan/epk_scan.py","report":"audit/packs/2R-scan.json","totals":{"registered_packs":136,"entries":54891,"unique_paths":52609,"NONE":20727,"COMPRESS":29812,"SECURITY":4352,"PANAMA":0,"HYBRIDCRYPT":0,"decode_failures":0,"overrides":2282,"overrides_different":2194,"case_collisions":0},"findings":["first-registered pack wins (CEterFileDict multimap, VS2022 STL)","short-layout packer in metin2_patch_{sd,mineral,mundi}: 4 SECURITY files rejected by 40250 CRC check","no server-delivered pack keys needed"]}
|
{"time":"2026-09-22T16:00:00Z","event":"pack_inventory","scope":"Client/pack (batch 2R)","tool":"tools/epk_scan/epk_scan.py","report":"audit/packs/2R-scan.json","totals":{"registered_packs":136,"entries":54891,"unique_paths":52609,"NONE":20727,"COMPRESS":29812,"SECURITY":4352,"PANAMA":0,"HYBRIDCRYPT":0,"decode_failures":0,"overrides":2282,"overrides_different":2194,"case_collisions":0},"findings":["first-registered pack wins (CEterFileDict multimap, VS2022 STL)","short-layout packer in metin2_patch_{sd,mineral,mundi}: 4 SECURITY files rejected by 40250 CRC check","no server-delivered pack keys needed"]}
|
||||||
{"time":"2026-09-23T10:00:00Z","event":"port_round","unit":"EterPack/*, EterBase/{tea,lzo,Timer,Stl,Random}.cpp + platform EterBase/{CRC32,FileBase,MappedFile,Debug,Utils}","ported":["EterPack/EterPack.cpp","EterPack/EterPackManager.cpp","EterPack/EterPackCursor.cpp","EterPack/EterPackPolicy_CSHybridCrypt.cpp","EterBase/tea.cpp","EterBase/lzo.cpp","EterBase/Timer.cpp","EterBase/Stl.cpp","EterBase/Random.cpp"],"status_note":"mirror copies + platform FileBase/MappedFile/CRC32/Debug/Utils; not runtime-reachable until the asset_io pack backend (PORT-PLAN §5 step 5), so port-map statuses stay TODO","divergent":[],"needs_live":[],"corrections":["2R registered_packs 136 was wrong: 124 Index pairs -> 119 RegisterPack successes, 103 distinct packs + root (PORT-PLAN, memory)"],"findings":["libc++ unordered_multimap also keeps first-registered-wins: 0 wrong winners over 2282 overrides","exactly the 4 short-layout SECURITY files fail CEterPackManager::Get","pack/Index spells Etc for ETC.eix: CEterPack::CreateIndexFile fopens directly, so the bootstrap must resolve the on-disk dbname"],"keys":"EterPack TEA keys generated at configure time into the build tree (EterPackKeys.generated.h), never committed","tests":["extension/tests/port_eterpack_test.cpp PASS (macOS, 52605 files / 2023459178 bytes)","script/port_gate.sh macos/android/ios/windows PASS"]}
|
{"time":"2026-09-23T10:00:00Z","event":"port_round","unit":"EterPack/*, EterBase/{tea,lzo,Timer,Stl,Random}.cpp + platform EterBase/{CRC32,FileBase,MappedFile,Debug,Utils}","ported":["EterPack/EterPack.cpp","EterPack/EterPackManager.cpp","EterPack/EterPackCursor.cpp","EterPack/EterPackPolicy_CSHybridCrypt.cpp","EterBase/tea.cpp","EterBase/lzo.cpp","EterBase/Timer.cpp","EterBase/Stl.cpp","EterBase/Random.cpp"],"status_note":"mirror copies + platform FileBase/MappedFile/CRC32/Debug/Utils; not runtime-reachable until the asset_io pack backend (PORT-PLAN §5 step 5), so port-map statuses stay TODO","divergent":[],"needs_live":[],"corrections":["2R registered_packs 136 was wrong: 124 Index pairs -> 119 RegisterPack successes, 103 distinct packs + root (PORT-PLAN, memory)"],"findings":["libc++ unordered_multimap also keeps first-registered-wins: 0 wrong winners over 2282 overrides","exactly the 4 short-layout SECURITY files fail CEterPackManager::Get","pack/Index spells Etc for ETC.eix: CEterPack::CreateIndexFile fopens directly, so the bootstrap must resolve the on-disk dbname"],"keys":"EterPack TEA keys generated at configure time into the build tree (EterPackKeys.generated.h), never committed","tests":["extension/tests/port_eterpack_test.cpp PASS (macOS, 52605 files / 2023459178 bytes)","script/port_gate.sh macos/android/ios/windows PASS"]}
|
||||||
{"time":"2026-09-23T12:00:00Z","event":"port_round","unit":"platform UserInterface PackInitialize + EterBase/FileLoader.cpp + pack:// asset_io backend (PORT-PLAN §5 step 5b)","ported":["EterBase/FileLoader.cpp"],"deleted":[],"divergent":[],"needs_live":[],"status_note":"PackInitialize (platform, on-disk dbname resolution, no LogBoxf) + mtpack40250 backend + Metin2Pack node; no runtime reader uses pack:// until the proto/GDScript switch, so port-map statuses stay TODO","tests":["extension/tests/port_eterpack_test.cpp via PackSingletons+PackInitialize(\"pack\"): 103 packs, oracle order, PASS"],"commit":"5b"}
|
{"time":"2026-09-23T12:00:00Z","event":"port_round","unit":"platform UserInterface PackInitialize + EterBase/FileLoader.cpp + pack:// asset_io backend (PORT-PLAN §5 step 5b)","ported":["EterBase/FileLoader.cpp"],"deleted":[],"divergent":[],"needs_live":[],"status_note":"PackInitialize (platform, on-disk dbname resolution, no LogBoxf) + mtpack40250 backend + Metin2Pack node; no runtime reader uses pack:// until the proto/GDScript switch, so port-map statuses stay TODO","tests":["extension/tests/port_eterpack_test.cpp via PackSingletons+PackInitialize(\"pack\"): 103 packs, oracle order, PASS"],"commit":"5b"}
|
||||||
|
{"time":"2026-09-23T12:30:00Z","event":"port_round","unit":"proto reader -> 40250 item_proto/mob_proto via CLZO + pack://locale/<lang>/ (PORT-PLAN §5 step 2 + step 5 runtime switch)","ported":["EterPack/EterPack.cpp (16 fns)","EterPack/EterPackManager.cpp (14)","EterPack/EterPackPolicy_CSHybridCrypt.cpp (1)","EterBase/lzo.cpp (11)","EterBase/tea.cpp (2)","EterBase/Stl.cpp (2)"],"adapted":["UserInterface/UserInterface.cpp PackInitialize","EterBase/FileLoader.cpp (5)","EterBase/MappedFile.cpp (9)","EterBase/FileBase.cpp (5)","EterBase/CRC32.cpp GetCRC32"],"deleted":["extension/src/proto/proto.cpp m2dev reader: XChaCha20 + MCOZ, 236-byte item / 335-byte mob strides"],"divergent":[],"needs_live":[],"status_note":"statuses set only for the functions a clang coverage run of proto_test + port_eterpack_test hit; the rest stay TODO. SMobTable is a local #pragma pack(1) mirror in proto.cpp (static_assert 255) until batch 2P ports GameLib/PythonNonPlayer. name = cp1252(szLocaleName) as CItemData::GetName / CPythonNonPlayer::GetMonsterName","tests":["extension/tests/proto_test.cpp: 5743 items / 1334 mobs from pack://locale/en PASS","extension/tests/proto_item_layout_test.cpp 156/255 strides, 236/335 rejected PASS","godot netbridge/equip_model/remote_player/mob_view PASS"],"commit":"2D-proto"}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/CRC32.cpp",
|
||||||
|
"reference_sha256": "80bbbc71a273097830279d96c7d7ac28df6d043675711eb1b6f28a9801a83016",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"GetCRC32": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/CRC32.cpp:GetCRC32"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CRC32 table implementation; same polynomial and GetCRC32 values (checked against every stored filename_crc)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"GetCaseCRC32": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"GetHFILECRC32": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"GetFileCRC32": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"GetFileSize": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/FileBase.cpp",
|
||||||
|
"reference_sha256": "262fcad3ae6ec2ee5c988c1beea0cfa2cc2adffc5e544fe36885bef1dc6dc71d",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"CFileBase::CFileBase": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileBase.cpp:CFileBase::CFileBase"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CFileBase over std::fstream; open falls back to a case-insensitive path match (NTFS spelling)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CFileBase::~CFileBase": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileBase.cpp:CFileBase::~CFileBase"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CFileBase over std::fstream; open falls back to a case-insensitive path match (NTFS spelling)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CFileBase::GetFileName": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::Destroy": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileBase.cpp:CFileBase::Destroy"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CFileBase over std::fstream; open falls back to a case-insensitive path match (NTFS spelling)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CFileBase::Close": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileBase.cpp:CFileBase::Close"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CFileBase over std::fstream; open falls back to a case-insensitive path match (NTFS spelling)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CFileBase::Create": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileBase.cpp:CFileBase::Create"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CFileBase over std::fstream; open falls back to a case-insensitive path match (NTFS spelling)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CFileBase::Size": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::SeekCur": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::Seek": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::GetPosition": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::Write": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::Read": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CFileBase::IsNull": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/FileLoader.cpp",
|
||||||
|
"reference_sha256": "aaf832b188b7fff6350c7e3a41147fa4abfa2873ce160022c433dcac848c6875",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"CMemoryTextFileLoader::CMemoryTextFileLoader": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileLoader.cpp:CMemoryTextFileLoader::CMemoryTextFileLoader"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "40250 file verbatim, one change: the high-bit test casts to signed char (char is unsigned on arm64)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::~CMemoryTextFileLoader": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileLoader.cpp:CMemoryTextFileLoader::~CMemoryTextFileLoader"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "40250 file verbatim, one change: the high-bit test casts to signed char (char is unsigned on arm64)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::SplitLineByTab": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::SplitLine2": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::SplitLine": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::GetLineCount": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileLoader.cpp:CMemoryTextFileLoader::GetLineCount"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "40250 file verbatim, one change: the high-bit test casts to signed char (char is unsigned on arm64)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::CheckLineIndex": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::GetLineString": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileLoader.cpp:CMemoryTextFileLoader::GetLineString"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "40250 file verbatim, one change: the high-bit test casts to signed char (char is unsigned on arm64)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMemoryTextFileLoader::Bind": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/FileLoader.cpp:CMemoryTextFileLoader::Bind"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "40250 file verbatim, one change: the high-bit test casts to signed char (char is unsigned on arm64)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::GetSize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::GetPosition": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::IsReadableSize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::Read": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::GetCurrentPositionPointer": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::CMemoryFileLoader": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMemoryFileLoader::~CMemoryFileLoader": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::GetSize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::Read": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::Open": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::Close": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::Initialize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::CDiskFileLoader": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CDiskFileLoader::~CDiskFileLoader": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/MappedFile.cpp",
|
||||||
|
"reference_sha256": "82d65b1bb2bdc91038be929e079212af33cf963e5d4722d10256bd1df4341ee9",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"CMappedFile::CMappedFile": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::CMappedFile"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::~CMappedFile": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::~CMappedFile"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::Create": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::Create"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::Get": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::Link": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::Link"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::BindLZObject": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::BindLZObject"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::BindLZObjectWithBufferedSize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::AppendDataBlock": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::Destroy": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::Destroy"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::Seek": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::Map": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::Map"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::GetCurrentSeekPoint": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::Size": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::Size"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
},
|
||||||
|
"CMappedFile::GetPosition": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::Read": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::GetSeekPosition": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMappedFile::Unmap": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/EterBase/MappedFile.cpp:CMappedFile::Unmap"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform CMappedFile: std::ifstream read into an owned buffer instead of CreateFileMapping/MapViewOfFile; same Create/Map/Link/BindLZObject contract",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/Stl.cpp",
|
||||||
|
"reference_sha256": "239c44c77e8dba08e91f8838447ad2f15ab19ac6328650d13da37b9600a249cd",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"korean_tolower": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/Stl.cpp:korean_tolower"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"stl_static_string": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"stl_lowers": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/Stl.cpp:stl_lowers"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"split_string": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/lzo.cpp",
|
||||||
|
"reference_sha256": "7a236172e9900733e119944ffabf6762a999d4346fde9241e3027836d72c1354",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"CLZObject::CLZObject": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::CLZObject"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::Initialize": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::Initialize"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::Clear": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::Clear"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::~CLZObject": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::~CLZObject"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::GetSize": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::GetSize"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::BeginCompress": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZObject::BeginCompressInBuffer": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZObject::Compress": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZObject::BeginDecompress": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::BeginDecompress"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::Decompress": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::Decompress"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::Encrypt": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZObject::__Decrypt": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZObject::__Decrypt"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZObject::AllocBuffer": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZO::CLZO": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZO::CLZO"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZO::~CLZO": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZO::~CLZO"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZO::CompressMemory": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZO::CompressEncryptedMemory": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CLZO::Decompress": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/lzo.cpp:CLZO::Decompress"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CLZO::GetWorkMemory": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterBase/tea.cpp",
|
||||||
|
"reference_sha256": "aeeb57c96a9febd22d11e221462f5f6b18830370847f45e74eec5fb085988007",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"tea_code": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"tea_decode": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/tea.cpp:tea_decode"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"tea_encrypt": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"tea_decrypt": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterBase/tea.cpp:tea_decrypt"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterPack/EterPack.cpp",
|
||||||
|
"reference_sha256": "7a38554a275da2fe7d16ea4c60a3ab644dbd3ed7845d19c385819045e74e6787",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"CMakePackLog::SetFileName": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::GetSingleton": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::CMakePackLog": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::~CMakePackLog": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::__IsLogMode": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::Writef": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::Writenf": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::Write": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::__Write": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::WriteErrorf": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::WriteErrornf": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::WriteError": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::__WriteError": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CMakePackLog::FlushError": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::CEterPack": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::CEterPack"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::~CEterPack": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::~CEterPack"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::Destroy": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::Destroy"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::GetPathName": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::Create": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::Create"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::DecryptIV": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::DecryptIndexFile": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::EncryptIndexFile": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::__BuildIndex": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::__BuildIndex"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::Get": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::Get"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::Get2": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::Get2"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::Delete": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::Extract": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GetNames": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::Put": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GenerateMD5Hash": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GetFragmentSize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::CreateIndexFile": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::CreateIndexFile"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::WriteIndex": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GetFreeBlockIndex": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::GetFreeBlockIndex"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::PushFreeIndex": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::PushFreeIndex"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::GetNewIndexPosition": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::NewIndex": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::FindIndex": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::FindIndex"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::IsExist": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::IsExist"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::CreateDataFile": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::CreateDataFile"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::GetNewDataPosition": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::ReadData": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::WriteData": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::WriteNewData": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GetIndexMap": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::DeleteUnreferencedData": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GetDBName": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterPack::GetDBName"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPack::__CreateFileNameKey_Panama": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::__Encrypt_Panama": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::__Decrypt_Panama": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPack::GetPackPolicy_HybridCrypt": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterFileDict::InsertItem": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterFileDict::InsertItem"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterFileDict::UpdateItem": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterFileDict::GetItem": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPack.cpp:CEterFileDict::GetItem"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterPack/EterPackManager.cpp",
|
||||||
|
"reference_sha256": "335a4bf569f4cf0b2154f39ca35f1e294f1a514641e082290f1f1ce6c0a336a9",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"CEterPackManager::FindPack": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::SetCacheMode": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::SetCacheMode"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::SetRelativePathMode": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::ConvertFileName": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::ConvertFileName"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::CompareName": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::LoadStaticCache": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::__FindCache": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::__FindCache"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::__ClearCacheMap": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::__ClearCacheMap"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::Get": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::Get"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::GetFromPack": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::GetFromPack"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::ArrangeMemoryMappedPack": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::GetFromFile": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::GetFromFile"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::isExistInPack": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::isExistInPack"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::isExist": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::isExist"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::RegisterRootPack": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::RegisterRootPack"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::GetRootPackFileName": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::DecryptPackIV": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::RegisterPackWhenPackMaking": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::RegisterPack": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::RegisterPack"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::SetSearchMode": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::SetSearchMode"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::GetSearchMode": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::CEterPackManager": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::CEterPackManager"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::~CEterPackManager": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackManager.cpp:CEterPackManager::~CEterPackManager"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"CEterPackManager::RetrieveHybridCryptPackKeys": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::RetrieveHybridCryptPackSDB": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CEterPackManager::WriteHybridCryptPackInfo": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"reference": "EterPack/EterPackPolicy_CSHybridCrypt.cpp",
|
||||||
|
"reference_sha256": "02db1037e746468b036a3ec2004a4ebffa568d5433fe6e94bc50729ed69e6e0b",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"GetFileExt": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::~EterPackPolicy_CSHybridCrypt": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/port/EterPack/EterPackPolicy_CSHybridCrypt.cpp:EterPackPolicy_CSHybridCrypt::~EterPackPolicy_CSHybridCrypt"
|
||||||
|
],
|
||||||
|
"status": "PORTED"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::IsContainingCryptKey": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::GenerateCryptKey": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::GetPerFileCryptKey": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::EncryptMemory": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::DecryptMemory": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::WriteCryptKeyToFile": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::ReadCryptKeyInfoFromStream": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::GenerateSupplementaryDataBlock": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::GetSupplementaryDataBlock": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::IsContainingSDBFile": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::WriteSupplementaryDataBlockToFile": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"EterPackPolicy_CSHybridCrypt::ReadSupplementatyDataBlockFromStream": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"reference": "UserInterface/UserInterface.cpp",
|
||||||
|
"reference_sha256": "beed987e4b1de627acafe7ea6f5fde989b8f4b33c8643fe2abb0f7152ea4a9eb",
|
||||||
|
"priority": "P4",
|
||||||
|
"contracts": [],
|
||||||
|
"functions": {
|
||||||
|
"ApplicationSetErrorString": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CheckPythonLibraryFilenames": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"ApplicationStringTable_Initialize": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"ApplicationStringTable_GetString": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"ApplicationStringTable_GetStringz": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"PackInitialize": {
|
||||||
|
"impl": [
|
||||||
|
"extension/src/platform/UserInterface/UserInterface.cpp:PackInitialize"
|
||||||
|
],
|
||||||
|
"status": "ADAPTED",
|
||||||
|
"note": "platform PackInitialize: Index pack names resolved to the on-disk .eix spelling case-insensitively; no LogBoxf; CTextFileLoader::SetCacheMode / CSoundData::SetPackMode not called (units not ported, neither affects pack registration)",
|
||||||
|
"test": "extension/tests/port_eterpack_test.cpp (103 packs, 54891 entries, first-registered wins, 4 SECURITY rejects); extension/tests/proto_test.cpp (40250 locale/en item_proto/mob_proto through pack://)"
|
||||||
|
},
|
||||||
|
"RunMainScript": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"Main": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"CreateMetin2GameMutex": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"DestroyMetin2GameMutex": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__ErrorPythonLibraryIsNotExist": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__IsTimeStampOption": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__PrintTimeStamp": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__IsLocaleOption": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__IsLocaleVersion": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__IsOpenIDAuthKeyOption": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"__IsOpenIDTestOption": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"WinMain": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"GrannyError": {
|
||||||
|
"status": "TODO"
|
||||||
|
},
|
||||||
|
"Setup": {
|
||||||
|
"status": "TODO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,10 +50,10 @@ target_include_directories(mtpack PUBLIC src/pack)
|
|||||||
target_link_libraries(mtpack PUBLIC mt3p::sodium mt3p::zstd xrender::formats)
|
target_link_libraries(mtpack PUBLIC mt3p::sodium mt3p::zstd xrender::formats)
|
||||||
target_compile_features(mtpack PUBLIC cxx_std_20)
|
target_compile_features(mtpack PUBLIC cxx_std_20)
|
||||||
|
|
||||||
# --- mtproto: item_proto / mob_proto reader (libsodium + LZO) ---
|
# --- mtproto: item_proto / mob_proto reader, 40250 format (CLZO/TEA from port_platform) ---
|
||||||
add_library(mtproto STATIC src/proto/proto.cpp)
|
add_library(mtproto STATIC src/proto/proto.cpp)
|
||||||
target_include_directories(mtproto PUBLIC src/proto)
|
target_include_directories(mtproto PUBLIC src/proto)
|
||||||
target_link_libraries(mtproto PUBLIC mt3p::sodium mt3p::minilzo)
|
target_link_libraries(mtproto PUBLIC port_platform)
|
||||||
target_compile_features(mtproto PUBLIC cxx_std_20)
|
target_compile_features(mtproto PUBLIC cxx_std_20)
|
||||||
|
|
||||||
# --- port_logic + port_platform: 40250 mirror (extension/src/port) and its platform layer
|
# --- port_logic + port_platform: 40250 mirror (extension/src/port) and its platform layer
|
||||||
@@ -146,10 +146,10 @@ if(BUILD_TESTING AND MT_HOST_BUILD)
|
|||||||
|
|
||||||
add_executable(proto_test tests/proto_test.cpp)
|
add_executable(proto_test tests/proto_test.cpp)
|
||||||
target_link_libraries(proto_test PRIVATE mtproto)
|
target_link_libraries(proto_test PRIVATE mtproto)
|
||||||
add_test(NAME proto.item_mob COMMAND $<TARGET_FILE:proto_test>)
|
# MT_40250_CLIENT comes from src/port/CMakeLists.txt; skipped (77) without the client, failed under
|
||||||
if(DEFINED ENV{M2_ASSETS})
|
# MT_ASSETS_STRICT=1.
|
||||||
set_tests_properties(proto.item_mob PROPERTIES ENVIRONMENT "M2_ASSETS=$ENV{M2_ASSETS}")
|
add_test(NAME proto.item_mob COMMAND $<TARGET_FILE:proto_test> ${MT_40250_CLIENT})
|
||||||
endif()
|
set_tests_properties(proto.item_mob PROPERTIES SKIP_RETURN_CODE 77)
|
||||||
|
|
||||||
add_executable(proto_item_layout_test tests/proto_item_layout_test.cpp)
|
add_executable(proto_item_layout_test tests/proto_item_layout_test.cpp)
|
||||||
target_link_libraries(proto_item_layout_test PRIVATE mtproto)
|
target_link_libraries(proto_item_layout_test PRIVATE mtproto)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// Platform-layer pieces of 40250 UserInterface/UserInterface.cpp (the WinMain unit). 40250 declares these nowhere:
|
// Platform-layer pieces of 40250 UserInterface/UserInterface.cpp (the WinMain unit). 40250 declares these nowhere:
|
||||||
// Main() in the same file calls them. The extension's pack bootstrap calls them instead.
|
// Main() in the same file calls them. The extension's pack bootstrap calls them instead.
|
||||||
|
|
||||||
// 40250: PackInitialize. Registers <folder>/Index the way the 40250 client does at startup. The CLZO and
|
// 40250: PackInitialize
|
||||||
// CEterPackManager singletons it needs must already exist (see PackSingletons).
|
// Registers <folder>/Index the way the 40250 client does at startup. The CLZO and CEterPackManager singletons
|
||||||
|
// it needs must already exist (see PackSingletons).
|
||||||
bool PackInitialize(const char * c_pszFolder);
|
bool PackInitialize(const char * c_pszFolder);
|
||||||
|
|
||||||
// The two function-static singletons 40250 Main() creates before PackInitialize("pack")
|
// The two function-static singletons 40250 Main() creates before PackInitialize("pack")
|
||||||
|
|||||||
@@ -369,13 +369,13 @@ class CItemData
|
|||||||
typedef struct SItemLimit
|
typedef struct SItemLimit
|
||||||
{
|
{
|
||||||
BYTE bType;
|
BYTE bType;
|
||||||
long lValue;
|
LONG lValue; // PORT: fixed-width Win32 long (item_proto record layout)
|
||||||
} TItemLimit;
|
} TItemLimit;
|
||||||
|
|
||||||
typedef struct SItemApply
|
typedef struct SItemApply
|
||||||
{
|
{
|
||||||
BYTE bType;
|
BYTE bType;
|
||||||
long lValue;
|
LONG lValue; // PORT: fixed-width Win32 long (item_proto record layout)
|
||||||
} TItemApply;
|
} TItemApply;
|
||||||
|
|
||||||
typedef struct SItemTable
|
typedef struct SItemTable
|
||||||
@@ -400,14 +400,15 @@ class CItemData
|
|||||||
|
|
||||||
TItemLimit aLimits[ITEM_LIMIT_MAX_NUM];
|
TItemLimit aLimits[ITEM_LIMIT_MAX_NUM];
|
||||||
TItemApply aApplies[ITEM_APPLY_MAX_NUM];
|
TItemApply aApplies[ITEM_APPLY_MAX_NUM];
|
||||||
long alValues[ITEM_VALUES_MAX_NUM];
|
LONG alValues[ITEM_VALUES_MAX_NUM]; // PORT: fixed-width Win32 long
|
||||||
long alSockets[ITEM_SOCKET_MAX_NUM];
|
LONG alSockets[ITEM_SOCKET_MAX_NUM]; // PORT: fixed-width Win32 long
|
||||||
DWORD dwRefinedVnum;
|
DWORD dwRefinedVnum;
|
||||||
WORD wRefineSet;
|
WORD wRefineSet;
|
||||||
BYTE bAlterToMagicItemPct;
|
BYTE bAlterToMagicItemPct;
|
||||||
BYTE bSpecular;
|
BYTE bSpecular;
|
||||||
BYTE bGainSocketPct;
|
BYTE bGainSocketPct;
|
||||||
} TItemTable;
|
} TItemTable;
|
||||||
|
static_assert(sizeof(SItemTable) == 156, "item_proto MIPX stride"); // PORT: 40250 ILP32 layout
|
||||||
|
|
||||||
// typedef struct SItemTable
|
// typedef struct SItemTable
|
||||||
// {
|
// {
|
||||||
|
|||||||
+172
-164
@@ -1,22 +1,87 @@
|
|||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
|
|
||||||
#include <sodium.h>
|
#include "EterBase/StdAfx.h"
|
||||||
|
#include "EterBase/lzo.h"
|
||||||
|
#include "GameLib/StdAfx.h"
|
||||||
|
#include "GameLib/ItemData.h"
|
||||||
|
#include "../platform/UserInterface/UserInterface.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <lzo/lzo1x.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace mtproto {
|
namespace mtproto {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr uint32_t FOURCC_MIPX = 0x5850494D; // "MIPX" bytes 4D 49 50 58 (LE u32)
|
// CPythonNonPlayer::SMobTable (UserInterface/PythonNonPlayer.h), the 40250 ILP32 layout. The mirror of that header
|
||||||
constexpr uint32_t FOURCC_MIPT = 0x5450494D; // "MIPT"
|
// needs UserInterface/StdAfx.h, which includes the Python headers (after batch 2P); until then the table is here.
|
||||||
constexpr uint32_t FOURCC_MMPT = 0x54504D4D; // "MMPT"
|
#pragma pack(push, 1)
|
||||||
constexpr uint32_t FOURCC_MCOZ = 0x5A4F434D; // "MCOZ"
|
struct SMobSkillLevel {
|
||||||
|
DWORD dwVnum;
|
||||||
|
BYTE bLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SMobTable {
|
||||||
|
DWORD dwVnum;
|
||||||
|
char szName[24 + 1]; // CHARACTER_NAME_MAX_LEN (UserInterface/StdAfx.h)
|
||||||
|
char szLocaleName[24 + 1];
|
||||||
|
|
||||||
|
BYTE bType;
|
||||||
|
BYTE bRank;
|
||||||
|
BYTE bBattleType;
|
||||||
|
BYTE bLevel;
|
||||||
|
BYTE bSize;
|
||||||
|
|
||||||
|
DWORD dwGoldMin;
|
||||||
|
DWORD dwGoldMax;
|
||||||
|
DWORD dwExp;
|
||||||
|
DWORD dwMaxHP;
|
||||||
|
BYTE bRegenCycle;
|
||||||
|
BYTE bRegenPercent;
|
||||||
|
WORD wDef;
|
||||||
|
|
||||||
|
DWORD dwAIFlag;
|
||||||
|
DWORD dwRaceFlag;
|
||||||
|
DWORD dwImmuneFlag;
|
||||||
|
|
||||||
|
BYTE bStr, bDex, bCon, bInt;
|
||||||
|
DWORD dwDamageRange[2];
|
||||||
|
|
||||||
|
short sAttackSpeed;
|
||||||
|
short sMovingSpeed;
|
||||||
|
BYTE bAggresiveHPPct;
|
||||||
|
WORD wAggressiveSight;
|
||||||
|
WORD wAttackRange;
|
||||||
|
|
||||||
|
char cEnchants[6]; // MOB_ENCHANTS_MAX_NUM
|
||||||
|
char cResists[11]; // MOB_RESISTS_MAX_NUM
|
||||||
|
|
||||||
|
DWORD dwResurrectionVnum;
|
||||||
|
DWORD dwDropItemVnum;
|
||||||
|
|
||||||
|
BYTE bMountCapacity;
|
||||||
|
BYTE bOnClickType;
|
||||||
|
|
||||||
|
BYTE bEmpire;
|
||||||
|
char szFolder[64 + 1];
|
||||||
|
float fDamMultiply;
|
||||||
|
DWORD dwSummonVnum;
|
||||||
|
DWORD dwDrainSP;
|
||||||
|
DWORD dwMonsterColor;
|
||||||
|
DWORD dwPolymorphItemVnum;
|
||||||
|
|
||||||
|
SMobSkillLevel Skills[5]; // MOB_SKILL_MAX_NUM
|
||||||
|
|
||||||
|
BYTE bBerserkPoint;
|
||||||
|
BYTE bStoneSkinPoint;
|
||||||
|
BYTE bGodSpeedPoint;
|
||||||
|
BYTE bDeathBlowPoint;
|
||||||
|
BYTE bRevivePoint;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
static_assert(sizeof(SMobTable) == 255, "mob_proto record");
|
||||||
|
|
||||||
|
using TItemTable = CItemData::TItemTable;
|
||||||
|
|
||||||
uint32_t rd_u32(const uint8_t *p) {
|
uint32_t rd_u32(const uint8_t *p) {
|
||||||
uint32_t v;
|
uint32_t v;
|
||||||
@@ -24,81 +89,12 @@ uint32_t rd_u32(const uint8_t *p) {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The fork's tea_decrypt (EterBase/tea.cpp): XChaCha20 with key/nonce derived from
|
std::string cstr(const char *p, size_t maxn) {
|
||||||
// the 16-byte input key via BLAKE2b. size is rounded up to a multiple of 8.
|
|
||||||
void tea_decrypt(uint8_t *dst, const uint8_t *src, const std::array<uint32_t, 4> &key32,
|
|
||||||
size_t size) {
|
|
||||||
uint8_t key16[16];
|
|
||||||
std::memcpy(key16, key32.data(), 16); // 4 LE dwords -> raw bytes
|
|
||||||
|
|
||||||
uint8_t dkey[crypto_stream_xchacha20_KEYBYTES];
|
|
||||||
uint8_t nonce[crypto_stream_xchacha20_NONCEBYTES];
|
|
||||||
crypto_generichash(dkey, sizeof(dkey), key16, 16,
|
|
||||||
reinterpret_cast<const uint8_t *>("M2DevPackEncrypt"), 16);
|
|
||||||
uint8_t nonce_seed[crypto_stream_xchacha20_NONCEBYTES + 8];
|
|
||||||
crypto_generichash(nonce_seed, sizeof(nonce_seed), key16, 16,
|
|
||||||
reinterpret_cast<const uint8_t *>("M2DevNonce"), 10);
|
|
||||||
std::memcpy(nonce, nonce_seed, sizeof(nonce));
|
|
||||||
|
|
||||||
size_t rs = (size % 8 == 0) ? size : size + 8 - (size % 8);
|
|
||||||
crypto_stream_xchacha20_xor(dst, src, rs, nonce, dkey);
|
|
||||||
sodium_memzero(dkey, sizeof(dkey));
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLZO container -> decompressed bytes.
|
|
||||||
bool clzo_decompress(const uint8_t *blob, size_t blob_len, const std::array<uint32_t, 4> &key,
|
|
||||||
std::vector<uint8_t> &out, std::string *err) {
|
|
||||||
if (blob_len < 20 || rd_u32(blob) != FOURCC_MCOZ) {
|
|
||||||
if (err) *err = "CLZO: bad MCOZ header";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const uint32_t enc_size = rd_u32(blob + 4);
|
|
||||||
const uint32_t comp_size = rd_u32(blob + 8);
|
|
||||||
const uint32_t real_size = rd_u32(blob + 12);
|
|
||||||
|
|
||||||
out.assign(real_size, 0);
|
|
||||||
lzo_uint out_len = real_size;
|
|
||||||
int r;
|
|
||||||
|
|
||||||
if (enc_size > 0) {
|
|
||||||
size_t rs = (enc_size % 8 == 0) ? enc_size : enc_size + 8 - (enc_size % 8);
|
|
||||||
if (16 + rs > blob_len) {
|
|
||||||
if (err) *err = "CLZO: encrypted region past end";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::vector<uint8_t> dec(rs);
|
|
||||||
tea_decrypt(dec.data(), blob + 16, key, enc_size); // src = blob+16 (== m_pbIn-4)
|
|
||||||
if (rd_u32(dec.data()) != FOURCC_MCOZ) {
|
|
||||||
if (err) *err = "CLZO: wrong key (inner MCOZ mismatch)";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
r = lzo1x_decompress_safe(dec.data() + 4, comp_size, out.data(), &out_len, nullptr);
|
|
||||||
} else {
|
|
||||||
if (20u + comp_size > blob_len) {
|
|
||||||
if (err) *err = "CLZO: compressed region past end";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
r = lzo1x_decompress_safe(blob + 20, comp_size, out.data(), &out_len, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r != LZO_E_OK) {
|
|
||||||
if (err) *err = "CLZO: lzo1x_decompress_safe failed (" + std::to_string(r) + ")";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (out_len != real_size) {
|
|
||||||
if (err) *err = "CLZO: size mismatch " + std::to_string(out_len) + " != " +
|
|
||||||
std::to_string(real_size);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string cstr(const uint8_t *p, size_t maxn) {
|
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
while (n < maxn && p[n]) {
|
while (n < maxn && p[n]) {
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
return std::string(reinterpret_cast<const char *>(p), n);
|
return std::string(p, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -114,131 +110,143 @@ bool load_proto(const std::string &path, const std::array<uint32_t, 4> &key, Pro
|
|||||||
return load_proto_bytes(file, key, out, err);
|
return load_proto_bytes(file, key, out, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The header checks of CItemManager::LoadItemTable / CPythonNonPlayer::LoadNonPlayerData, then CLZO::Decompress.
|
||||||
bool load_proto_bytes(const std::vector<uint8_t> &file, const std::array<uint32_t, 4> &key,
|
bool load_proto_bytes(const std::vector<uint8_t> &file, const std::array<uint32_t, 4> &key,
|
||||||
Proto &out, std::string *err) {
|
Proto &out, std::string *err) {
|
||||||
if (sodium_init() < 0) {
|
out = Proto{};
|
||||||
if (err) *err = "sodium_init failed";
|
if (file.size() < 12) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (lzo_init() != LZO_E_OK) {
|
|
||||||
if (err) *err = "lzo_init failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (file.size() < 16) {
|
|
||||||
if (err) *err = "file too small";
|
if (err) *err = "file too small";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *p = file.data();
|
size_t pos = 0;
|
||||||
out.fourcc = rd_u32(p);
|
auto read_u32 = [&](uint32_t &v) {
|
||||||
p += 4;
|
if (pos + 4 > file.size()) {
|
||||||
|
|
||||||
uint32_t data_size = 0;
|
|
||||||
if (out.fourcc == FOURCC_MIPX) {
|
|
||||||
out.version = rd_u32(p);
|
|
||||||
p += 4;
|
|
||||||
out.stride = rd_u32(p);
|
|
||||||
p += 4;
|
|
||||||
out.elements = rd_u32(p);
|
|
||||||
p += 4;
|
|
||||||
data_size = rd_u32(p);
|
|
||||||
p += 4;
|
|
||||||
if (out.version != 1) {
|
|
||||||
if (err) *err = "MIPX version != 1";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (out.fourcc == FOURCC_MIPT || out.fourcc == FOURCC_MMPT) {
|
v = rd_u32(file.data() + pos);
|
||||||
out.elements = rd_u32(p);
|
pos += 4;
|
||||||
p += 4;
|
return true;
|
||||||
data_size = rd_u32(p);
|
};
|
||||||
p += 4;
|
|
||||||
|
read_u32(out.fourcc);
|
||||||
|
if (out.fourcc == MAKEFOURCC('M', 'I', 'P', 'X')) {
|
||||||
|
if (!read_u32(out.version) || !read_u32(out.stride)) {
|
||||||
|
if (err) *err = "truncated MIPX header";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (out.version != 1) {
|
||||||
|
if (err) *err = "invalid item_proto VERSION " + std::to_string(out.version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (out.stride != sizeof(TItemTable)) {
|
||||||
|
if (err) *err = "invalid item_proto STRIDE " + std::to_string(out.stride) + " != sizeof(SItemTable)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (out.fourcc == MAKEFOURCC('M', 'I', 'P', 'T')) {
|
||||||
|
out.stride = sizeof(TItemTable);
|
||||||
|
} else if (out.fourcc == MAKEFOURCC('M', 'M', 'P', 'T')) {
|
||||||
|
out.stride = sizeof(SMobTable);
|
||||||
} else {
|
} else {
|
||||||
if (err) *err = "unknown proto fourcc";
|
if (err) *err = "invalid proto type";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (static_cast<size_t>(p - file.data()) + data_size > file.size()) {
|
uint32_t data_size = 0;
|
||||||
|
if (!read_u32(out.elements) || !read_u32(data_size)) {
|
||||||
|
if (err) *err = "truncated proto header";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 40250 reads dwDataSize bytes without checking; CLZO then reads its own header lengths.
|
||||||
|
if (pos + data_size > file.size() || data_size < sizeof(CLZObject::THeader)) {
|
||||||
if (err) *err = "declared data_size past end of file";
|
if (err) *err = "declared data_size past end of file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clzo_decompress(p, data_size, key, out.blob, err)) {
|
PackSingletons(); // CLZO, as 40250 Main() creates it
|
||||||
|
DWORD dwKey[4] = {key[0], key[1], key[2], key[3]};
|
||||||
|
CLZObject zObj;
|
||||||
|
if (!CLZO::Instance().Decompress(zObj, file.data() + pos, dwKey)) {
|
||||||
|
if (err) *err = "CLZO::Decompress failed (wrong key or corrupt data)";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out.elements == 0) {
|
if (out.fourcc == MAKEFOURCC('M', 'M', 'P', 'T') && (zObj.GetSize() % sizeof(SMobTable)) != 0) {
|
||||||
if (err) *err = "0 elements";
|
if (err) *err = "invalid mob_proto size " + std::to_string(zObj.GetSize()) + ", check data format";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (out.stride == 0) {
|
// 40250 walks dwElements records from the buffer without comparing against its size.
|
||||||
if (out.blob.size() % out.elements != 0) {
|
if (static_cast<uint64_t>(out.stride) * out.elements > zObj.GetSize()) {
|
||||||
if (err) *err = "blob not divisible by element count";
|
if (err) *err = "elements * stride past decompressed size";
|
||||||
return false;
|
|
||||||
}
|
|
||||||
out.stride = static_cast<uint32_t>(out.blob.size() / out.elements);
|
|
||||||
}
|
|
||||||
if (static_cast<size_t>(out.stride) * out.elements != out.blob.size()) {
|
|
||||||
if (err) *err = "stride * elements != blob size";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const BYTE *buf = zObj.GetBuffer();
|
||||||
|
out.blob.assign(buf, buf + static_cast<size_t>(out.stride) * out.elements);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemRecord parse_item(const uint8_t *r, uint32_t stride) {
|
ItemRecord parse_item(const uint8_t *r, uint32_t stride) {
|
||||||
ItemRecord it;
|
ItemRecord it;
|
||||||
if (!r || stride < 236) {
|
if (!r || stride != sizeof(TItemTable)) {
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
it.vnum = rd_u32(r + 0);
|
TItemTable t;
|
||||||
it.vnum_range = rd_u32(r + 4);
|
std::memcpy(&t, r, sizeof(t));
|
||||||
it.name = cstr(r + 8, 65);
|
it.vnum = t.dwVnum;
|
||||||
it.locale_name = cstr(r + 73, 65);
|
it.vnum_range = t.dwVnumRange;
|
||||||
it.type = r[138];
|
it.name = cstr(t.szName, sizeof(t.szName));
|
||||||
it.sub_type = r[139];
|
it.locale_name = cstr(t.szLocaleName, sizeof(t.szLocaleName));
|
||||||
it.weight = r[140];
|
it.type = t.bType;
|
||||||
it.size = r[141];
|
it.sub_type = t.bSubType;
|
||||||
it.anti_flags = rd_u32(r + 142);
|
it.weight = t.bWeight;
|
||||||
it.flags = rd_u32(r + 146);
|
it.size = t.bSize;
|
||||||
it.wear_flags = rd_u32(r + 150);
|
it.anti_flags = t.dwAntiFlags;
|
||||||
it.buy_price = rd_u32(r + 158);
|
it.flags = t.dwFlags;
|
||||||
it.sell_price = rd_u32(r + 162);
|
it.wear_flags = t.dwWearFlags;
|
||||||
for (int i = 0; i < 2; ++i) {
|
it.immune_flag = t.dwImmuneFlag;
|
||||||
const size_t off = 166 + static_cast<size_t>(i) * 5;
|
it.buy_price = t.dwIBuyItemPrice;
|
||||||
it.limits[i].type = r[off];
|
it.sell_price = t.dwISellItemPrice;
|
||||||
it.limits[i].value = static_cast<int32_t>(rd_u32(r + off + 1));
|
for (int i = 0; i < CItemData::ITEM_LIMIT_MAX_NUM; ++i) {
|
||||||
|
it.limits[i] = {t.aLimits[i].bType, t.aLimits[i].lValue};
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < CItemData::ITEM_APPLY_MAX_NUM; ++i) {
|
||||||
const size_t off = 176 + static_cast<size_t>(i) * 5;
|
it.applies[i] = {t.aApplies[i].bType, t.aApplies[i].lValue};
|
||||||
it.applies[i].type = r[off];
|
|
||||||
it.applies[i].value = static_cast<int32_t>(rd_u32(r + off + 1));
|
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 6; ++i) {
|
for (int i = 0; i < CItemData::ITEM_VALUES_MAX_NUM; ++i) {
|
||||||
it.values[i] = (int32_t)rd_u32(r + 191 + i * 4);
|
it.values[i] = t.alValues[i];
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < CItemData::ITEM_SOCKET_MAX_NUM; ++i) {
|
||||||
it.sockets[i] = static_cast<int32_t>(rd_u32(r + 215 + i * 4));
|
it.sockets[i] = t.alSockets[i];
|
||||||
}
|
}
|
||||||
it.refined_vnum = rd_u32(r + 227);
|
it.refined_vnum = t.dwRefinedVnum;
|
||||||
it.refine_set = static_cast<uint16_t>(r[231] | (static_cast<uint16_t>(r[232]) << 8));
|
it.refine_set = t.wRefineSet;
|
||||||
it.alter_to_magic_pct = r[233];
|
it.alter_to_magic_pct = t.bAlterToMagicItemPct;
|
||||||
it.specular = r[234];
|
it.specular = t.bSpecular;
|
||||||
it.gain_socket_pct = r[235];
|
it.gain_socket_pct = t.bGainSocketPct;
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
MobRecord parse_mob(const uint8_t *r, uint32_t stride) {
|
MobRecord parse_mob(const uint8_t *r, uint32_t stride) {
|
||||||
MobRecord m;
|
MobRecord m;
|
||||||
if (!r || stride < 139) {
|
if (!r || stride != sizeof(SMobTable)) {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
m.vnum = rd_u32(r + 0);
|
SMobTable t;
|
||||||
m.name = cstr(r + 4, 65);
|
std::memcpy(&t, r, sizeof(t));
|
||||||
m.locale_name = cstr(r + 69, 65);
|
m.vnum = t.dwVnum;
|
||||||
m.type = r[134];
|
m.name = cstr(t.szName, sizeof(t.szName));
|
||||||
m.rank = r[135];
|
m.locale_name = cstr(t.szLocaleName, sizeof(t.szLocaleName));
|
||||||
m.battle_type = r[136];
|
m.type = t.bType;
|
||||||
m.level = r[137];
|
m.rank = t.bRank;
|
||||||
m.size = r[138];
|
m.battle_type = t.bBattleType;
|
||||||
|
m.level = t.bLevel;
|
||||||
|
m.size = t.bSize;
|
||||||
|
m.attack_speed = t.sAttackSpeed;
|
||||||
|
m.moving_speed = t.sMovingSpeed;
|
||||||
|
m.on_click_type = t.bOnClickType;
|
||||||
|
m.empire = t.bEmpire;
|
||||||
|
m.folder = cstr(t.szFolder, sizeof(t.szFolder));
|
||||||
|
m.monster_color = t.dwMonsterColor;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+52
-42
@@ -1,15 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
// item_proto / mob_proto reader for this m2dev fork.
|
// item_proto / mob_proto reader, the 40250 format (GameLib/ItemManager.cpp CItemManager::LoadItemTable,
|
||||||
|
// UserInterface/PythonNonPlayer.cpp CPythonNonPlayer::LoadNonPlayerData).
|
||||||
//
|
//
|
||||||
// Outer: MIPX (item) = [u32 'MIPX'][u32 ver=1][u32 stride][u32 elements][u32 datasize][blob]
|
// Outer: item = [u32 'MIPX'][u32 ver=1][u32 stride=156][u32 elements][u32 datasize][CLZObject]
|
||||||
// MMPT (mob) = [u32 'MMPT'][u32 elements][u32 datasize][blob]
|
// (or the older [u32 'MIPT'][u32 elements][u32 datasize][CLZObject])
|
||||||
// Blob = CLZO container: [u32 'MCOZ'][u32 encryptSize][u32 compressedSize][u32 realSize]
|
// mob = [u32 'MMPT'][u32 elements][u32 datasize][CLZObject]
|
||||||
// data @ blob+20. encryptSize>0 -> XChaCha20-decrypt (the fork's tea_*: key =
|
// CLZObject = [u32 'MCOZ'][u32 encryptSize][u32 compressedSize][u32 realSize] + TEA/LZO payload, decoded by the
|
||||||
// BLAKE2b(key16,"M2DevPackEncrypt"), nonce = BLAKE2b(key16,"M2DevNonce")[:24]) of
|
// ported CLZO::Decompress with the table's key. Records are #pragma pack(1) ILP32 structs:
|
||||||
// encryptSize bytes starting at blob+16, yielding [u32 'MCOZ'][lzo1x] -> LZO ->
|
// CItemData::TItemTable (156 bytes, port/GameLib/ItemData.h) and CPythonNonPlayer::TMobTable (255 bytes).
|
||||||
// realSize bytes. encryptSize==0 -> LZO straight from blob+20.
|
|
||||||
// Decompressed blob = `elements` records of `stride` bytes (stride from the MIPX header,
|
|
||||||
// or realSize/elements for MMPT). Records are #pragma pack(1).
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -18,14 +16,15 @@
|
|||||||
|
|
||||||
namespace mtproto {
|
namespace mtproto {
|
||||||
|
|
||||||
// The 4-DWORD keys the client hardcodes (GameLib/ItemManager.cpp, PythonNonPlayer.cpp).
|
// The 4-DWORD keys 40250 hardcodes: s_adwItemProtoKey (GameLib/ItemManager.cpp), s_adwMobProtoKey
|
||||||
|
// (UserInterface/PythonNonPlayer.cpp).
|
||||||
inline constexpr std::array<uint32_t, 4> ITEM_PROTO_KEY = {173217u, 72619434u, 408587239u, 27973291u};
|
inline constexpr std::array<uint32_t, 4> ITEM_PROTO_KEY = {173217u, 72619434u, 408587239u, 27973291u};
|
||||||
inline constexpr std::array<uint32_t, 4> MOB_PROTO_KEY = {4813894u, 18955u, 552631u, 6822045u};
|
inline constexpr std::array<uint32_t, 4> MOB_PROTO_KEY = {4813894u, 18955u, 552631u, 6822045u};
|
||||||
|
|
||||||
struct Proto {
|
struct Proto {
|
||||||
uint32_t fourcc = 0;
|
uint32_t fourcc = 0;
|
||||||
uint32_t version = 0;
|
uint32_t version = 0;
|
||||||
uint32_t stride = 0; // record size
|
uint32_t stride = 0; // record size: sizeof(TItemTable) or sizeof(TMobTable)
|
||||||
uint32_t elements = 0; // record count
|
uint32_t elements = 0; // record count
|
||||||
std::vector<uint8_t> blob; // elements * stride bytes
|
std::vector<uint8_t> blob; // elements * stride bytes
|
||||||
|
|
||||||
@@ -34,14 +33,14 @@ struct Proto {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load + decompress. `key` is ITEM_PROTO_KEY or MOB_PROTO_KEY.
|
// Load + decompress. `key` is ITEM_PROTO_KEY or MOB_PROTO_KEY; the format follows the fourcc.
|
||||||
// Parse from an already-read buffer (host reads via godot::FileAccess for res://).
|
// Parse from an already-read buffer (the extension reads through asset_io, e.g. pack://locale/en/item_proto).
|
||||||
bool load_proto_bytes(const std::vector<uint8_t> &bytes, const std::array<uint32_t, 4> &key,
|
bool load_proto_bytes(const std::vector<uint8_t> &bytes, const std::array<uint32_t, 4> &key,
|
||||||
Proto &out, std::string *err);
|
Proto &out, std::string *err);
|
||||||
bool load_proto(const std::string &path, const std::array<uint32_t, 4> &key, Proto &out,
|
bool load_proto(const std::string &path, const std::array<uint32_t, 4> &key, Proto &out,
|
||||||
std::string *err = nullptr);
|
std::string *err = nullptr);
|
||||||
|
|
||||||
// --- typed views over the leading fields (rest is offset-stable per stride) ---
|
// --- typed views over the records ---
|
||||||
|
|
||||||
struct ItemLimit {
|
struct ItemLimit {
|
||||||
uint8_t type = 0;
|
uint8_t type = 0;
|
||||||
@@ -53,42 +52,53 @@ struct ItemApply {
|
|||||||
int32_t value = 0;
|
int32_t value = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CItemData::TItemTable, field for field.
|
||||||
struct ItemRecord {
|
struct ItemRecord {
|
||||||
uint32_t vnum = 0;
|
uint32_t vnum = 0;
|
||||||
uint32_t vnum_range = 0;
|
uint32_t vnum_range = 0;
|
||||||
std::string name; // szName[65] @ 8
|
std::string name; // szName[25]
|
||||||
std::string locale_name; // szLocaleName[65] @ 73
|
std::string locale_name; // szLocaleName[25]
|
||||||
uint8_t type = 0; // @ 138
|
uint8_t type = 0;
|
||||||
uint8_t sub_type = 0; // @ 139
|
uint8_t sub_type = 0;
|
||||||
uint8_t weight = 0; // @ 140
|
uint8_t weight = 0;
|
||||||
uint8_t size = 0; // @ 141
|
uint8_t size = 0;
|
||||||
uint32_t anti_flags = 0; // @ 142 (ITEM_ANTIFLAG_*; sell = 1 << 8)
|
uint32_t anti_flags = 0; // ITEM_ANTIFLAG_*
|
||||||
uint32_t flags = 0; // @ 146 (ITEM_FLAG_*; count-per-1-gold = 1 << 3)
|
uint32_t flags = 0; // ITEM_FLAG_*
|
||||||
uint32_t wear_flags = 0; // @ 150
|
uint32_t wear_flags = 0;
|
||||||
uint32_t buy_price = 0; // @ 158
|
uint32_t immune_flag = 0;
|
||||||
uint32_t sell_price = 0; // @ 162
|
uint32_t buy_price = 0; // dwIBuyItemPrice
|
||||||
std::array<ItemLimit, 2> limits{}; // aLimits[2] @ 166 (type + long)
|
uint32_t sell_price = 0; // dwISellItemPrice
|
||||||
std::array<ItemApply, 3> applies{}; // aApplies[3] @ 176 (type + long)
|
std::array<ItemLimit, 2> limits{};
|
||||||
int32_t values[6] = {0}; // alValues[6] @ 191 (armor: values[3] = body shape index)
|
std::array<ItemApply, 3> applies{};
|
||||||
int32_t sockets[3] = {0}; // alSockets[3] @ 215
|
int32_t values[6] = {0}; // alValues (armor: values[3] = body shape index)
|
||||||
uint32_t refined_vnum = 0; // dwRefinedVnum @ 227
|
int32_t sockets[3] = {0};
|
||||||
uint16_t refine_set = 0; // wRefineSet @ 231
|
uint32_t refined_vnum = 0;
|
||||||
uint8_t alter_to_magic_pct = 0; // bAlterToMagicItemPct @ 233
|
uint16_t refine_set = 0;
|
||||||
uint8_t specular = 0; // @ 234 -> PARITY §2.7 fSpecular = specular/100
|
uint8_t alter_to_magic_pct = 0;
|
||||||
uint8_t gain_socket_pct = 0; // bGainSocketPct @ 235
|
uint8_t specular = 0; // PARITY §2.7 fSpecular = specular/100
|
||||||
|
uint8_t gain_socket_pct = 0;
|
||||||
};
|
};
|
||||||
|
// Empty record when `stride` is not sizeof(TItemTable).
|
||||||
ItemRecord parse_item(const uint8_t *rec, uint32_t stride);
|
ItemRecord parse_item(const uint8_t *rec, uint32_t stride);
|
||||||
|
|
||||||
|
// The leading CPythonNonPlayer::TMobTable fields plus the ones the client reads for display.
|
||||||
struct MobRecord {
|
struct MobRecord {
|
||||||
uint32_t vnum = 0;
|
uint32_t vnum = 0;
|
||||||
std::string name; // szName[65] @ 4
|
std::string name; // szName[25]
|
||||||
std::string locale_name; // @ 69
|
std::string locale_name; // szLocaleName[25]
|
||||||
uint8_t type = 0; // @ 134
|
uint8_t type = 0;
|
||||||
uint8_t rank = 0; // @ 135
|
uint8_t rank = 0;
|
||||||
uint8_t battle_type = 0; // @ 136
|
uint8_t battle_type = 0;
|
||||||
uint8_t level = 0; // @ 137
|
uint8_t level = 0;
|
||||||
uint8_t size = 0; // @ 138
|
uint8_t size = 0;
|
||||||
|
int16_t attack_speed = 0;
|
||||||
|
int16_t moving_speed = 0;
|
||||||
|
uint8_t on_click_type = 0;
|
||||||
|
uint8_t empire = 0;
|
||||||
|
std::string folder; // szFolder[65]
|
||||||
|
uint32_t monster_color = 0;
|
||||||
};
|
};
|
||||||
|
// Empty record when `stride` is not sizeof(TMobTable).
|
||||||
MobRecord parse_mob(const uint8_t *rec, uint32_t stride);
|
MobRecord parse_mob(const uint8_t *rec, uint32_t stride);
|
||||||
|
|
||||||
} // namespace mtproto
|
} // namespace mtproto
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ using namespace godot;
|
|||||||
|
|
||||||
namespace mtgodot {
|
namespace mtgodot {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// szName is the Korean (CP949) development name; what 40250 shows is szLocaleName (CItemData::GetName,
|
||||||
|
// CPythonNonPlayer::GetMonsterName) in the locale code page, 1252 for locale.cfg "10002 1252 en".
|
||||||
|
String from_cp1252(const std::string &s) {
|
||||||
|
static const char32_t k80[32] = {
|
||||||
|
0x20AC, 0xFFFD, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0xFFFD, 0x017D, 0xFFFD,
|
||||||
|
0xFFFD, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0xFFFD, 0x017E, 0x0178,
|
||||||
|
};
|
||||||
|
String out;
|
||||||
|
for (unsigned char c : s) {
|
||||||
|
out += String::chr(c >= 0x80 && c < 0xA0 ? k80[c - 0x80] : static_cast<char32_t>(c));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void Metin2Proto::_bind_methods() {
|
void Metin2Proto::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("load_item_proto", "path"), &Metin2Proto::load_item_proto);
|
ClassDB::bind_method(D_METHOD("load_item_proto", "path"), &Metin2Proto::load_item_proto);
|
||||||
ClassDB::bind_method(D_METHOD("load_mob_proto", "path"), &Metin2Proto::load_mob_proto);
|
ClassDB::bind_method(D_METHOD("load_mob_proto", "path"), &Metin2Proto::load_mob_proto);
|
||||||
@@ -65,8 +83,8 @@ Dictionary Metin2Proto::item(int vnum) const {
|
|||||||
mtproto::ItemRecord r = mtproto::parse_item(m_item.record(it->second), m_item.stride);
|
mtproto::ItemRecord r = mtproto::parse_item(m_item.record(it->second), m_item.stride);
|
||||||
d["vnum"] = (int)r.vnum;
|
d["vnum"] = (int)r.vnum;
|
||||||
d["vnum_range"] = (int)r.vnum_range;
|
d["vnum_range"] = (int)r.vnum_range;
|
||||||
d["name"] = String::utf8(r.name.c_str());
|
d["name"] = from_cp1252(r.locale_name); // CItemData::GetName()
|
||||||
d["locale_name"] = String::utf8(r.locale_name.c_str());
|
d["locale_name"] = from_cp1252(r.locale_name);
|
||||||
d["type"] = (int)r.type;
|
d["type"] = (int)r.type;
|
||||||
d["sub_type"] = (int)r.sub_type;
|
d["sub_type"] = (int)r.sub_type;
|
||||||
d["weight"] = (int)r.weight;
|
d["weight"] = (int)r.weight;
|
||||||
@@ -74,6 +92,7 @@ Dictionary Metin2Proto::item(int vnum) const {
|
|||||||
d["anti_flags"] = (int)r.anti_flags;
|
d["anti_flags"] = (int)r.anti_flags;
|
||||||
d["flags"] = (int)r.flags;
|
d["flags"] = (int)r.flags;
|
||||||
d["wear_flags"] = (int)r.wear_flags;
|
d["wear_flags"] = (int)r.wear_flags;
|
||||||
|
d["immune_flag"] = (int)r.immune_flag;
|
||||||
d["buy_price"] = (int)r.buy_price;
|
d["buy_price"] = (int)r.buy_price;
|
||||||
d["sell_price"] = (int)r.sell_price;
|
d["sell_price"] = (int)r.sell_price;
|
||||||
{
|
{
|
||||||
@@ -126,13 +145,19 @@ Dictionary Metin2Proto::mob(int vnum) const {
|
|||||||
}
|
}
|
||||||
mtproto::MobRecord r = mtproto::parse_mob(m_mob.record(it->second), m_mob.stride);
|
mtproto::MobRecord r = mtproto::parse_mob(m_mob.record(it->second), m_mob.stride);
|
||||||
d["vnum"] = (int)r.vnum;
|
d["vnum"] = (int)r.vnum;
|
||||||
d["name"] = String::utf8(r.name.c_str());
|
d["name"] = from_cp1252(r.locale_name); // CPythonNonPlayer::GetMonsterName()
|
||||||
d["locale_name"] = String::utf8(r.locale_name.c_str());
|
d["locale_name"] = from_cp1252(r.locale_name);
|
||||||
d["type"] = (int)r.type;
|
d["type"] = (int)r.type;
|
||||||
d["rank"] = (int)r.rank;
|
d["rank"] = (int)r.rank;
|
||||||
d["battle_type"] = (int)r.battle_type;
|
d["battle_type"] = (int)r.battle_type;
|
||||||
d["level"] = (int)r.level;
|
d["level"] = (int)r.level;
|
||||||
d["size"] = (int)r.size;
|
d["size"] = (int)r.size;
|
||||||
|
d["attack_speed"] = (int)r.attack_speed;
|
||||||
|
d["moving_speed"] = (int)r.moving_speed;
|
||||||
|
d["on_click_type"] = (int)r.on_click_type;
|
||||||
|
d["empire"] = (int)r.empire;
|
||||||
|
d["folder"] = String(r.folder.c_str());
|
||||||
|
d["monster_color"] = (int64_t)r.monster_color;
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Metin2Proto — GDExtension node exposing item_proto / mob_proto to GDScript.
|
// Metin2Proto — GDExtension node exposing item_proto / mob_proto to GDScript.
|
||||||
//
|
//
|
||||||
// var proto = Metin2Proto.new()
|
// var proto = Metin2Proto.new()
|
||||||
// proto.load_item_proto("<assets>/locale/locale/en/item_proto")
|
// proto.load_item_proto("pack://locale/en/item_proto") # 40250 format, via asset_io
|
||||||
// var d := proto.item(19) # { vnum, name, locale_name, type, sub_type, ... }
|
// var d := proto.item(19) # { vnum, name, locale_name, type, sub_type, ... }
|
||||||
//
|
//
|
||||||
// Used by the P2 inventory/equipment windows for names / types / tooltips and by
|
// Used by the P2 inventory/equipment windows for names / types / tooltips and by
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Synthetic item_proto record test. It locks the offsets used by the
|
// Synthetic item_proto / mob_proto records. Locks the 40250 CItemData::TItemTable (156 bytes) and
|
||||||
// ClientVS22 TItemTable layout without requiring the 2.2G asset tree.
|
// CPythonNonPlayer::TMobTable (255 bytes) offsets without the client assets.
|
||||||
#include "../src/proto/proto.h"
|
#include "../src/proto/proto.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -16,51 +16,59 @@ static int g_fail = 0;
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static void put_u32(std::array<uint8_t, 236> &record, size_t offset, uint32_t value) {
|
template <size_t N>
|
||||||
|
static void put_u32(std::array<uint8_t, N> &record, size_t offset, uint32_t value) {
|
||||||
record[offset + 0] = static_cast<uint8_t>(value);
|
record[offset + 0] = static_cast<uint8_t>(value);
|
||||||
record[offset + 1] = static_cast<uint8_t>(value >> 8);
|
record[offset + 1] = static_cast<uint8_t>(value >> 8);
|
||||||
record[offset + 2] = static_cast<uint8_t>(value >> 16);
|
record[offset + 2] = static_cast<uint8_t>(value >> 16);
|
||||||
record[offset + 3] = static_cast<uint8_t>(value >> 24);
|
record[offset + 3] = static_cast<uint8_t>(value >> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
template <size_t N>
|
||||||
std::array<uint8_t, 236> record{};
|
static void put_u16(std::array<uint8_t, N> &record, size_t offset, uint16_t value) {
|
||||||
|
record[offset + 0] = static_cast<uint8_t>(value);
|
||||||
|
record[offset + 1] = static_cast<uint8_t>(value >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void item_layout() {
|
||||||
|
std::array<uint8_t, 156> record{};
|
||||||
put_u32(record, 0, 12345);
|
put_u32(record, 0, 12345);
|
||||||
put_u32(record, 4, 7);
|
put_u32(record, 4, 7);
|
||||||
std::memcpy(record.data() + 8, "TestItem", 9);
|
std::memcpy(record.data() + 8, "TestItem", 9);
|
||||||
std::memcpy(record.data() + 73, "LocaleItem", 11);
|
std::memcpy(record.data() + 33, "LocaleItem", 11);
|
||||||
record[138] = 3;
|
record[58] = 3;
|
||||||
record[139] = 4;
|
record[59] = 4;
|
||||||
record[140] = 2;
|
record[60] = 2;
|
||||||
record[141] = 1;
|
record[61] = 1;
|
||||||
put_u32(record, 142, 1u << 8);
|
put_u32(record, 62, 1u << 8);
|
||||||
put_u32(record, 146, 1u << 3);
|
put_u32(record, 66, 1u << 3);
|
||||||
put_u32(record, 150, 0x12345678);
|
put_u32(record, 70, 0x12345678);
|
||||||
put_u32(record, 158, 9000);
|
put_u32(record, 74, 0x55);
|
||||||
put_u32(record, 162, 6000);
|
put_u32(record, 78, 9000);
|
||||||
record[166] = 1;
|
put_u32(record, 82, 6000);
|
||||||
put_u32(record, 167, 42);
|
record[86] = 1;
|
||||||
record[176] = 2;
|
put_u32(record, 87, 42);
|
||||||
put_u32(record, 177, -7);
|
record[96] = 2;
|
||||||
put_u32(record, 191 + 3 * 4, 3);
|
put_u32(record, 97, static_cast<uint32_t>(-7));
|
||||||
put_u32(record, 215, 1001);
|
put_u32(record, 111 + 3 * 4, 3);
|
||||||
put_u32(record, 219, -1);
|
put_u32(record, 135, 1001);
|
||||||
put_u32(record, 227, 12346);
|
put_u32(record, 139, static_cast<uint32_t>(-1));
|
||||||
record[231] = 0x34;
|
put_u32(record, 147, 12346);
|
||||||
record[232] = 0x12;
|
put_u16(record, 151, 0x1234);
|
||||||
record[233] = 11;
|
record[153] = 11;
|
||||||
record[234] = 75;
|
record[154] = 75;
|
||||||
record[235] = 88;
|
record[155] = 88;
|
||||||
|
|
||||||
const mtproto::ItemRecord item = mtproto::parse_item(record.data(), record.size());
|
const mtproto::ItemRecord item = mtproto::parse_item(record.data(), record.size());
|
||||||
CHECK(item.vnum == 12345, "vnum offset");
|
CHECK(item.vnum == 12345, "vnum offset");
|
||||||
CHECK(item.vnum_range == 7, "vnum range offset");
|
CHECK(item.vnum_range == 7, "vnum range offset");
|
||||||
CHECK(item.name == "TestItem", "name offset");
|
CHECK(item.name == "TestItem", "name offset");
|
||||||
CHECK(item.locale_name == "LocaleItem", "locale name offset");
|
CHECK(item.locale_name == "LocaleItem", "locale name offset");
|
||||||
CHECK(item.type == 3 && item.sub_type == 4, "type offsets");
|
CHECK(item.type == 3 && item.sub_type == 4 && item.weight == 2 && item.size == 1, "type offsets");
|
||||||
CHECK(item.anti_flags == (1u << 8), "anti_flags offset");
|
CHECK(item.anti_flags == (1u << 8), "anti_flags offset");
|
||||||
CHECK(item.flags == (1u << 3), "flags offset");
|
CHECK(item.flags == (1u << 3), "flags offset");
|
||||||
CHECK(item.wear_flags == 0x12345678, "wear_flags offset");
|
CHECK(item.wear_flags == 0x12345678, "wear_flags offset");
|
||||||
|
CHECK(item.immune_flag == 0x55, "immune_flag offset");
|
||||||
CHECK(item.buy_price == 9000 && item.sell_price == 6000, "price offsets");
|
CHECK(item.buy_price == 9000 && item.sell_price == 6000, "price offsets");
|
||||||
CHECK(item.limits[0].type == 1 && item.limits[0].value == 42, "limit offsets");
|
CHECK(item.limits[0].type == 1 && item.limits[0].value == 42, "limit offsets");
|
||||||
CHECK(item.applies[0].type == 2 && item.applies[0].value == -7, "apply offsets");
|
CHECK(item.applies[0].type == 2 && item.applies[0].value == -7, "apply offsets");
|
||||||
@@ -69,11 +77,50 @@ int main() {
|
|||||||
CHECK(item.refined_vnum == 12346 && item.refine_set == 0x1234, "refine offsets");
|
CHECK(item.refined_vnum == 12346 && item.refine_set == 0x1234, "refine offsets");
|
||||||
CHECK(item.alter_to_magic_pct == 11 && item.specular == 75 && item.gain_socket_pct == 88,
|
CHECK(item.alter_to_magic_pct == 11 && item.specular == 75 && item.gain_socket_pct == 88,
|
||||||
"tail offsets");
|
"tail offsets");
|
||||||
|
CHECK(mtproto::parse_item(record.data(), 236).vnum == 0, "m2dev 236-byte stride rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mob_layout() {
|
||||||
|
std::array<uint8_t, 255> record{};
|
||||||
|
put_u32(record, 0, 101);
|
||||||
|
std::memcpy(record.data() + 4, "Wild Dog", 9);
|
||||||
|
std::memcpy(record.data() + 29, "LocaleDog", 10);
|
||||||
|
record[54] = 0; // bType
|
||||||
|
record[55] = 1; // bRank
|
||||||
|
record[56] = 2; // bBattleType
|
||||||
|
record[57] = 45; // bLevel
|
||||||
|
record[58] = 1; // bSize
|
||||||
|
// 59 gold/exp/hp (16), 75 regen (2), 77 wDef, 79 AI/race/immune (12), 91 str..int (4), 95 damage (8)
|
||||||
|
put_u16(record, 103, static_cast<uint16_t>(-5)); // sAttackSpeed
|
||||||
|
put_u16(record, 105, 120); // sMovingSpeed
|
||||||
|
// 107 aggr pct, 108 sight, 110 range, 112 enchants[6], 118 resists[11], 129 resurrection, 133 drop
|
||||||
|
record[137] = 3; // bMountCapacity
|
||||||
|
record[138] = 1; // bOnClickType
|
||||||
|
record[139] = 2; // bEmpire
|
||||||
|
std::memcpy(record.data() + 140, "monster/dog", 12);
|
||||||
|
// 205 fDamMultiply, 209 summon, 213 drain sp
|
||||||
|
put_u32(record, 217, 0xAABBCCDD); // dwMonsterColor
|
||||||
|
// 221 polymorph, 225 skills 5x5, 250 berserk..revive
|
||||||
|
record[254] = 9;
|
||||||
|
|
||||||
|
const mtproto::MobRecord m = mtproto::parse_mob(record.data(), record.size());
|
||||||
|
CHECK(m.vnum == 101, "mob vnum offset");
|
||||||
|
CHECK(m.name == "Wild Dog" && m.locale_name == "LocaleDog", "mob name offsets");
|
||||||
|
CHECK(m.rank == 1 && m.battle_type == 2 && m.level == 45 && m.size == 1, "mob byte offsets");
|
||||||
|
CHECK(m.attack_speed == -5 && m.moving_speed == 120, "mob speed offsets");
|
||||||
|
CHECK(m.on_click_type == 1 && m.empire == 2, "mob click/empire offsets");
|
||||||
|
CHECK(m.folder == "monster/dog", "mob folder offset");
|
||||||
|
CHECK(m.monster_color == 0xAABBCCDD, "mob color offset");
|
||||||
|
CHECK(mtproto::parse_mob(record.data(), 335).vnum == 0, "m2dev 335-byte stride rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
item_layout();
|
||||||
|
mob_layout();
|
||||||
if (g_fail) {
|
if (g_fail) {
|
||||||
std::fprintf(stderr, "%d check(s) failed\n", g_fail);
|
std::fprintf(stderr, "%d check(s) failed\n", g_fail);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
std::printf("all item layout checks passed\n");
|
std::printf("all item/mob layout checks passed\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
// item_proto / mob_proto reader — against the real locale files if present.
|
// item_proto / mob_proto against the real 40250 client: registers <Client>/pack like the client does, reads
|
||||||
// Set M2_ASSETS to the Metin2 assets dir, else falls back to a repo-relative
|
// "locale/en/item_proto" and "locale/en/mob_proto" through CEterPackManager::Get (the path
|
||||||
// guess; skips (passes) if the files are not found.
|
// CPythonApplication::Create hands LoadItemTable/LoadNonPlayerData), and parses them in the 40250 format.
|
||||||
|
//
|
||||||
|
// proto_test <40250 Client dir>
|
||||||
|
//
|
||||||
|
// Exit 77 (ctest SKIP) when the client is missing, unless MT_ASSETS_STRICT=1, which turns that into a failure.
|
||||||
#include "../src/proto/proto.h"
|
#include "../src/proto/proto.h"
|
||||||
|
#include "../src/platform/PackBackend.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -18,46 +23,43 @@ static int g_fail = 0;
|
|||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
static std::string assets_root() {
|
static bool load(const char *name, const std::array<uint32_t, 4> &key, Proto &p) {
|
||||||
if (const char *e = std::getenv("M2_ASSETS")) {
|
std::vector<uint8_t> bytes;
|
||||||
return e;
|
if (!mtpack40250::read(name, bytes)) {
|
||||||
|
std::fprintf(stderr, "FAIL: %s not in the packs\n", name);
|
||||||
|
++g_fail;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return "../../assets"; // ctest cwd = build/extension -> <repo>/assets
|
std::string err;
|
||||||
|
const bool ok = load_proto_bytes(bytes, key, p, &err);
|
||||||
|
CHECK(ok, (std::string("load ") + name + ": " + err).c_str());
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool exists(const std::string &p) {
|
int main(int argc, char **argv) {
|
||||||
FILE *f = std::fopen(p.c_str(), "rb");
|
const char *strict = std::getenv("MT_ASSETS_STRICT");
|
||||||
if (f) {
|
const bool is_strict = strict && std::string(strict) == "1";
|
||||||
std::fclose(f);
|
const std::string client = argc > 1 ? argv[1] : "";
|
||||||
return true;
|
if (client.empty() || !mtpack40250::initialize(client)) {
|
||||||
|
std::printf("proto_test: no 40250 Client/pack at '%s'\n", client.c_str());
|
||||||
|
return is_strict ? 1 : 77;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
// CEterPackManager::isExist, what AssetRoot.locale_file() asks before loading.
|
||||||
const std::string base = assets_root() + "/locale/locale/en";
|
CHECK(mtpack40250::exists("locale/en/item_proto"), "isExist locale/en/item_proto");
|
||||||
const std::string ipath = base + "/item_proto";
|
CHECK(mtpack40250::exists("LOCALE\\EN\\MOB_PROTO"), "isExist normalizes case and separators");
|
||||||
const std::string mpath = base + "/mob_proto";
|
CHECK(!mtpack40250::exists("locale/en/no_such_proto"), "isExist rejects a missing name");
|
||||||
|
|
||||||
if (!exists(ipath) || !exists(mpath)) {
|
|
||||||
std::printf("skipped (no locale proto files at %s)\n", base.c_str());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- item_proto ---
|
// --- item_proto ---
|
||||||
{
|
{
|
||||||
Proto p;
|
Proto p;
|
||||||
std::string err;
|
if (load("locale/en/item_proto", ITEM_PROTO_KEY, p)) {
|
||||||
bool ok = load_proto(ipath, ITEM_PROTO_KEY, p, &err);
|
|
||||||
CHECK(ok, ("load item_proto: " + err).c_str());
|
|
||||||
if (ok) {
|
|
||||||
CHECK(p.fourcc == 0x5850494Du, "item fourcc MIPX");
|
CHECK(p.fourcc == 0x5850494Du, "item fourcc MIPX");
|
||||||
CHECK(p.version == 1, "item version 1");
|
CHECK(p.version == 1, "item version 1");
|
||||||
CHECK(p.stride == 236, "item stride 236 (== pack(1) sizeof TItemTable)");
|
CHECK(p.stride == 156, "item stride 156 (== sizeof TItemTable)");
|
||||||
CHECK(p.elements > 100, "item elements > 100");
|
CHECK(p.elements > 100, "item elements > 100");
|
||||||
CHECK(p.blob.size() == static_cast<size_t>(p.stride) * p.elements, "item blob size");
|
CHECK(p.blob.size() == static_cast<size_t>(p.stride) * p.elements, "item blob size");
|
||||||
|
|
||||||
// every record: vnum monotonically increasing, name printable ascii
|
|
||||||
uint32_t prev = 0;
|
uint32_t prev = 0;
|
||||||
int named = 0, mono = 1;
|
int named = 0, mono = 1;
|
||||||
uint8_t max_spec = 0;
|
uint8_t max_spec = 0;
|
||||||
@@ -74,68 +76,61 @@ int main() {
|
|||||||
max_spec = it.specular;
|
max_spec = it.specular;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CHECK(mono, "item vnums non-decreasing");
|
CHECK(mono, "item vnums increasing");
|
||||||
CHECK(named > p.elements * 0.8, "item: >80% have a name");
|
CHECK(named > p.elements * 0.8, "item: >80% have a name");
|
||||||
std::printf("item_proto: %u items, stride %u, %d named, max bSpecular=%u\n",
|
std::printf("item_proto: %u items, stride %u, %d named, max bSpecular=%u\n",
|
||||||
p.elements, p.stride, named, max_spec);
|
p.elements, p.stride, named, max_spec);
|
||||||
|
|
||||||
ItemRecord first = parse_item(p.record(0), p.stride);
|
// alValues[3] = body-armor shape index; "Monk Plate Armour" 11200..11209 share shape 3 and
|
||||||
std::printf(" [0] vnum=%u '%s' type=%u sub=%u weight=%u\n", first.vnum,
|
// bSpecular ramps with the refine level.
|
||||||
first.name.c_str(), first.type, first.sub_type, first.weight);
|
|
||||||
CHECK(first.vnum > 0 && first.vnum < 100000, "item[0] vnum sane");
|
|
||||||
CHECK(!first.name.empty(), "item[0] has a name");
|
|
||||||
|
|
||||||
// alValues[3] = body-armor shape index (anchored by bSpecular @234).
|
|
||||||
// "Monk Plate Armour" 11200..11209 all share shape 3; specular ramps
|
|
||||||
// with the refine level (0 -> 100).
|
|
||||||
bool found_armor = false;
|
bool found_armor = false;
|
||||||
for (uint32_t i = 0; i < p.elements; ++i) {
|
for (uint32_t i = 0; i < p.elements; ++i) {
|
||||||
ItemRecord it = parse_item(p.record(i), p.stride);
|
ItemRecord it = parse_item(p.record(i), p.stride);
|
||||||
if (it.vnum == 11209) {
|
if (it.vnum == 11209) {
|
||||||
found_armor = true;
|
found_armor = true;
|
||||||
std::printf(" 11209 values=[%d,%d,%d,%d,%d,%d] spec=%u\n", it.values[0],
|
std::printf(" 11209 '%s' values=[%d,%d,%d,%d,%d,%d] spec=%u\n", it.locale_name.c_str(),
|
||||||
it.values[1], it.values[2], it.values[3], it.values[4], it.values[5],
|
it.values[0], it.values[1], it.values[2], it.values[3], it.values[4],
|
||||||
it.specular);
|
it.values[5], it.specular);
|
||||||
CHECK(it.values[3] == 3, "item 11209: values[3] == shape 3");
|
CHECK(it.values[3] == 3, "item 11209: values[3] == shape 3");
|
||||||
CHECK(it.specular == 100, "item 11209 (+9): bSpecular == 100");
|
CHECK(it.specular == 100, "item 11209 (+9): bSpecular == 100");
|
||||||
}
|
}
|
||||||
if (it.vnum == 11200) {
|
if (it.vnum == 11200) {
|
||||||
CHECK(it.values[3] == 3 && it.specular == 0,
|
CHECK(it.values[3] == 3 && it.specular == 0, "item 11200 (+0): shape 3, specular 0");
|
||||||
"item 11200 (+0): shape 3, specular 0");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CHECK(found_armor, "item_proto contains vnum 11209");
|
CHECK(found_armor, "item_proto contains vnum 11209");
|
||||||
|
// CItemData::GetName() is szLocaleName.
|
||||||
|
ItemRecord yang = parse_item(p.record(0), p.stride);
|
||||||
|
std::printf(" [0] vnum=%u locale_name='%s'\n", yang.vnum, yang.locale_name.c_str());
|
||||||
|
CHECK(yang.vnum == 1 && yang.locale_name == "Yang", "item[0] = vnum 1 'Yang'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- mob_proto ---
|
// --- mob_proto ---
|
||||||
{
|
{
|
||||||
Proto p;
|
Proto p;
|
||||||
std::string err;
|
if (load("locale/en/mob_proto", MOB_PROTO_KEY, p)) {
|
||||||
bool ok = load_proto(mpath, MOB_PROTO_KEY, p, &err);
|
|
||||||
CHECK(ok, ("load mob_proto: " + err).c_str());
|
|
||||||
if (ok) {
|
|
||||||
CHECK(p.fourcc == 0x54504D4Du, "mob fourcc MMPT");
|
CHECK(p.fourcc == 0x54504D4Du, "mob fourcc MMPT");
|
||||||
CHECK(p.stride == 335, "mob stride 335 (derived from realSize/elements)");
|
CHECK(p.stride == 255, "mob stride 255 (== sizeof TMobTable)");
|
||||||
CHECK(p.elements > 100, "mob elements > 100");
|
CHECK(p.elements > 100, "mob elements > 100");
|
||||||
|
|
||||||
int named = 0, maxlvl = 0;
|
int named = 0, maxlvl = 0, with_folder = 0;
|
||||||
for (uint32_t i = 0; i < p.elements; ++i) {
|
for (uint32_t i = 0; i < p.elements; ++i) {
|
||||||
MobRecord m = parse_mob(p.record(i), p.stride);
|
MobRecord m = parse_mob(p.record(i), p.stride);
|
||||||
if (!m.name.empty()) {
|
named += !m.name.empty();
|
||||||
++named;
|
with_folder += !m.folder.empty();
|
||||||
}
|
|
||||||
if (m.level > maxlvl) {
|
if (m.level > maxlvl) {
|
||||||
maxlvl = m.level;
|
maxlvl = m.level;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CHECK(named > p.elements * 0.8, "mob: >80% named");
|
CHECK(named > p.elements * 0.8, "mob: >80% named");
|
||||||
|
CHECK(with_folder > p.elements * 0.5, "mob: >50% have a model folder");
|
||||||
CHECK(maxlvl > 20 && maxlvl < 256, "mob levels in a sane range");
|
CHECK(maxlvl > 20 && maxlvl < 256, "mob levels in a sane range");
|
||||||
MobRecord first = parse_mob(p.record(0), p.stride);
|
MobRecord first = parse_mob(p.record(0), p.stride);
|
||||||
std::printf("mob_proto: %u mobs, stride %u, %d named, maxlvl=%d\n [0] vnum=%u '%s' "
|
std::printf("mob_proto: %u mobs, stride %u, %d named, maxlvl=%d\n [0] vnum=%u '%s' "
|
||||||
"type=%u rank=%u level=%u\n",
|
"type=%u rank=%u level=%u folder='%s'\n",
|
||||||
p.elements, p.stride, named, maxlvl, first.vnum, first.name.c_str(), first.type,
|
p.elements, p.stride, named, maxlvl, first.vnum, first.locale_name.c_str(), first.type,
|
||||||
first.rank, first.level);
|
first.rank, first.level, first.folder.c_str());
|
||||||
CHECK(first.vnum > 0 && first.vnum < 60000, "mob[0] vnum sane");
|
CHECK(first.vnum > 0 && first.vnum < 60000, "mob[0] vnum sane");
|
||||||
CHECK(!first.name.empty(), "mob[0] has a name");
|
CHECK(!first.name.empty(), "mob[0] has a name");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,3 +45,28 @@ static func sub(rel: String) -> String:
|
|||||||
# 资源目录是否真的存在(缺资源的测试据此跳过)。
|
# 资源目录是否真的存在(缺资源的测试据此跳过)。
|
||||||
static func available() -> bool:
|
static func available() -> bool:
|
||||||
return DirAccess.dir_exists_absolute(path())
|
return DirAccess.dir_exists_absolute(path())
|
||||||
|
|
||||||
|
# --- 40250 client (pack/Index + .eix/.epk), docs/PORT-PLAN.md 2R item 6 / 2D ---
|
||||||
|
# 解析顺序:环境变量 MT_40250_CLIENT;开发树 res://../../40250/Server Client TMP4/Client。
|
||||||
|
static func client_path() -> String:
|
||||||
|
var env := OS.get_environment("MT_40250_CLIENT")
|
||||||
|
if env != "":
|
||||||
|
return env
|
||||||
|
return ProjectSettings.globalize_path("res://../../40250/Server Client TMP4/Client").simplify_path()
|
||||||
|
|
||||||
|
# 按 40250 PackInitialize("pack") 注册 <client>/pack;只注册一次,之后返回首次结果。
|
||||||
|
static func pack_ready() -> bool:
|
||||||
|
if not ClassDB.class_exists("Metin2Pack"):
|
||||||
|
return false
|
||||||
|
return bool(ClassDB.class_call_static("Metin2Pack", "initialize", client_path()))
|
||||||
|
|
||||||
|
# 40250 的 locale 文件(CPythonApplication::Create: "%s/item_proto" % LocalePath,LocalePath = "locale/<lang>"),
|
||||||
|
# 走 pack://;该语言没有时回退 en。pack 没就绪返回 ""。
|
||||||
|
static func locale_file(name: String, lang: String = "en") -> String:
|
||||||
|
if not pack_ready():
|
||||||
|
return ""
|
||||||
|
for l in [lang, "en"]:
|
||||||
|
var p := "pack://locale/%s/%s" % [l, name]
|
||||||
|
if bool(ClassDB.class_call_static("Metin2Pack", "exists", "locale/%s/%s" % [l, name])):
|
||||||
|
return p
|
||||||
|
return ""
|
||||||
|
|||||||
@@ -278,8 +278,8 @@ func _run() -> void:
|
|||||||
if ClassDB.class_exists("Metin2Proto"):
|
if ClassDB.class_exists("Metin2Proto"):
|
||||||
var pr: Object = ClassDB.instantiate("Metin2Proto")
|
var pr: Object = ClassDB.instantiate("Metin2Proto")
|
||||||
get_root().add_child(pr)
|
get_root().add_child(pr)
|
||||||
var ip := assets.path_join("locale/locale/en/item_proto")
|
var ip := AssetRoot.locale_file("item_proto")
|
||||||
if FileAccess.file_exists(ip) and pr.call("load_item_proto", ip):
|
if ip != "" and pr.call("load_item_proto", ip):
|
||||||
var em3: Node = EquipModel.new()
|
var em3: Node = EquipModel.new()
|
||||||
get_root().add_child(em3)
|
get_root().add_child(em3)
|
||||||
em3.setup(fc, il, func() -> Node: return model, assets)
|
em3.setup(fc, il, func() -> Node: return model, assets)
|
||||||
|
|||||||
@@ -488,10 +488,8 @@ func setup(m2client: Node, assets_root: String,
|
|||||||
if ClassDB.class_exists("Metin2Proto") and assets_root != "":
|
if ClassDB.class_exists("Metin2Proto") and assets_root != "":
|
||||||
proto = ClassDB.instantiate("Metin2Proto")
|
proto = ClassDB.instantiate("Metin2Proto")
|
||||||
add_child(proto)
|
add_child(proto)
|
||||||
var ip := assets_root.path_join("locale/locale/%s/item_proto" % cur_lang)
|
var ip := AssetRoot.locale_file("item_proto", cur_lang)
|
||||||
if not FileAccess.file_exists(ip):
|
if ip != "":
|
||||||
ip = assets_root.path_join("locale/locale/en/item_proto")
|
|
||||||
if FileAccess.file_exists(ip):
|
|
||||||
proto.call("load_item_proto", ip)
|
proto.call("load_item_proto", ip)
|
||||||
# CPythonPlayer::IsEquipItemInSlot and SetItemData both consult the
|
# CPythonPlayer::IsEquipItemInSlot and SetItemData both consult the
|
||||||
# same ItemManager table. Share this authority with native senders so
|
# same ItemManager table. Share this authority with native senders so
|
||||||
@@ -505,10 +503,8 @@ func setup(m2client: Node, assets_root: String,
|
|||||||
chat.set_proto(proto)
|
chat.set_proto(proto)
|
||||||
if quest_dialog and quest_dialog.has_method("set_proto"):
|
if quest_dialog and quest_dialog.has_method("set_proto"):
|
||||||
quest_dialog.set_proto(proto)
|
quest_dialog.set_proto(proto)
|
||||||
var mp := assets_root.path_join("locale/locale/%s/mob_proto" % cur_lang)
|
var mp := AssetRoot.locale_file("mob_proto", cur_lang)
|
||||||
if not FileAccess.file_exists(mp):
|
if mp != "":
|
||||||
mp = assets_root.path_join("locale/locale/en/mob_proto")
|
|
||||||
if FileAccess.file_exists(mp):
|
|
||||||
proto.call("load_mob_proto", mp)
|
proto.call("load_mob_proto", mp)
|
||||||
await _yield()
|
await _yield()
|
||||||
net_play.proto = proto # 按 race 分类 NPC/怪(bType 在本 fork 不可靠)
|
net_play.proto = proto # 按 race 分类 NPC/怪(bType 在本 fork 不可靠)
|
||||||
|
|||||||
@@ -175,8 +175,8 @@ func _run() -> void:
|
|||||||
var audio: Node = Audio.new()
|
var audio: Node = Audio.new()
|
||||||
get_root().add_child(audio)
|
get_root().add_child(audio)
|
||||||
audio.setup(assets)
|
audio.setup(assets)
|
||||||
var mp := assets.path_join("locale/locale/en/mob_proto")
|
var mp := AssetRoot.locale_file("mob_proto")
|
||||||
if not FileAccess.file_exists(mp) or not proto.call("load_mob_proto", mp):
|
if mp == "" or not proto.call("load_mob_proto", mp):
|
||||||
print(" (skip: 无 mob_proto)")
|
print(" (skip: 无 mob_proto)")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -130,12 +130,12 @@ func _test_proto() -> void:
|
|||||||
return
|
return
|
||||||
var p: Object = ClassDB.instantiate("Metin2Proto")
|
var p: Object = ClassDB.instantiate("Metin2Proto")
|
||||||
get_root().add_child(p)
|
get_root().add_child(p)
|
||||||
var root := AssetRoot.sub("locale/locale/en")
|
var ip := AssetRoot.locale_file("item_proto")
|
||||||
if not FileAccess.file_exists(root + "/item_proto"):
|
if ip == "":
|
||||||
print(" (skip Metin2Proto load: no item_proto)")
|
print(" (skip Metin2Proto load: no 40250 pack)")
|
||||||
p.queue_free()
|
p.queue_free()
|
||||||
return
|
return
|
||||||
_ck(p.call("load_item_proto", root + "/item_proto"), "load_item_proto: %s" % p.call("get_last_error"))
|
_ck(p.call("load_item_proto", ip), "load_item_proto: %s" % p.call("get_last_error"))
|
||||||
_ck(p.call("item_count") > 1000, "item_count > 1000 (%d)" % p.call("item_count"))
|
_ck(p.call("item_count") > 1000, "item_count > 1000 (%d)" % p.call("item_count"))
|
||||||
var yang: Dictionary = p.call("item", 1)
|
var yang: Dictionary = p.call("item", 1)
|
||||||
_ck(yang.get("name", "") == "Yang", "item(1).name == Yang")
|
_ck(yang.get("name", "") == "Yang", "item(1).name == Yang")
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ func _run() -> void:
|
|||||||
if ClassDB.class_exists("Metin2Proto"):
|
if ClassDB.class_exists("Metin2Proto"):
|
||||||
proto = ClassDB.instantiate("Metin2Proto")
|
proto = ClassDB.instantiate("Metin2Proto")
|
||||||
get_root().add_child(proto)
|
get_root().add_child(proto)
|
||||||
var proto_path := assets.path_join("locale/locale/en/item_proto")
|
var proto_path := AssetRoot.locale_file("item_proto")
|
||||||
if not FileAccess.file_exists(proto_path) or not proto.call("load_item_proto", proto_path):
|
if proto_path == "" or not proto.call("load_item_proto", proto_path):
|
||||||
proto = null
|
proto = null
|
||||||
var effects := EffectRegistry.new()
|
var effects := EffectRegistry.new()
|
||||||
effects.setup(assets)
|
effects.setup(assets)
|
||||||
|
|||||||
@@ -355,11 +355,9 @@ func _rebuild_model() -> void:
|
|||||||
if _proto == null and ClassDB.class_exists("Metin2Proto"):
|
if _proto == null and ClassDB.class_exists("Metin2Proto"):
|
||||||
_proto = ClassDB.instantiate("Metin2Proto")
|
_proto = ClassDB.instantiate("Metin2Proto")
|
||||||
add_child(_proto)
|
add_child(_proto)
|
||||||
for lang in ["en", "common"]:
|
var ip := AssetRoot.locale_file("item_proto")
|
||||||
var ip := _assets.path_join("locale/locale/%s/item_proto" % lang)
|
if ip != "":
|
||||||
if FileAccess.file_exists(ip):
|
_proto.call("load_item_proto", ip)
|
||||||
_proto.call("load_item_proto", ip)
|
|
||||||
break
|
|
||||||
var equip := EquipModel.new()
|
var equip := EquipModel.new()
|
||||||
pv.add_child(equip)
|
pv.add_child(equip)
|
||||||
equip.setup_remote(null, func() -> Node: return pv, _assets, job,
|
equip.setup_remote(null, func() -> Node: return pv, _assets, job,
|
||||||
|
|||||||
Reference in New Issue
Block a user