After building the APK, android/build.sh now picks the attached device (ANDROID_SERIAL for several),
installs the APK (reinstalling on a signature mismatch), syncs the zh client into files/Client and, with
--launch, wakes the phone and starts the app. Without a device it only builds.
push-client.sh is now incremental (android/sync_client.py): the device keeps files/Client/.mt_sync
(size, mtime, path of what the last sync wrote), so later syncs send only new and changed files as one
tar streamed through `adb shell -T run-as` (no copy in /data/local/tmp) and delete the ones gone
locally. Only game resources are synced; settings and logs the app writes there are kept. --locale zh
rebuilds build/zh-locale/Client first when its inputs changed. android/adb_env.sh picks adb and falls
back to server port 5038 when :5037 cannot bind.
Verified against a fake adb device: first sync (1.4 GB, byte-identical), no-op resync, changed files,
deletions, locale switch, --full, the install/launch path and the no-device path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/server/item-mall-shop becomes tools/server/npc-shops with one
directory per shop (shop.conf + items.tsv) and install.sh <shop dir>.
New mount shop: 35 ride tickets on NPC 20368 (renamed 坐骑商人 via
NPC_NAME -> mob_names.txt) beside the stable boy in a1/b1/c1. Verified
on 203: 35 slots at the drafted prices, bought + used 71114 -> mounted.
zh overlay: 71118 虎坐骑券(黄), 20368 坐骑商人.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the macOS-only AudioToolbox decoder with mt_host::decode_audio: a
RIFF/WAVE PCM parser plus vendored dr_mp3, resampled/remixed to 44.1 kHz
stereo float with SDL_ConvertAudioSamples. macOS and Android now share one
path; the AudioToolbox link is gone.
BGM is no longer cached in clips_ (a 5-minute track is ~100 MB of float):
only the playing track is kept, and a new one decodes on a worker thread
(the pack read stays on the main thread) so map changes do not stall.
host.audio_decode test: synthetic wav layouts + all 1578 pack .wav, 9 pack
.mp3 and 25 Client/BGM .mp3 decode (7060 s of audio in ~3.6 s on M2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Godot-era batch records move verbatim to docs/archive/PORT-HISTORY.md.
README and THIRD-PARTY no longer mention submodules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Untrack build-debug/ and build-asan/ (CMake trees committed by mistake in
c9389431) and ignore them.
- Remove extension/src/net and extension/src/pack (m2dev protocol and XChaCha
pack format, superseded by the ported 40250 code), their 18 unregistered tests
and the net/pack tools, the MT_BUILD_NET_TOOLS probe, and the libsodium and zstd
submodules they alone used.
- The fake login server's classic cipher, sequence table and wire layouts move to
extension/tests/classic/ (still used by port.login_flow, port.packet and the
native client's offline mode).
- Archive Godot/m2dev-era plans under docs/archive/; THIRD-PARTY.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eterpack_pack writes locale packs in the original format (COMPRESS
entries, LZO+TEA index), and build_zh_locale.py packs locale/zh into
locale_zh.eix/.epk registered in pack/Index instead of leaving loose
files.
The reviewed translations for names and keys still in English are
kept apart from the server snapshot in data/zh_40250/*_zh_overlay.txt
(1,607 items, 231 mobs/NPCs, 45 locale_game keys) and override it at
build time. Only YMIR, the coordinate-named warp markers and the empty
TOOLTIP_UNSEAL_LEFT_TIME remain English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mt_codepage implements code pages 874/932/936/949/950/1250-1258 from
Microsoft's bestfit<cp>.txt sources (script/gen_codepage_tables.py,
SHA-256 pinned). Win32Crt's MultiByteToWideChar/WideCharToMultiByte/
IsDBCSLeadByteEx and net/text_codec use it, so the Android API 24 build
no longer depends on iconv. port.codepage checks every table against
its source file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- UserInterface/PythonShop.cpp ported verbatim (shop module, initshop after initfly);
pending stub and module stubs removed.
- 40250 PythonApplicationCursor.cpp and SetMouseHandler moved into the platform
CPythonApplication on file-static members; GetCursorNum out of line, since uishop's
app.GetCursor() dereferenced the missing application object.
- Fake server: general store (npclist 9003) answers CG_ON_CLICK with GC_SHOP START,
CG_SHOP BUY/SELL2/END like CShop::Buy / CShopManager::Sell.
- port.login_flow step 10: click the shopkeeper, right-click buy, drag-sell, ESC close.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fake server spawns a stray dog outside the village's BANPK safe disc
and answers CG_TARGET/CG_ATTACK with GC_TARGET/GC_DAMAGE_INFO/GC_DEAD.
port.login_flow and python_game_render_test hold the left button on it
(MBF_SMART -> __OnPressActor) until it dies. Adds the host/test helper
PythonBoot::Evaluate / Metin2PythonHost.evaluate.
python_3d_surface: emission is now multiplied by the texture, like
D3D's emissive-then-MODULATE, which removes the grey click-glow quad and
the grey wash on characters. Damage numbers wait on CPythonSystem (4-g).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- UserInterface/PythonIME(+Module) ported verbatim; ime module stubs removed, initime() after initdbg()
- EterLib/IME.cpp in platform/: 40250 buffer/WMChar bodies, IMM/TSF processors no-op; committed
multi-byte text via PythonBoot::UIChar emulating GCS_RESULTSTR
- GameApplicationAdapter RunIME* -> CWindowManager; Godot keys -> ui_ime_key/ui_char
- port.login_flow/login_live type the account through the IME; chat CG_CHAT round-trip on the fake server
- python_game_render_test logs in with Godot key events
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without MT_PORT_PATH the app runs 40250 system.py on the Python UI and
3D surfaces; MT_PORT_PATH=legacy keeps the GDScript AppFlow. The
smoke/playable live tests stay on legacy, MT_TEST_MODE=python_ui remains
a port alias, and an unset switch falls back to legacy (with a warning)
when the Python host or the 40250 pack is missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameLib map/area/terrain units, PRTerrainLib Terrain/TextureSet,
PythonBackground(+Module) and ColorTransitionHelper ported verbatim;
pending stand-ins and background module stubs removed. Hand edits:
BeginEnvironment's pointer tag via uintptr_t, LoadWaterMap reads the
file's 32-bit longs. Terrain draw passes stay platform no-ops (Godot
draws the terrain). RecordingDevice gains textures, buffers and render
targets; orthographic draws become UI commands so CPythonMiniMap's
masked tiles show through minimap_image_filter in python_ui_surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EffectLib's 18 units are copied verbatim (Type.h: drop a comment backslash
that clang splices into the next enum line). The remaining Utils stubs get
the 40250 bodies, so .msf fly data resolve relative .mse attachments, and
CPythonResource registers mde/mdatr as 40250 does. login_flow spawns
level_up.mse and sees its particle draws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy PythonSkill.cpp and the EterBase/Poly formula parser verbatim, drop the
pending stand-in and the skill module stub, register initskill() after
initfly() and build CPythonSkill in m_pySkill's place. LoadLocaleData now
loads skilldesc/skilltable. Skill and item icon handles go through
Py_BuildPointer/PyTuple_GetPointer (ILP32 ints truncate 64-bit pointers).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy PythonMiniMap.cpp/PythonMiniMapModule.cpp and PRTerrainLib/StdAfx.cpp
verbatim, drop the pending stand-in and the miniMap module stub, register
initMiniMap() after initnet(). PR_FLOAT_TO_INTASM uses lrintf off MSVC;
D3DXColorModulate added. CMapOutdoor is not ported, so the pending
CMapManager::IsMapOutdoor() returns false (DIVERGENT) and the minimap's terrain
and atlas paths stay on 40250's no-outdoor-map branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy PythonTextTail.cpp/PythonTextTailModule.cpp verbatim, drop the pending
stand-in and the textTail module stub, register initTextTail() after
initChat() and call CPythonTextTail::Initialize() in Create as 40250 does.
Implement CScreen::ProjectPosition/UnprojectPosition (were stubs) with
D3DXVec3Project/Unproject, and ApplicationStringTable_GetString(DWORD) over
UserInterface.rc's English string table. port.login_flow projects the ground
item's name tail in an ALT frame and picks it back by screen position.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy PythonExchange.cpp/PythonExchangeModule.cpp verbatim, drop the pending
stand-in and the exchange module stub, register initTrade() after
initNonPlayer(). The fake server answers CG_EXCHANGE START with the partner's
item + gold and CANCEL with END; port.login_flow drives the exchange round-trip
through the Python exchange/net modules and uiexchange.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verbatim 40250 CPythonItem and the item Python module replace the
pending stand-in and the module stub; CPythonApplication::Create calls
m_pyItem.Create() in 40250 order. itemGetIconImage returns its handle
through Py_BuildPointer (the ILP32 int truncated it and wndMgr.SetSlot
crashed on the live inventory). CMapManager::GetNormal evaluates
CTerrain::CalculateNormal on the native height tile; the item text-tail
members stay stubs, Pick answering -1 like an empty tail map.
The fake server drops vnum 19 next to the actor at entergame and answers
CG_ITEM_PICKUP with GC_ITEM_GROUND_DEL; login_flow picks it up through
player.PickCloseItem and checks item.SelectItem(19).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verbatim 40250 GameLib ItemData/ItemManager and UserInterface
PythonNonPlayer(+Module) replace their pending stand-ins and the
nonplayer module stub. LoadLocaleData's body moves into the platform
CPythonApplication stand-in, so the select phase loads item_proto and
mob_proto; CPythonSkill is still pending, so it returns false after the
tables load. login_flow checks item 19 (Sword+9) and mob 101 (Wild Dog),
offline and live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verbatim 40250 CServerStateChecker and the ServerStateChecker Python
module replace the stub; PythonBoot owns the instance like
CPythonApplication::m_kServerStateChecker. The fake server gets a state
port that answers CG_STATE_CHECKER the way CInputHandshake and
CInputDB::RespondChannelStatus do; login_flow requests the channel list
after the character list (live: {1: 1, 2: 1}).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The network stream phases, AccountConnector, NetStream/NetDevice/
NetAddress, EterPack*, lzo/tea/cipher/Timer and friends were copied in
2V1/2A but never marked. port_copy diff: 23 exact mechanical copies,
5 with // PORT: edits only; no platform stand-ins. Logic layer 67.9%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Verbatim copies of the 40250 flying-object units and the effect/fly
Python modules replace the pending CFlyingManager stand-in and the two
module stubs; initeffect/initfly run after initnet as in 40250 and the
flying manager gets the background as its map manager.
CMapManager::GetTerrainHeight is added over the native height tiles.
login_flow checks playersettingmodule registered the indexed fly data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both units were copied verbatim in 2V2-b and are on the running path since
2V3; diffed against 40250 (only include case, comment encoding and two
PORT edits) and no duplicate definitions outside port/. P0 queue is empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>