Support empty resources and project Android services
Sync changed files to Google Drive / sync (push) Successful in 1m2s

This commit is contained in:
admin_d committed 2026-09-14 23:49:46 +05:00
1 parent c6b5725c35
commit 19ee837986
5 files changed
+77 -6

No files matched your search

+42 -1
View File
@@ -1,11 +1,12 @@
"""Generate isolated Android CGE targets. No KaM stubs or rewritten game loaders."""
from __future__ import annotations
import hashlib, os, xml.etree.ElementTree as ET
import hashlib, os, re, shutil, xml.etree.ElementTree as ET
from pathlib import Path
from common import ROOT, ProbeError
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']
_ANDROID_SERVICE_NAME=re.compile(r'[A-Za-z][A-Za-z0-9_]*\Z')
_PROJECT_ATTRIBUTES={
'name':'kam_mobile', 'caption':'KaM Remake Mobile', 'author':'KaM Mobile',
@@ -60,6 +61,33 @@ def _permanent_manifest() -> tuple[ET.ElementTree, ET.Element]:
raise ProbeError('Invalid permanent compiler options')
return tree,root
def android_service_roots(root: Path) -> tuple[Path,...]:
"""Return validated immediate project-owned Android service directories."""
try:
if not root.exists(): return ()
if root.is_symlink() or not root.is_dir():
raise ProbeError('Android services root must be a real directory: '+str(root))
resolved_root=root.resolve()
services=[]
for service in sorted(root.iterdir(),key=lambda path:path.name):
if service.is_symlink():
raise ProbeError('Android service must not be a symlink: '+str(service))
if not service.is_dir(): continue
if not _ANDROID_SERVICE_NAME.fullmatch(service.name):
raise ProbeError('Unsafe Android service name: '+repr(service.name))
resolved_service=service.resolve()
if not resolved_service.is_relative_to(resolved_root):
raise ProbeError('Android service escapes its root: '+str(service))
for path in service.rglob('*'):
if path.is_symlink():
raise ProbeError('Android service contains a symlink: '+str(path))
services.append(service)
return tuple(services)
except ProbeError:
raise
except (OSError, ValueError, RuntimeError) as error:
raise ProbeError(f'Cannot inspect Android services: {error}') from error
def materialize_application_project(project: Path, source_resolution: dict) -> dict:
"""Create one fresh Castle project from the permanent manifest and RP01 paths."""
if project.exists():raise ProbeError('Target directory must be fresh: '+str(project))
@@ -78,6 +106,19 @@ def materialize_application_project(project: Path, source_resolution: dict) -> d
for path in paths:ET.SubElement(slot,'path',value=_manifest_path(path,project,kind))
ET.indent(tree)
tree.write(project/'CastleEngineManifest.xml',encoding='utf-8',xml_declaration=True)
source_services=ROOT/'app/android_services'
services=android_service_roots(source_services)
if services:
destination_root=project/'android_services'
destination_root.mkdir()
for service in services:
destination=destination_root/service.name
if destination.exists() or destination.is_symlink():
raise ProbeError('Android service destination already exists: '+str(destination))
try:
shutil.copytree(service,destination)
except OSError as error:
raise ProbeError(f'Cannot copy Android service {service.name}: {error}') from error
version=_one_child(root,'version')
return {'project_dir':project,'data_root':data_root,'name':root.get('name'),'caption':root.get('caption'),
'package':root.get('qualified_name'),'version':version.get('value'),'version_code':int(version.get('code')),