Initial import

This commit is contained in:
admin_d committed 2026-09-11 18:19:50 +05:00
commit dfcc43c72d
34 files changed
+3057

No files matched your search

+129
View File
@@ -0,0 +1,129 @@
"""Generate isolated Android CGE targets. No KaM stubs or rewritten game loaders."""
from __future__ import annotations
import json, os, shutil, urllib.parse, xml.etree.ElementTree as ET
from pathlib import Path
from common import ROOT, COMMIT, VERSION, CPUS, ProbeError, safe_relative, sha256, write_json
from source_audit import search_paths
UPSTREAM_DEFINES=['DBG_SKIP_SECURE_AUTH'] # Existing upstream switch; private multiplayer authentication is out of scope.
COMPILER_OPTIONS=['-B','-CX-','-XX-','-k--no-undefined','-k--no-gc-sections']
def imports_source(profile:dict)->tuple[str,list[str]]:
units=list(profile['units'])
points=profile['id'] in ('core_types','core_streams','integration')
streams=profile['id'] in ('core_streams','integration')
if points and 'KM_Points' not in units:units.append('KM_Points')
head='unit ProbeImports;\n{$mode delphi}{$H+}\ninterface\nuses ProbeReport;\nprocedure RunUpstreamChecks(R:TProbeReport);\nimplementation\n'
head+='uses SysUtils'+(' ,'+', '.join(units) if units else '')+';\n'
head+='procedure RunUpstreamChecks(R:TProbeReport);\n'
if points:
head+='var P,Q:TKMPoint;\n'
if streams:head+=' S:TKMemoryStreamBinary; I:Integer; U:UnicodeString;\n'
head+='begin\n'
if not units:
head+=" R.Add('upstream.imports','NOT_RUN','Platform-only APK; no KaM modules imported');\n"
else:
head+=" R.Add('upstream.imports','PASS','Real imported units initialized: "+', '.join(units)+"; this does not execute a game world');\n"
if points:
head+=''' try
P:=KMPoint(17,-3); Q:=KMPointRight(P);
R.Check('core.points',(P.X=17) and (P.Y=-3) and (Q.X=18) and (Q.Y=-3),
'Calls original KMPoint / KMPointRight, not a local replacement');
R.Check('core.point_layout',(SizeOf(TKMPoint)=8) and (SizeOf(TKMPointF)=8),
Format('TKMPoint=%d TKMPointF=%d',[SizeOf(TKMPoint),SizeOf(TKMPointF)]));
except on E:Exception do R.Add('core.points','FAIL',E.ClassName+': '+E.Message);end;
'''
if streams:
head+=''' try
S:=TKMemoryStreamBinary.Create;
try
S.Write(Integer(123456)); S.Write(P); S.WriteW(UnicodeString('KaM Android'));
S.Position:=0; S.Read(I); S.Read(Q); S.ReadW(U);
R.Check('core.stream_roundtrip',(I=123456) and (Q.X=P.X) and (Q.Y=P.Y)
and (U=UnicodeString('KaM Android')) and (S.Position=S.Size),
'Calls original TKMemoryStreamBinary Read/Write/ReadW/WriteW');
finally S.Free;end;
except on E:Exception do R.Add('core.stream_roundtrip','FAIL',E.ClassName+': '+E.Message);end;
'''
head+='end;\nend.\n'
return head,units
def asset_inventory(root:Path|None, includes:list[str]|None=None)->list[dict]:
if root is None:return []
root=root.resolve()
if not root.is_dir():raise ProbeError('Asset directory does not exist: '+str(root))
if includes:
result=[]
seen=set()
for raw in includes:
rel=safe_relative(raw.replace('\\','/'))
if rel in seen:continue
seen.add(rel);p=root/rel
if p.is_symlink():raise ProbeError('Asset symlinks are not allowed: '+str(p))
if not p.is_file():raise ProbeError('Requested asset path not found under asset root: '+rel)
result.append({'path':rel,'size':p.stat().st_size,'sha256':sha256(p),
'url':'castle-data:/game/'+urllib.parse.quote(rel,safe='/')})
return result
result=[]
for p in sorted(root.rglob('*')):
if p.is_symlink():raise ProbeError('Asset symlinks are not allowed: '+str(p))
if p.is_dir():continue
if not p.is_file():raise ProbeError('Not a regular asset: '+str(p))
rel=safe_relative(p.relative_to(root).as_posix())
if rel=='README.md' or '.git' in p.parts:continue
result.append({'path':rel,'size':p.stat().st_size,'sha256':sha256(p),
'url':'castle-data:/game/'+urllib.parse.quote(rel,safe='/')})
return result
def make_target(project:Path,profile:dict,cpu:str,build_id:str,source:Path|None,
extras:dict,assets_root:Path|None=None,assets:list[dict]|None=None)->dict:
if project.exists():raise ProbeError('Target directory must be fresh: '+str(project))
project.mkdir(parents=True)
shutil.copytree(ROOT/'code',project/'code')
text,units=imports_source(profile)
(project/'code/ProbeImports.pas').write_text(text,encoding='utf-8')
(project/'code/ProbeBuild.pas').write_text(
'unit ProbeBuild;\n{$mode objfpc}{$H+}\ninterface\nconst\n'+
f" ProbeBuildId='{build_id}';\n ProbeProfile='{profile['id']}';\n ProbeCpu='{cpu}';\n"+
f" ProbeUpstreamCommit='{COMMIT}';\n ProbePointerBytes={CPUS[cpu][3]};\nimplementation\nend.\n")
data=project/'data';data.mkdir()
entries=assets or []
if entries and assets_root is None:raise ProbeError('Missing asset root')
for e in entries:
dest=data/'game'/e['path'];dest.parent.mkdir(parents=True,exist_ok=True)
shutil.copyfile(assets_root/e['path'],dest)
if sha256(dest)!=e['sha256']:raise ProbeError('Asset changed while staging: '+e['path'])
write_json(data/'assets-manifest.json',{'schema':'kam-probe-assets/1','files':entries})
name='kam_probe_'+profile['id']
root=ET.Element('project',name=name,caption='KaM Android: '+profile['id'],
author='KaM Mobile',qualified_name='org.kammobile.archprobe.'+profile['id'].replace('_',''),
game_units=profile.get('game_unit','GameInitialize'),compiler='fpc',screen_orientation='landscape',fullscreen_immersive='true')
ET.SubElement(root,'version',value=VERSION,code='30')
options=ET.SubElement(root,'compiler_options');paths=ET.SubElement(options,'search_paths')
ET.SubElement(paths,'path',value='code/')
include=[]
if units:
if source is None:raise ProbeError('KaM source is required for '+profile['id'])
include=search_paths(source)
include.extend(Path(p).resolve() for p in extras.get('source_paths',[]))
for p in include:
if not p.is_dir():raise ProbeError('Source path missing: '+str(p))
ET.SubElement(paths,'path',value=os.path.relpath(p,project).replace(os.sep,'/'))
defines=ET.SubElement(options,'defines')
for define in (UPSTREAM_DEFINES if units else []):ET.SubElement(defines,'define').text=define
custom=ET.SubElement(options,'custom_options')
for flag in COMPILER_OPTIONS:ET.SubElement(custom,'option').text=flag
libraries=ET.SubElement(options,'library_paths')
for p in extras.get('library_paths',[]):
p=Path(p).resolve()
if not p.is_dir():raise ProbeError('Native library path missing: '+str(p))
ET.SubElement(libraries,'path',value=os.path.relpath(p,project).replace(os.sep,'/'))
# Android SDK versions deliberately inherited from the locked CGE image.
ET.indent(root)
ET.ElementTree(root).write(project/'CastleEngineManifest.xml',encoding='utf-8',xml_declaration=True)
description={'profile':profile['id'],'name':name,'package':'org.kammobile.archprobe.'+profile['id'].replace('_',''),
'cpu':cpu,'units':units,'game_unit':profile.get('game_unit','GameInitialize'),'source_paths':[str(p) for p in include],
'compiler_options':COMPILER_OPTIONS,'upstream_defines':UPSTREAM_DEFINES if units else [],
'assets':entries,'shims':False}
write_json(project/'target.json',description)
return description