#!/usr/bin/env python3
"""Repackage pinned official Warzone 2100 4.7.0; regular npm builds are offline."""
# SPDX-License-Identifier: GPL-2.0-or-later
from pathlib import Path
import gzip, hashlib, html, json, re, tarfile, urllib.request, zipfile
ROOT = Path(__file__).resolve().parent.parent
CACHE = ROOT / 'artifacts/warzone2100/upstream'
DEST = ROOT / 'public/computer-games/vendor/warzone2100'
CHUNK = 20 * 1024 * 1024
UPSTREAM = {
 'warzone2100_web_wasm32_archive.zip': 'f729df690038511e0dee662554475a9a9a7c8a9488a268d647b93e7953820ee3',
 'warzone2100_src.tar.xz': '95ee4d5b88680ea1b1cf230b67ea84028e08a2458b84605ac9f7fb9eb97c4e37',
}
def sha(data): return hashlib.sha256(data).hexdigest()
def write(path, data):
 path.parent.mkdir(parents=True, exist_ok=True); path.write_bytes(data.encode() if isinstance(data,str) else data)
def record(path): return {'path':path.name, 'bytes':path.stat().st_size, 'sha256':sha(path.read_bytes())}
def split(path, data):
 files=[]
 for i,start in enumerate(range(0,len(data),CHUNK)):
  part=path if len(data)<=CHUNK else path.with_name(path.name+f'.part{i+1:02d}')
  write(part,data[start:start+CHUNK]); files.append(record(part))
 return files
CACHE.mkdir(parents=True, exist_ok=True)
ledger={}
for name,digest in UPSTREAM.items():
 url='https://github.com/Warzone2100/warzone2100/releases/download/4.7.0/'+name
 path=CACHE/name
 if not path.exists(): urllib.request.urlretrieve(url,path)
 if sha(path.read_bytes())!=digest: raise ValueError('Upstream checksum mismatch: '+name)
 ledger[name]={'url':url,'sha256':digest,'bytes':path.stat().st_size}
with zipfile.ZipFile(CACHE/'warzone2100_web_wasm32_archive.zip') as z:
 data=z.read('warzone2100.data')
 wasm=z.read('warzone2100.wasm')
 script=z.read('warzone2100.js').decode()
 # Only generated-JS integration changes; WASM and asset contents remain exact.
 old='openDatabase(db=>checkCachedPackage'
 assert script.count(old)==1
 script=script.replace(old,'if(Module.leafData){processPackageData(Module.leafData)}else '+old)
 old='preInit();run();'
 assert script.count(old)==1
 script=script.replace(old,'Module.FS=FS;Module.leafPause=()=>{runtimeKeepalivePush();MainLoop.pause()};Module.leafResume=()=>{MainLoop.resume();runtimeKeepalivePop()};'+old)
 old='FS.syncfs(true,err=>{console.log("loaded from idbfs",FS.readdir(configDirPath));'
 assert script.count(old)==1
 script=script.replace(old,'FS.syncfs(true,err=>{Module.leafSaveStatus?.(err?"unavailable":"restored");console.log("loaded from idbfs",FS.readdir(configDirPath));')
 old='xhr.open(requestMethod,url_,!fetchAttrSynchronous,userNameStr,passwordStr);'
 assert script.count(old)==1
 script=script.replace(old,'if(/^https?:/.test(url_) && new URL(url_).origin!==location.origin) url_="/computer-games/vendor/warzone2100/offline.json";'+old)
 write(DEST/'offline.json','{}\n')
 write(DEST/'warzone2100.js','/* Leaf Arcade generated-JS changes, 2026-10-05; GPL-2.0-or-later. See NOTICE.txt. */\n'+script)
 write(DEST/'warzone2100.wasm.gz',gzip.compress(wasm,compresslevel=9,mtime=0))
 parts=split(DEST/'game.data.gz',gzip.compress(data,compresslevel=9,mtime=0))
 music=z.read('pkg/music/warzone2100-music.data')
 write(DEST/'music.data',music)
 music_js=z.read('pkg/music/warzone2100-music.js').decode()
 music_files=json.loads(re.search(r'loadPackage\((\{"files": .*?\})\);',music_js).group(1))['files']
 manifest={'version':'4.7.0','script':record(DEST/'warzone2100.js'),'wasm':record(DEST/'warzone2100.wasm.gz'),'data':parts,'dataBytes':len(data),'extras':[record(DEST/'music.data')],'musicFiles':music_files}
 write(DEST/'package.json',json.dumps(manifest,indent=2)+'\n')
with tarfile.open(CACHE/'warzone2100_src.tar.xz') as t:
 for name in ['COPYING','COPYING.NONGPL','COPYING.README','AUTHORS','platforms/emscripten/README-build.md','data/music/COPYING.README','data/music/COPYING.NONGPL']:
  try: write(DEST/name.replace('/','-'),t.extractfile('warzone2100/'+name).read())
  except KeyError: pass
 # Retain bundled third-party notices separately as well as in complete source.
 notices=[]
 for member in t.getmembers():
  if member.isfile() and member.size<200000 and ('3rdparty/' in member.name or '/pkg/licenses/' in member.name) and re.search(r'/(LICENSE[^/]*|COPYING[^/]*|copyright)$',member.name,re.I):
   notices.append(member.name+'\n'+t.extractfile(member).read().decode(errors='replace'))
 write(DEST/'THIRD-PARTY-NOTICES.txt','\n\n'.join(notices))
source=DEST/'source'
archive=CACHE/'warzone2100_src.tar.xz'
files=split(source/archive.name,archive.read_bytes())
write(source/'manifest.json',json.dumps({'name':archive.name,'sha256':UPSTREAM[archive.name],'parts':files},indent=2)+'\n')
links=''.join(f'<li><a download href="{f["path"]}">{f["path"]}</a> ({f["bytes"]:,} bytes)</li>' for f in files)
write(source/'index.html',f'''<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Warzone 2100 corresponding source</title><body><h1>Warzone 2100 4.7.0 — source and data</h1><p>Exact official release source and editable data, including third-party submodules, build instructions, licenses and credits. Numbered parts concatenate in order:</p><ul>{links}</ul><pre>cat warzone2100_src.tar.xz.part* &gt; warzone2100_src.tar.xz\nSHA-256: {UPSTREAM[archive.name]}</pre><p>Build instructions: <code>platforms/emscripten/README-build.md</code> in the archive. Dependency versions are pinned in <code>vcpkg.json</code> and the bundled vcpkg overlay ports. Use the release web-build workflow for its Emscripten SDK version.</p><p><a download href="leaf-integration.tar.gz">Complete readable Leaf Arcade integration source archive (GPLv2+)</a>. Individual files: <a href="../runtime.js">runtime.js</a>, <a href="../index.html">player HTML</a>, <a href="../player.css">CSS</a>, <a href="import-warzone2100.py">repackaging script and exact generated-JS changes</a>. The WASM is unchanged; the script bypasses the data cache using verified local bytes, exports FS, reports storage failures and routes optional remote checks to an empty local file.</p><p><a href="../NOTICE.txt">Full notices</a> · <a href="https://github.com/Warzone2100/warzone2100/tree/4.7.0">Official source</a></p></body></html>''')
write(source/'import-warzone2100.py',Path(__file__).read_bytes())
with tarfile.open(source/'leaf-integration.tar.gz','w:gz') as integration:
 for name in ['runtime.js','index.html','player.css']:
  integration.add(DEST/name,arcname='public/computer-games/vendor/warzone2100/'+name)
 integration.add(Path(__file__),arcname='scripts/import-warzone2100.py')
write(DEST/'NOTICE.txt','''Warzone 2100 4.7.0 — Pumpkin Studios / Eidos and the Warzone 2100 Project.
Engine and original source/data: GNU GPL version 2 or later, with the linking
exception in COPYING.README. See COPYING, COPYING.README, AUTHORS and
COPYING.NONGPL for complete terms, exceptions and credits. Some artwork is
CC BY-SA 3.0 or CC0; original credits and notices are retained. Music credits and
terms: data-music-COPYING.README and data-music-COPYING.NONGPL. Bundled third-party
notices: THIRD-PARTY-NOTICES.txt and the full release source archive.
Official WASM and data unchanged. No original commercial files are required.
Leaf Arcade integration, 2026-10-05, GPLv2+: lazy verified local downloads,
compressed/chunked data and WASM; preload bundled soundtrack; fit canvas,
touch mouse gestures, pause/audio teardown, local saves and keyboard escape.
Generated-JS changes: verified preload bypass; expose FS and keepalive-safe pause/resume; route optional remote\nupdate/compatibility checks to an empty local file; report IDBFS
restore failure. Exact changes in source/import-warzone2100.py.
Upstream launcher, analytics, CDN dependencies and service worker are not used.
This browser edition is intended for local singleplayer; no video package or
online service is supplied. The source download contains build instructions,
editable data and upstream third-party sources. No upstream endorsement implied.
https://wz2100.net/ ; https://github.com/Warzone2100/warzone2100
''')
initial=sum(f['bytes'] for f in [manifest['script'],manifest['wasm'],*manifest['data'],*manifest['extras']])
write(DEST/'provenance.json',json.dumps({'version':'4.7.0','initialBytes':initial,'upstream':ledger,'wasmUncompressedSHA256':sha(wasm),'dataUncompressedSHA256':sha(data),'files':[record(DEST/f['path']) for f in [manifest['script'],manifest['wasm'],*manifest['data'],*manifest['extras']]] + [record(DEST/name) for name in ['runtime.js','index.html','player.css','offline.json','NOTICE.txt']]},indent=2)+'\n')
print(f'Initial download: {initial/1024/1024:.1f} MiB')
