Files
mtgodot-poc/extension/src/port/CMakeLists.txt
T
shenleiandClaude Opus 5 ffbb5d48d6 port(2P step 3c): stage python27.zip into the mobile sandbox
CPython opens the stdlib zip with its own stdio, so it needs a real path.
On the desktop res:// is a directory and globalize_path() is enough; in an
exported build res:// is inside the PCK, which nothing outside Godot reads.

  make_stdlib_zip.py now also writes <out>.sha256
  mtpython_stdlib_project copies python27.zip + .sha256 into project/
    (gitignored), and both export presets' include_filter lists them, so
    they enter the PCK — checked with --export-pack
  extension/src/python_stdlib.{h,cpp} (Metin2Python.stdlib_path in
    GDScript): reads the bytes out of the PCK, writes
    user://python27.zip.part, hashes the file as written against the
    shipped digest, then renames. A sandbox copy is reused only when it
    matches, so a first start killed mid-copy and a zip replaced by a new
    build both re-stage instead of feeding zipimport a torn file.
  PythonHost::SetDefaultStdLibPath / DefaultStdLibPath() answer with that
    path ($MT_PYTHON_STDLIB still wins). python_stdlib.cpp is the only unit
    that knows res:// / user://; port_platform stays godot-free.

Also fixes the Windows gate, which step 3b broke: without mtpython the
MinGW build compiled UserInterface/StdAfx.h (it includes ScriptLib/StdAfx.h,
as the original PCH does). port/CMakeLists.txt excludes that header and
PythonPackModule.cpp with ScriptLib, and platform/CMakeLists.txt excludes
platform/ScriptLib/ the same way.

Not done: on-device staging. The macOS test drives the mobile path with
force_stage=true, and nothing in the app boot calls it yet — the
interpreter only starts in the process with 2V0.

gates: python_stdlib_test.gd PASS (in-place path, staging, sha256, bytes
equal res://, no .part, ZIPReader finds encodings/__init__.py, no re-copy
on a second call, corrupted copy re-staged) · macOS ctest 27/27 ·
mingw + android + ios port_platform compile clean · port_map.py check 0
errors · key leak check 8/8 none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 09:39:33 +09:00

176 lines
9.5 KiB
CMake

# port_logic — mirror of the 40250 client logic (extension/src/port/<Lib>/<File>.{h,cpp}).
# No godot-cpp dependency: libmtgodot links it, and it also builds on its own (top-level
# -DMTGODOT_BUILD_EXTENSION=OFF -DMTGODOT_BUILD_PORT=ON) for targets without an extension build.
# See docs/PORT-PLAN.md batch 2A.
file(GLOB_RECURSE MT_PORT_SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB_RECURSE MT_PORT_HEADERS CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
if(WIN32)
# Stand-ins for Windows SDK headers; on Windows the real ones are used.
list(FILTER MT_PORT_HEADERS EXCLUDE REGEX "/common/shim/win32/")
endif()
# The script layer needs the embedded interpreter (mt3p::python), which has no Windows build yet
# (PORT-PLAN 批次 2P step 4). Without it, ScriptLib and its <Python-2.7/*> shims are left out.
# The units outside ScriptLib that reach it: UserInterface/StdAfx.h includes ScriptLib/StdAfx.h (as the
# original PCH does), and PythonPackModule.cpp is a Python module. Both go with it.
set(MT_PORT_NEEDS_PYTHON "/ScriptLib/|/UserInterface/StdAfx\\.h|/UserInterface/PythonPackModule\\.cpp")
if(NOT TARGET mtpython)
list(FILTER MT_PORT_SOURCES EXCLUDE REGEX "${MT_PORT_NEEDS_PYTHON}")
list(FILTER MT_PORT_HEADERS EXCLUDE REGEX "${MT_PORT_NEEDS_PYTHON}|/common/shim/sdk/Python-2\\.7/")
endif()
add_library(port_logic STATIC ${MT_PORT_SOURCES})
target_include_directories(port_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
# 40250 system headers the ported sources keep including verbatim: D3D8/D3DX/Miles SDK types everywhere,
# Windows/CRT ones only where the platform has none.
set(MT_PORT_SHIMS ${CMAKE_CURRENT_SOURCE_DIR}/common/shim/sdk)
if(NOT WIN32)
list(APPEND MT_PORT_SHIMS ${CMAKE_CURRENT_SOURCE_DIR}/common/shim/win32)
endif()
target_include_directories(port_logic PUBLIC ${MT_PORT_SHIMS})
# Libraries 40250 links that are vendored as-is: <lzo/lzo1x.h> (EterBase/lzo.h), <cryptopp/*>
# (EterBase/cipher.h), <Python-2.7/*> (ScriptLib).
target_link_libraries(port_logic PUBLIC mt3p::minilzo mt3p::cryptopp)
if(TARGET mtpython)
target_link_libraries(port_logic PUBLIC mt3p::python)
endif()
# 40250 sources carry MSVC `#pragma warning(...)` lines verbatim, and non-literal printf formats
# (EterImageLib/StdAfx.h) that the Android toolchain turns into errors.
set(MT_PORT_WARNING_FLAGS)
if(NOT MSVC)
set(MT_PORT_WARNING_FLAGS -Wno-unknown-pragmas -Wno-format-security)
endif()
target_compile_options(port_logic PUBLIC ${MT_PORT_WARNING_FLAGS})
target_compile_features(port_logic PUBLIC cxx_std_20)
# Armadillo nanomite markers (StdAfx.h `__asm _emit` jumps over a signature): x86-32 MSVC inline asm that no
# target here accepts, and a no-op without the Armadillo wrapper, so they expand to nothing.
target_compile_definitions(port_logic PUBLIC "NANOBEGIN=" "NANOEND=")
if(MINGW)
target_compile_options(port_logic PUBLIC -include ${CMAKE_CURRENT_SOURCE_DIR}/common/Win32MinMax.h)
endif()
# EterPack/EterPack.cpp includes EterPackKeys.generated.h in place of the two TEA key arrays: the keys are
# read from the reference EterPack.cpp at configure time and written into the build tree, never the repository.
# Without the reference the arrays are zero and every COMPRESS/SECURITY entry fails to decode.
set(MT_40250_SOURCE "$ENV{MT_40250_SOURCE}" CACHE PATH "40250 ClientVS22/source reference root")
if(NOT MT_40250_SOURCE)
set(MT_40250_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/../../../../40250/Server Client TMP4/ClientVS22/source")
endif()
set(MT_PORT_GENERATED ${CMAKE_CURRENT_BINARY_DIR}/generated)
set(mt_eterpack_ref "${MT_40250_SOURCE}/EterPack/EterPack.cpp")
set(mt_eterpack_keys "")
if(EXISTS "${mt_eterpack_ref}")
file(READ "${mt_eterpack_ref}" mt_ref_text)
foreach(arr s_adwEterPackKey s_adwEterPackSecurityKey)
string(REGEX MATCH "static DWORD ${arr}\\[\\][ \t\r\n]*=[ \t\r\n]*{[^}]*}" block "${mt_ref_text}")
string(REGEX MATCHALL "[0-9]+" values "${block}")
list(LENGTH values n)
if(NOT n EQUAL 4)
message(FATAL_ERROR "port: ${arr} not found in ${mt_eterpack_ref}")
endif()
list(JOIN values ", " values)
string(APPEND mt_eterpack_keys "static DWORD ${arr}[] = { ${values} };\n")
endforeach()
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${mt_eterpack_ref}")
else()
message(WARNING "port: ${mt_eterpack_ref} not found (set MT_40250_SOURCE); EterPack keys are zero")
string(APPEND mt_eterpack_keys "static DWORD s_adwEterPackKey[] = { 0, 0, 0, 0 };\n"
"static DWORD s_adwEterPackSecurityKey[] = { 0, 0, 0, 0 };\n")
endif()
file(CONFIGURE OUTPUT ${MT_PORT_GENERATED}/EterPackKeys.generated.h
CONTENT "// Generated from the 40250 reference EterPack.cpp by port/CMakeLists.txt; do not commit.\n#pragma once\n${mt_eterpack_keys}")
target_include_directories(port_logic PRIVATE ${MT_PORT_GENERATED})
if(WIN32)
target_link_libraries(port_logic PUBLIC winmm)
endif()
# 40250 Distribute|Win32 preprocessor definitions per library (vs_files/<Lib>/<Lib>.vcxproj),
# minus WIN32/_WINDOWS/_LIB/_CRT_SECURE_NO_WARNINGS: Windows-only branches go through platform/,
# never through ported logic. UserInterface/Locale_inc.h feature macros come with that header.
set(MT_PORT_DEFINES_default NDEBUG)
set(MT_PORT_DEFINES_GameLib NDEBUG USE_LOD)
set(MT_PORT_DEFINES_UserInterface NDEBUG USE_LOD _DISTRIBUTE)
function(mt_port_apply_defines file)
file(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${file})
string(REGEX REPLACE "/.*" "" lib "${rel}")
if(DEFINED MT_PORT_DEFINES_${lib})
set(defs ${MT_PORT_DEFINES_${lib}})
else()
set(defs ${MT_PORT_DEFINES_default})
endif()
set_property(SOURCE ${file} APPEND PROPERTY COMPILE_DEFINITIONS ${defs})
endfunction()
foreach(src IN LISTS MT_PORT_SOURCES)
mt_port_apply_defines(${src})
endforeach()
# Gate: every header under port/ compiles on its own (and twice, for its include guard), with its
# library's definitions. As in 40250, where each library's StdAfx.h is the precompiled header of
# every translation unit, the library's StdAfx.h comes first. Built by default, so every platform
# build enforces it.
set(MT_PORT_GATE_SOURCES)
foreach(hdr IN LISTS MT_PORT_HEADERS)
file(RELATIVE_PATH rel ${CMAKE_CURRENT_SOURCE_DIR} ${hdr})
string(MAKE_C_IDENTIFIER "${rel}" id)
set(tu ${CMAKE_CURRENT_BINARY_DIR}/header_gate/${id}.cpp)
string(REGEX REPLACE "/.*" "" lib "${rel}")
set(pch "")
# 40250 spells it StdAfx.h or Stdafx.h; use the on-disk name for case-sensitive hosts.
file(GLOB stdafx RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${lib}/[Ss]td[Aa]fx.h)
if(NOT lib STREQUAL "common" AND stdafx)
set(pch "#include \"${stdafx}\"\n")
endif()
file(CONFIGURE OUTPUT ${tu} CONTENT "${pch}#include \"${rel}\"\n#include \"${rel}\"\n")
set_property(SOURCE ${tu} APPEND PROPERTY OBJECT_DEPENDS ${hdr})
list(APPEND MT_PORT_GATE_SOURCES ${tu})
# Definitions follow the header's library, not the generated file's location.
if(DEFINED MT_PORT_DEFINES_${lib})
set_property(SOURCE ${tu} APPEND PROPERTY COMPILE_DEFINITIONS ${MT_PORT_DEFINES_${lib}})
else()
set_property(SOURCE ${tu} APPEND PROPERTY COMPILE_DEFINITIONS ${MT_PORT_DEFINES_default})
endif()
endforeach()
add_library(port_header_gate OBJECT ${MT_PORT_GATE_SOURCES})
target_include_directories(port_header_gate PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${MT_PORT_SHIMS})
target_link_libraries(port_header_gate PRIVATE mt3p::minilzo mt3p::cryptopp)
if(TARGET mtpython)
target_link_libraries(port_header_gate PRIVATE mt3p::python)
endif()
target_compile_features(port_header_gate PRIVATE cxx_std_20)
target_compile_definitions(port_header_gate PRIVATE "NANOBEGIN=" "NANOEND=")
if(MINGW)
target_compile_options(port_header_gate PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/common/Win32MinMax.h)
endif()
target_compile_options(port_header_gate PRIVATE ${MT_PORT_WARNING_FLAGS})
add_dependencies(port_logic port_header_gate)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../platform ${CMAKE_CURRENT_BINARY_DIR}/../platform)
if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
add_executable(port_common_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_common_test.cpp)
target_link_libraries(port_common_test PRIVATE port_logic)
add_test(NAME port.common COMMAND $<TARGET_FILE:port_common_test>)
# Against the real 40250 Client/pack next to the reference source; skipped (77) when it is missing,
# failed instead under MT_ASSETS_STRICT=1.
set(MT_40250_CLIENT "$ENV{MT_40250_CLIENT}" CACHE PATH "40250 Client directory (pack/, Eternexus/)")
if(NOT MT_40250_CLIENT)
set(MT_40250_CLIENT "${MT_40250_SOURCE}/../../Client")
endif()
add_executable(port_eterpack_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_eterpack_test.cpp)
target_link_libraries(port_eterpack_test PRIVATE port_platform)
add_test(NAME port.eterpack COMMAND $<TARGET_FILE:port_eterpack_test> ${MT_40250_CLIENT})
set_tests_properties(port.eterpack PROPERTIES SKIP_RETURN_CODE 77)
if(TARGET mtpython)
add_executable(port_python_launcher_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_python_launcher_test.cpp)
target_link_libraries(port_python_launcher_test PRIVATE port_platform)
add_dependencies(port_python_launcher_test mtpython_stdlib)
add_test(NAME port.python_launcher
COMMAND $<TARGET_FILE:port_python_launcher_test> ${MT_40250_CLIENT} ${MT_PYTHON_STDLIB_ZIP})
set_tests_properties(port.python_launcher PROPERTIES SKIP_RETURN_CODE 77)
endif()
endif()