§1.5 CG_CLIENT_VERSION: send_client_version() now branches on locale. EUROPE (this project's locale) sends CGClientVersion2 (header 0xf1, 67B, timestamp "1215955205"); non-EUROPE sends CGClientVersion (0xfd) + CMake-injected MT_BUILD_TIMESTAMP in C __TIMESTAMP__ form. New ClassicSession::set_executable_name() (default "metin2.bin"; TODO(W0): M2Client passes OS::get_executable_path().get_file()). m_version_sent set only after send_fixed() succeeds. wire_classic.h gains CGClientVersion2 + size-table entry. §1.10 fixed-length text: new extension/src/net/text_codec.h — to_wire()/ from_wire_str() apply a hard byte cap without splitting a UTF-8 multibyte sequence; godot::String overloads are __has_include-guarded so mtnet stays godot-free. classic_session.cpp routes 8 fixed-length name/sign/comment fills through to_wire(); parser reads GC_CHAR_ADDITIONAL_INFO name via from_wire_str(). New net_text_codec_test (ctest net.text_codec). Locale codepage (CP949/CP1252) transcoding remains a follow-up; wire bytes are currently UTF-8 (ASCII round-trips). §2.2 two-packet PC/NPC merge: ClassicParser GC_CHARACTER_ADD drops invisible races (20025/20038/20039); PC/NPC only stash a bare Entity into m_pending_actor; everything else spawns immediately with a name from root/npclist.txt (new npc_names.h + set_npclist_path(); $MT_ASSETS/$M2_ASSETS fallback; TODO(W0): M2Client passes the asset root). GC_CHAR_ADDITIONAL_INFO with no pending entry records m_last_error + m_unhandled_headers and drops (never half-creates); on a hit it merges the pending Entity with the additional-info's 9 fields into one mut_spawn_full() then erases the pending entry. New pending_actor_count() test hook; net_classic_session_test updated (version assertions -> 0xf1; stash -> merge -> spawn, immediate monster spawn, invisible-race drop, no-pending additional-info is a no-op). Depends on W2 §2.2 steps 4-5 (entity_store mut_char_info touch / mut_shop_sign cleanup) — the merge works with current W2 code as-is. Build + ctest 17/17 (16 + net.text_codec); netbridge_test, p10_test green. Docs: CLIENT-GAP.md W1 row + increment-50 change-log entry; CLIENT-GAP-FIX.md §1.5/§1.10/§2.2 status + facts and C.5 batch record. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014yvAPqPivoY7vmBbzgqK4W
190 lines
8.2 KiB
CMake
190 lines
8.2 KiB
CMake
# mtgodot GDExtension — registers Metin2Model / Metin2AnimPlayer, links libgr2.
|
|
# libgr2 is added at the top-level CMakeLists (vendored under ../libgr2), so here
|
|
# we just consume the xrender::libgr2 target.
|
|
|
|
# --- godot-cpp (submodule pinned at 101ae38034304346a46ea9ea84ae156d3e860496) ---
|
|
# The parent gitlink is the source-of-truth; .gitmodules intentionally has no
|
|
# moving branch. The selected bundle targets Godot 4.7's extension API.
|
|
set(GODOTCPP_API_VERSION "4.7" CACHE STRING "Target Godot API version" FORCE)
|
|
add_subdirectory(godot-cpp)
|
|
|
|
# --- vendored native deps: libsodium / libzstd / miniLZO ---
|
|
# Built from source (pinned submodules + vendored miniLZO) so the same tree
|
|
# compiles on the Android NDK and iOS SDK, which have no Homebrew. Exposes the
|
|
# aliases mt3p::sodium / mt3p::zstd / mt3p::minilzo. See docs/THIRD-PARTY.md.
|
|
add_subdirectory(third_party)
|
|
|
|
# --- mtnet: Metin2 net protocol core (no godot-cpp dep; libsodium only) ---
|
|
add_library(mtnet STATIC
|
|
src/net/secure_cipher.cpp
|
|
src/net/net_stream.cpp
|
|
src/net/entity_store.cpp
|
|
src/net/mark_image.cpp
|
|
src/net/classic/classic_stream.cpp # MT_PROTOCOL=classic (40250)
|
|
src/net/classic/classic_parser.cpp
|
|
src/net/classic/classic_session.cpp
|
|
src/net/classic/classic_cipher.cpp
|
|
)
|
|
target_include_directories(mtnet PUBLIC src/net)
|
|
target_link_libraries(mtnet PUBLIC mt3p::sodium mt3p::minilzo mt3p::cryptopp)
|
|
target_compile_features(mtnet PUBLIC cxx_std_20)
|
|
# §1.5: non-EUROPE CG_CLIENT_VERSION timestamp, in C __TIMESTAMP__ form
|
|
# ("Www Mmm dd hh:mm:ss yyyy"); regenerated on each CMake configure.
|
|
string(TIMESTAMP MT_BUILD_TS "%a %b %d %H:%M:%S %Y")
|
|
target_compile_definitions(mtnet PRIVATE MT_BUILD_TIMESTAMP="${MT_BUILD_TS}")
|
|
|
|
# --- mtpack: m2dev-fork asset pack reader/writer (libsodium + zstd) ---
|
|
add_library(mtpack STATIC
|
|
src/pack/eterpack.cpp
|
|
src/pack/pack_mount.cpp
|
|
src/pack/asset_source.cpp
|
|
)
|
|
target_include_directories(mtpack PUBLIC src/pack)
|
|
target_link_libraries(mtpack PUBLIC mt3p::sodium mt3p::zstd xrender::formats)
|
|
target_compile_features(mtpack PUBLIC cxx_std_20)
|
|
|
|
# --- mtproto: item_proto / mob_proto reader (libsodium + LZO) ---
|
|
add_library(mtproto STATIC src/proto/proto.cpp)
|
|
target_include_directories(mtproto PUBLIC src/proto)
|
|
target_link_libraries(mtproto PUBLIC mt3p::sodium mt3p::minilzo)
|
|
target_compile_features(mtproto PUBLIC cxx_std_20)
|
|
|
|
# Host-only CLIs + tests: they run on the build machine, so skip them entirely
|
|
# when cross-compiling the extension for a device.
|
|
set(MT_HOST_BUILD FALSE)
|
|
if(CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
|
|
set(MT_HOST_BUILD TRUE)
|
|
endif()
|
|
|
|
if(MT_HOST_BUILD)
|
|
add_executable(packtool tools/packtool.cpp)
|
|
target_link_libraries(packtool PRIVATE mtpack)
|
|
|
|
# net_probe: connect to an auth server, run the handshake + CG_LOGIN3, report.
|
|
add_executable(net_probe tools/net_probe.cpp)
|
|
target_link_libraries(net_probe PRIVATE mtnet)
|
|
|
|
# net_e2e: full auth -> game -> char list -> select -> PHASE_GAME against a
|
|
# real server; dumps entities / points / inventory. Live integration check.
|
|
add_executable(net_e2e tools/net_e2e.cpp)
|
|
target_link_libraries(net_e2e PRIVATE mtnet)
|
|
|
|
# net_classic_e2e: live 40250 classic handshake -> login -> char select -> PHASE_GAME probe.
|
|
add_executable(net_classic_e2e tools/net_classic_e2e.cpp)
|
|
target_link_libraries(net_classic_e2e PRIVATE mtnet)
|
|
endif()
|
|
|
|
if(BUILD_TESTING AND MT_HOST_BUILD)
|
|
add_executable(net_cipher_test tests/net_cipher_test.cpp)
|
|
target_link_libraries(net_cipher_test PRIVATE mtnet)
|
|
add_test(NAME net.cipher_roundtrip COMMAND $<TARGET_FILE:net_cipher_test>)
|
|
|
|
add_executable(net_loopback_test tests/net_loopback_test.cpp)
|
|
target_link_libraries(net_loopback_test PRIVATE mtnet)
|
|
add_test(NAME net.loopback_flow COMMAND $<TARGET_FILE:net_loopback_test>)
|
|
|
|
add_executable(net_entity_test tests/net_entity_test.cpp)
|
|
target_link_libraries(net_entity_test PRIVATE mtnet)
|
|
add_test(NAME net.entity_store COMMAND $<TARGET_FILE:net_entity_test>)
|
|
|
|
# 40250 "classic" backend (MT_PROTOCOL=classic).
|
|
add_executable(net_classic_wire_test tests/net_classic_wire_test.cpp)
|
|
target_include_directories(net_classic_wire_test PRIVATE src/net)
|
|
target_compile_features(net_classic_wire_test PRIVATE cxx_std_20)
|
|
add_test(NAME net.classic_wire COMMAND $<TARGET_FILE:net_classic_wire_test>)
|
|
|
|
add_executable(net_classic_stream_test tests/net_classic_stream_test.cpp)
|
|
target_link_libraries(net_classic_stream_test PRIVATE mtnet)
|
|
add_test(NAME net.classic_stream COMMAND $<TARGET_FILE:net_classic_stream_test>)
|
|
|
|
add_executable(net_classic_session_test tests/net_classic_session_test.cpp)
|
|
target_link_libraries(net_classic_session_test PRIVATE mtnet)
|
|
add_test(NAME net.classic_session COMMAND $<TARGET_FILE:net_classic_session_test>)
|
|
|
|
add_executable(net_classic_cipher_test tests/net_classic_cipher_test.cpp)
|
|
target_link_libraries(net_classic_cipher_test PRIVATE mtnet)
|
|
add_test(NAME net.classic_cipher COMMAND $<TARGET_FILE:net_classic_cipher_test>)
|
|
|
|
add_executable(net_classic_encstream_test tests/net_classic_encstream_test.cpp)
|
|
target_link_libraries(net_classic_encstream_test PRIVATE mtnet)
|
|
add_test(NAME net.classic_encstream COMMAND $<TARGET_FILE:net_classic_encstream_test>)
|
|
|
|
add_executable(net_classic_mark_test tests/net_classic_mark_test.cpp)
|
|
target_link_libraries(net_classic_mark_test PRIVATE mtnet)
|
|
add_test(NAME net.classic_mark COMMAND $<TARGET_FILE:net_classic_mark_test>)
|
|
|
|
add_executable(net_text_codec_test tests/net_text_codec_test.cpp)
|
|
target_link_libraries(net_text_codec_test PRIVATE mtnet)
|
|
add_test(NAME net.text_codec COMMAND $<TARGET_FILE:net_text_codec_test>)
|
|
|
|
add_executable(net_mark_test tests/net_mark_test.cpp)
|
|
target_link_libraries(net_mark_test PRIVATE mtnet mt3p::minilzo)
|
|
add_test(NAME net.guild_mark COMMAND $<TARGET_FILE:net_mark_test>)
|
|
|
|
add_executable(pack_roundtrip_test tests/pack_roundtrip_test.cpp)
|
|
target_link_libraries(pack_roundtrip_test PRIVATE mtpack)
|
|
add_test(NAME pack.roundtrip COMMAND $<TARGET_FILE:pack_roundtrip_test>)
|
|
|
|
add_executable(proto_test tests/proto_test.cpp)
|
|
target_link_libraries(proto_test PRIVATE mtproto)
|
|
add_test(NAME proto.item_mob COMMAND $<TARGET_FILE:proto_test>)
|
|
if(DEFINED ENV{M2_ASSETS})
|
|
set_tests_properties(proto.item_mob PROPERTIES ENVIRONMENT "M2_ASSETS=$ENV{M2_ASSETS}")
|
|
endif()
|
|
endif()
|
|
|
|
# --- the extension library ---
|
|
# macOS/Android: SHARED (Godot dlopen()s it). iOS: STATIC — the platform forbids
|
|
# loading dynamic libraries, so the archive is linked into the app at export time
|
|
# (see docs/PLATFORMS.md for the remaining F1 export/sign steps).
|
|
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
|
set(MT_LIB_KIND STATIC)
|
|
set(MT_PLAT_TAG "ios")
|
|
elseif(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
|
set(MT_LIB_KIND SHARED)
|
|
set(MT_PLAT_TAG "android")
|
|
else()
|
|
set(MT_LIB_KIND SHARED)
|
|
set(MT_PLAT_TAG "macos")
|
|
endif()
|
|
|
|
add_library(mtgodot ${MT_LIB_KIND}
|
|
src/register_types.cpp
|
|
src/metin2_model.cpp
|
|
src/metin2_anim.cpp
|
|
src/metin2_world.cpp
|
|
src/terrain_splat.cpp
|
|
src/static_object.cpp
|
|
src/tree_placeholder.cpp
|
|
src/environment_builder.cpp
|
|
src/water_builder.cpp
|
|
src/gr2_bridge.cpp
|
|
src/m2_material.cpp
|
|
src/dxt.cpp
|
|
src/asset_io.cpp
|
|
src/texture_util.cpp
|
|
src/net/m2_client.cpp
|
|
src/proto/proto_node.cpp
|
|
)
|
|
target_compile_features(mtgodot PRIVATE cxx_std_20)
|
|
target_link_libraries(mtgodot PRIVATE godot::cpp xrender::libgr2 xrender::formats mtnet mtproto)
|
|
|
|
# Godot's .gdextension expects, per platform:
|
|
# project/bin/libmtgodot.macos.template_{debug,release}.dylib
|
|
# project/bin/libmtgodot.ios.template_{debug,release}.a
|
|
# project/bin/libmtgodot.android.template_{debug,release}.<arch>.so
|
|
set(MT_CFG_TAG "$<IF:$<CONFIG:Release>,template_release,template_debug>")
|
|
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
|
|
# godot_arch_name-style suffix; OnePlus 13 = arm64.
|
|
set(MT_ARCH_SUFFIX ".${CMAKE_ANDROID_ARCH_ABI}")
|
|
string(REPLACE "arm64-v8a" "arm64" MT_ARCH_SUFFIX "${MT_ARCH_SUFFIX}")
|
|
else()
|
|
set(MT_ARCH_SUFFIX "")
|
|
endif()
|
|
set_target_properties(mtgodot PROPERTIES
|
|
PREFIX "lib"
|
|
OUTPUT_NAME "mtgodot.${MT_PLAT_TAG}.${MT_CFG_TAG}${MT_ARCH_SUFFIX}"
|
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/project/bin"
|
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/project/bin"
|
|
)
|