Manual/Transport & Bootstrap/Startup, version-check & hot-update
v1.0.364 notes · build 2026-11-07 12:10 UTC

Startup, version-check & hot-update

18-state cold start · check-version · 4 patch streams · ChaCha wrap
#bootstrap #hotupdate

Cold-start state machine

18 states: Logo, Permission, CheckResVersion, DownloadManifest/DownloadUpdate, LoadDataTable, GetServerList, ConnectGame, Login, LoadScene→EnterGame. 1.0.364: CheckResVersion clears cached IP/port/conn-type on every cold start so the GSL call always runs static; the cached ip/port/zone shortcut (state 6→9) is taken only on mid-session reconnect (serverInfo redirect).

Check-version

GET {host}/gameservice/getlsu3dversion.php?packageName=com.fun.lastwar.gp&...&gm=0&server=<cached zone>&table_env=table_online&buildId=<versionCode>&returnJson=1&unityVersion=440. table_env=table_online is required for the server to return table_version and locale [live, 2026-10-05 A/B]; earlier builds sent both empty and were still answered live.

  • Hosts raced in parallel, first success wins; the winner becomes the base URL for getserverlist.php, getnotice.php, probe.php. ~5% of devices are A/B'd to ea- hosts via GrayUtils.GetPercentageFromMd5 = MD5(deviceId) read as LE uint64, mod 100 + 1 <= 5. Hosts: lastwar-serverlist-cf.lastwarapp.net, lastwar-serverlist-us-aws-ali.lastwargame.com, lastwar-serverlist-us-gcp-ali.lastwargame.com; ea bucket: lastwar-serverlist-ea-us-aws.lastwargame.com, -ea-us-gcp-. A valid response = non-empty code (error) or updateType in 0..3 + non-empty hotUpdateMsg.
  • Response fields (parser CheckResVersionState):
  • resMsg = Base64 DER RSA pubkey, fresh each check, feeds the GSL crypto
  • updateType: 0 patch, 1 optional dialog, 2 forced/blocking, 3 falls to default → acts as 0 (Login handler rewrites 3→0); downloadurl store link
  • hotUpdateMsg = ;-separated name,version,size,crc32 over GameRes,DllRes,DataTable,Lua,Download,PackageRes (size/crc32 = the manifest file itself; APK gameres.version = 2342,18537644,856103597) static
  • lwfile2 = version|bzSize|bzCrc[|rawSize|rawCrc];oldVer|patchSize|patchCrc;... (patch only when the local version matches an oldVer)
  • table_version = version,size,md5[,crc][;patch chain]; locale = version,lang1,...
  • client_config = |-separated "0"/"1" indexed by switch id (ClientSwitch.IsOn(N))
  • checkok — review mode only (skips table+locale updates). Live iOS 1.0.344(786) reply carried only 8 of these fields plus table_version/locale/empty x_table_version with table_online live.
  • The Login response may carry updateType (SFS Int) + new_version (UtfString); cross-server Login with 2 fails error 9 (1.0.367); 2026-10-04 iOS got updateType=3, new_version=1.0.350 live. new_version only gates role creation via ShumeiSdkManager.IsForbidCreateRole static.
  • resVersion = {GameRes}.{DllRes}_{lwfile2 version}F_{table_version}.{locale}; e.g. 2159.1505_821F_39516.23570 live. The tail is local state: CURRENT_TABLE_VERSION, LOCALE_VERSION. 1.0.364 APK baseline 2342.1501_825F_39432.23557; goclient sends "0" for missing parts — accepted for months.

4 independently-patched payloads

  • Asset bundles (gameres)
  • C# assemblies: 118 XOR'd .mdl in assets/Assemblies/; noUpdateAssemblies.json = [{"name":"SmartFox2X"}]; dllres v1501 lists 117 remote <asm>.encrypt.bytes; no mscorlib entry; updates land in persistentDataPath/Assemblies/<asm>.mdl, searched before the APK static
  • Lua bundle: flat LWLF file, no AssetBundle
  • Tables: plain ZIP table_*.data

On-disk XOR wrap

ChaCha reduced to 8 rounds (4 double-rounds), IETF layout, detected by magic swap:

Plain magicWrappedContentKeystream starts
50 4B 03 04CHACtable ZIPbyte 4
42 5A 68 (BZh)PRBbzip2 (Lua bundle)byte 4
BSDIFFPURBDPbsdiff patchbyte 8 (bytes 6-7 plaintext)

Keys derived via two self-referential ChaCha8 passes static.

XAssetPro (VEngine 6.1.5)

BundleFragment0.bytes = 539 MB, 8,059 UnityFS bundles end-to-end, no gaps; engine version stripped (5.x.x/0.0.0; real engine 2019.4.41f1); LZ4HC blocks. BundleOffsetTable.bytes/AliasOffsetTable.bytes map name/alias → (fragment index << 32) | offset; opened via AssetBundle.LoadFromFile(fragment, 0, offset). Manifests are text ([Version],[Directories],[Paths],[Bundles],[Groups]); bundle line id,name,crc32,size,pathIds,depIds,downloadModes,alias; alias = sha256(name w/o ext) + ext; 32-hex name suffix = MD5 of bytes; .version stamp version,size,crc32. Pack = bundles with downloadModes incl 0 (Base): 8,059 of 36,745 (gameres v2342), 539 MB of 3.04 GB; the rest on demand.

CDN & Lua bundle

  • CDN: https://lastwar-cdn.akamaized.net/hotupdate/ primary, fallbacks lastwar-cdn.lastwarapp.net, cdn.lastwar.com. Path <CDN>/<app id>/<Android|iOS>/<file>; manifest fetched as ZIP <name>_small_v<version>, CRC-checked; bundles by plain name only (alias HEAD → 404, name → 200) live. TLS certificate validation is disabled on this download path; integrity = manifest CRC-32 only static.
  • Lua bundle: LWScripts_<version>_u440.bz2, patch LWScripts_<new>_<old>_u440.patch; app ids com.fun.lastwar.gp / com.lastwar.ios; CDN copy is plain bzip2, the disk copy is re-encrypted as LWLF v2. Versions 780–826 all HTTP 200 live. Chunk counts: v793 = 18,514 (1.0.351), v825 = 18,733 (1.0.364 APK, tag 20260921_1892_App), v826 = 18,740 (live Android; 827/830 → 404), v821 = 18,740 (live iOS). Request schemas identical across all four.
notes build 2026-11-07 12:10 UTC · lw-notes@9c47e1b · sources: own capture/analysis cross-checked against the public dossier