Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,161 @@
{
"Assets/Items/Equipment/Maps/Map_Paper_01/Map_Paper_01_folded.xob": "E443663E5241622A",
"Assets/Items/Equipment/Radios/Radio_R107M/Radio_R107M_01_item.xob": "77C9FCF3FB1246E9",
"Assets/Items/Equipment/workspaces/paper_map.agr": "83675D0B0AB1B2C3",
"Assets/Items/Equipment/workspaces/player_map.asi": "56794534F6B8C3D7",
"Assets/Props/Fabric/Flags/Data/Flag_1_2_SovietArmy.emat": "B47B52D9DA68905D",
"Assets/Props/Fabric/Flags/Data/Flag_1_2_USA.emat": "610803CC3F3D6649",
"Assets/Props/Military/AmmoBox/EquipmentBoxWooden_01/Data/EquipmentBoxWooden_Weapon_01_USSR.emat": "7D2E831AFA5CF50D",
"Assets/Props/Military/AmmoBox/EquipmentBoxWooden_01/EquipmentBoxWooden_Weapon_01.xob": "819ED3266EE5113B",
"Assets/Props/Military/Sandbags/Data/Sandbags_01_Burlap_MLOD.emat": "2169E8EB73E1C127",
"Assets/Props/Military/Sandbags/Data/Sandbags_01_short_Burlap.emat": "31529E064AA7C917",
"Assets/Props/Military/Sandbags/Sandbag_01_short.xob": "268DBDABB3C94AD7",
"Assets/Structures/BuildingsParts/Doors/DoorFrame_door_metal_A_RIGHT_EXT_B.xob": "E3BC73BFBC7AA497",
"Assets/Structures/Walls/Concrete/ConcreteWall_01_3m.xob": "0B96D139FEBCA006",
"Configs/Callsigns/Callsigns_US.conf": "CB7824D572DD0D5C",
"Configs/Callsigns/Callsigns_USSR.conf": "958043CE1648EC43",
"Configs/EntityCatalog/US/Characters_EntityCatalog_US.conf": "B56E8B4D4B195559",
"Configs/EntityCatalog/US/Groups_EntityCatalog_US.conf": "4E70E7C4C2C17D4A",
"Configs/EntityCatalog/US/InventoryItems_EntityCatalog_US.conf": "5F7EC52FC40A03E2",
"Configs/EntityCatalog/US/SupplyContainerItems_EntityCatalog_US.conf": "0BF36EFF9FFAF92B",
"Configs/EntityCatalog/US/WeaponTripod_EntityCatalog_US.conf": "0E4B62F186F97C43",
"Configs/EntityCatalog/USSR/Characters_EntityCatalog_USSR.conf": "3B679F1799D55F6F",
"Configs/EntityCatalog/USSR/Groups_EntityCatalog_USSR.conf": "37D5B75AAC3E72EA",
"Configs/EntityCatalog/USSR/InventoryItems_EntityCatalog_USSR.conf": "C53421647C3D0D2E",
"Configs/EntityCatalog/USSR/SupplyContainerItems_EntityCatalog_USSR.conf": "B4CA6761AC935808",
"Configs/EntityCatalog/USSR/WeaponTripod_EntityCatalog_USSR.conf": "DDF17C8C140BD1F2",
"Configs/Factions/BLUFOR.conf": "E7B7EA4823DD81F0",
"Configs/Factions/FIA.conf": "8053DB656DD18B14",
"Configs/Factions/OPFOR.conf": "B122E3B10BB3404B",
"Configs/GameOverScreen/BaseGameOverScreensConfig.conf": "651583FFFB7FC765",
"Configs/Identities/FactionIdentity_US.conf": "B600946E9ACC17C2",
"Configs/Identities/FactionIdentity_USSR.conf": "FBBCD73926989580",
"Configs/Identities/Territories/FactionHomeTerritory_US.conf": "B548A5B264939A98",
"Configs/Identities/Territories/FactionHomeTerritory_USSR.conf": "31FC78AF00523A99",
"Configs/Map/MapPlain.conf": "A786DD4868598F15",
"Configs/Navmesh/Navmesh_GM_Eden_Soldier.conf": "8BBC73472092902E",
"Configs/Navmesh/Navmesh_GM_Eden_Vehicle.conf": "0228A79846FD8A97",
"Configs/System/ActionContext/ManualCameraContext.conf": "C538807EEE48BED8",
"Configs/System/chimeraInputCommon.conf": "795184CF9AD764DB",
"Configs/System/chimeraMenus.conf": "C747AFB6B750CE9A",
"Configs/Systems/MissionSystems.conf": "C50579A2EC48E8EF",
"Prefabs/AI/Groups/Group_Base.et": "000CD338713F2B5A",
"Prefabs/AI/SCR_AIWorld.et": "E0A05C76552E7F58",
"Prefabs/AI/Waypoints/AIWaypoint_Cycle.et": "35BD6541CBB8AC08",
"Prefabs/AI/Waypoints/AIWaypoint_Defend.et": "93291E72AC23930F",
"Prefabs/AI/Waypoints/AIWaypoint_GetIn.et": "712F4795CF8B91C7",
"Prefabs/AI/Waypoints/AIWaypoint_GetOut.et": "C40316EE26846CAB",
"Prefabs/AI/Waypoints/AIWaypoint_Move.et": "750A8D1695BD6998",
"Prefabs/AI/Waypoints/AIWaypoint_SearchAndDestroy.et": "B3E7B8DC2BAB8ACC",
"Prefabs/Characters/Campaign/Final/OPFOR/USSR_Army/Regular/Campaign_USSR_Player_MortarCrew.et": "86E565387A634103",
"Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_Base.et": "836E7E39AAC5888B",
"Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_HeliPilot.et": "42A502E3BB727CEB",
"Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_Sniper.et": "0F6689B491641155",
"Prefabs/Characters/Factions/BLUFOR/US_Army/Character_US_Unarmed.et": "2F912ED6E399FF47",
"Prefabs/Characters/Factions/BLUFOR/US_Army/GreenBerets/Character_US_SF.et": "36E28C628A2F83CB",
"Prefabs/Characters/Factions/BLUFOR/US_Army/GreenBerets/Character_US_SF_Medic.et": "9A234C5857D92187",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_AR.et": "23ADBBC31B6A3DC6",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_AT.et": "1C78331E156A3D65",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_HeliPilot.et": "A62FA97C4EC64F14",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_LAT.et": "BF643BE4ADBDFDD3",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_RTO.et": "612F43A4D5AE765F",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_Rifleman.et": "DCB41B3746FDD1BE",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SL.et": "5436629450D8387A",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_SR.et": "333DA6244C7DA34C",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Character_USSR_Sharpshooter.et": "976AC400219898FA",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Spetsnaz/Character_USSR_SF.et": "2DB452B3EC386B92",
"Prefabs/Characters/Factions/OPFOR/USSR_Army/Spetsnaz/Character_USSR_SF_Medic.et": "8CA70597606992EC",
"Prefabs/Characters/Footwear/CombatBoots_US_01.et": "DAAFD15478BDE1C3",
"Prefabs/Characters/HeadGear/Hat_Patrol_US_01/Hat_Patrol_US_01.et": "F545C1BE7C99954A",
"Prefabs/Characters/HeadGear/Helmet_PASGT_01/Helmet_PASGT_01_base.et": "8A2B2A544F3ED831",
"Prefabs/Characters/Uniforms/Jacket_US_BDU_rolledup.et": "3CCA7A9BB4FD3197",
"Prefabs/Characters/Uniforms/Pants_US_BDU.et": "604BB72BE8E023C2",
"Prefabs/Decals/Dirt/Decal_Dirt_Patch_03.et": "4AD6EB80A8B1FA3A",
"Prefabs/Groups/BLUFOR/Group_US_FireTeam.et": "84E5BBAB25EA23E5",
"Prefabs/Groups/INDFOR/Group_FIA_FireTeam.et": "5BEA04939D148B1D",
"Prefabs/Groups/OPFOR/Group_USSR_Base.et": "8DE0C0830FE0C33D",
"Prefabs/Groups/OPFOR/Group_USSR_RifleSquad.et": "E552DABF3636C2AD",
"Prefabs/Items/Core/Item_Base.et": "C95E11C60810F432",
"Prefabs/Items/Equipment/Backpacks/Backpack_Kolobok.et": "41A9C55B61F375F0",
"Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et": "243948B23D90BECB",
"Prefabs/Items/Equipment/Kits/MedicalKit_01/MedicalKit_01_US.et": "AE578EEA4244D41F",
"Prefabs/Items/Equipment/Kits/MedicalKit_01/MedicalKit_01_USSR.et": "21EF98BFC1EB3793",
"Prefabs/Items/Equipment/Kits/RepairKit_01/RepairKit_01_wrench.et": "33B2DFDCD0EBA3DB",
"Prefabs/Items/Equipment/Maps/Map_Paper_01/PaperMap_01_folded.et": "13772C903CB5E4F7",
"Prefabs/Items/Equipment/Radios/Radio_ANPRC77.et": "9B6B61BB3FE3DFB0",
"Prefabs/Items/Equipment/Radios/Radio_R107M.et": "54C68E438DD34265",
"Prefabs/Items/Equipment/Radios/Radio_R148.et": "E1A5D4B878AA8980",
"Prefabs/Items/Equipment/Watches/Watch_SandY184A.et": "78ED4FEF62BBA728",
"Prefabs/Items/Fuel/Jerrycan_01/Jerrycan_01_item.et": "12D5AD21E383B768",
"Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_USSR_01.et": "C3F1FA1E2EC2B345",
"Prefabs/Items/Medicine/FieldDressing_01/FieldDressing_US_01.et": "A81F501D3EF6F38E",
"Prefabs/Items/Medicine/MorphineInjection_01/MorphineInjection_01.et": "0D9A5DCF89AE7AA9",
"Prefabs/Items/Medicine/SalineBag_01/SalineBag_USSR_01.et": "527D7C5D2E476BDC",
"Prefabs/Items/Medicine/SalineBag_01/SalineBag_US_01.et": "00E36F41CA310E2A",
"Prefabs/Items/Medicine/Tourniquet_01/Tourniquet_US_01.et": "D70216B1B2889129",
"Prefabs/Items/Misc/IntelligenceFolder_E_01/IntelligenceFolder_E_01.et": "6D56FED1E55A8F84",
"Prefabs/MP/Managers/Factions/FactionManager_Base.et": "A242612865F2A48E",
"Prefabs/MP/Managers/Factions/FactionManager_USxUSSR.et": "589540B35EBA8CDC",
"Prefabs/MP/Managers/Loadouts/LoadoutManager_USxUSSR.et": "97EF4C1A41F63DDB",
"Prefabs/MP/Modes/GameMode_Base.et": "0F307326459A1395",
"Prefabs/MP/ScriptedChatEntity.et": "F69BC912AC8236F9",
"Prefabs/Props/EmptyStorages/USSR_AmmoBox_empty.et": "EBBC859CCD94D96D",
"Prefabs/Props/Furniture/Table_01.et": "53506DCB398C7EBE",
"Prefabs/Props/Military/Antennas/Antenna_FIA_02/Antenna_FIA_02_base.et": "B3949F6D215A9F78",
"Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et": "CFFEB23FBEA00322",
"Prefabs/Structures/BuildingParts/Doors/DoorSet_Base.et": "87CADB5ADC989993",
"Prefabs/Structures/BuildingParts/Doors/Door_MetalGate_01_Short_LEFT_EXT_ST_B.et": "34B08027DBDE322A",
"Prefabs/Structures/BuildingParts/Doors/Door_Metal_A_RIGHT_EXT_COV_B.et": "67F17696FF278AE0",
"Prefabs/Structures/Military/Camps/TentSmallUS_01/TentSmallUS_01.et": "01AE5FD77A9A4C21",
"Prefabs/Structures/Military/Camps/TentUSSR_01/TentUSSR_01_base.et": "BE5D5CFA90224646",
"Prefabs/Structures/Military/Camps/TentUS_01/TentUSMedical_01.et": "F8427C3EFC52644E",
"Prefabs/Structures/Walls/BarbedWire/BarbedTape_01/BarbedTape_01_Long_noStake.et": "46904AF5D473FB75",
"Prefabs/Structures/Walls/BarbedWire/BarbedTape_01/BarbedTape_01_Short_noStake.et": "CAE1588B6265A2AB",
"Prefabs/Structures/Walls/Concrete/ConcreteWall_01/ConcreteWall_01_3m.et": "60E5729BEA18F875",
"Prefabs/Systems/Radio/RadioManager.et": "B8E09FAB91C4ECCD",
"Prefabs/Tasks/BaseTask.et": "1D0F815858EE24AD",
"Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_armed.et": "7BD282AF716ED639",
"Prefabs/Vehicles/Helicopters/Mi8MT/Mi8MT_unarmed_transport.et": "DF5CCB7C0FF049F4",
"Prefabs/Vehicles/Wheeled/UAZ469/UAZ469.et": "259EE7B78C51B624",
"Prefabs/Vehicles/Wheeled/UAZ469/UAZ469_PKM.et": "0B4DEA8078B78A9B",
"Prefabs/Vehicles/Wheeled/UAZ469/VehParts/Seats/UAZ469_seats_rear.et": "780AAF0A51AEE61A",
"Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_ammo.et": "3336BE330C4F355B",
"Prefabs/Vehicles/Wheeled/Ural4320/Ural4320_transport.et": "16C1F16C9B053801",
"Prefabs/Weapons/Ammo/Ammo_Shell_82mm_HE_O832DU.et": "98EC9C526AFBA282",
"Prefabs/Weapons/Attachments/Optics/Optic_4x20/Optic_4x20.et": "BD496EE1B40DC510",
"Prefabs/Weapons/Attachments/Optics/Optic_ARTII/Optic_ARTII.et": "D2018EDB1BBF4C88",
"Prefabs/Weapons/Attachments/Optics/Optic_PSO1/Optic_PSO1.et": "C850A33226B8F9C1",
"Prefabs/Weapons/Core/VehiclePeriscope_DummyWeapon_Base.et": "7E1DD1CE4D6A23F4",
"Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et": "B05A816C0BF50802",
"Prefabs/Weapons/Handguns/PM/Handgun_PM.et": "C0F7DD85A86B2900",
"Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et": "BBB50A815A2F916B",
"Prefabs/Weapons/Magazines/Magazine_762x51_M14_20rnd_SpecialBall.et": "627255315038152A",
"Prefabs/Weapons/Magazines/Magazine_762x54_SVD_10rnd_Sniper.et": "9CCB46C6EE632C1A",
"Prefabs/Weapons/Magazines/Magazine_9x18_PM_8rnd_Ball.et": "8B853CDD11BA916E",
"Prefabs/Weapons/Mortars/2B14/Mortar_2B14.et": "D1FFE458E8AC4BDB",
"Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et": "FA5C25BF66A53DCF",
"Prefabs/World/Game/PerceptionManager.et": "028DAEAD63E056BE",
"Sounds/Items/_SharedData/PickUp/Items_PickUp_Map.acp": "13F8D0DE67678118",
"Sounds/Vehicles/Wheeled/UAZ469/Vehicles_UAZ469_Horn.acp": "7E64D37BE3E1F3FA",
"UI/Fonts/RobotoCondensed/RobotoCondensed_Regular.fnt": "3E7733BAC8C831F6",
"UI/Textures/DeployMenu/Loadout-BG/Loadout_BG_USSR.imageset": "21533F2089005056",
"UI/Textures/DeployMenu/Loadout-BG/Loadout_US_BG.imageset": "F8AC305446D5D480",
"UI/Textures/Editor/EditableEntities/Factions/EditableEntity_Faction_USA.edds": "EB7F65DBC9392557",
"UI/Textures/Editor/EditableEntities/Factions/EditableEntity_Faction_USSR.edds": "40B12B0DF911B856",
"UI/Textures/Flags/US-flag-base-3it.edds": "46FB2FB2C2125A3C",
"UI/Textures/Flags/USSR-flag-base-3it.edds": "50824EB9F2C215AB",
"UI/Textures/Icons/icons_mapMarkersUI-glow.imageset": "67B3A6DC2D712B52",
"UI/Textures/Icons/icons_mapMarkersUI.imageset": "E23427CAC80DA8B7",
"UI/Textures/Icons/icons_wrapperUI-glow.imageset": "00FE3DBDFD15227B",
"UI/Textures/Icons/icons_wrapperUI.imageset": "3262679C50EF4F01",
"UI/layouts/HUD/GameOver/EndScreen/EndScreenContent_NoImage.layout": "C4507D07F3C54831",
"UI/layouts/Menus/PauseMenu/PauseMenuButton.layout": "9ECCD201BCF07E95",
"UI/layouts/Task/TaskListEntry.layout": "B71975032150BAFB",
"UI/layouts/Task/TaskListEntryDescription.layout": "552EB1921B5C548C",
"UI/layouts/WidgetLibrary/Buttons/WLib_ButtonText.layout": "75C912A1C89BE6C2",
"UI/layouts/WidgetLibrary/ComboBox/WLib_ComboBox.layout": "4B5AE6E64037FFB4",
"UI/layouts/WidgetLibrary/EditBox/WLib_EditBox.layout": "0022F0B45ADBC5AC",
"system/textures/defWhite.edds": "A64A8C63E0FBAE87",
"worlds/Eden/Eden.ent": "853E92315D1D9EFE",
"worlds/MP/Navmeshes/LowResEden.nmn": "5E19E60F26327356"
}
@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Exercise exact dependency closure, resource ownership and isolated profile inputs."""
import copy
import hashlib
import json
from pathlib import Path
import shutil
import sys
import tempfile
import unittest
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'tools'))
import external_addons as ext
ROOT=Path(__file__).resolve().parents[1]
class ExternalAddonsTests(unittest.TestCase):
def setUp(self):
self.temp=tempfile.TemporaryDirectory();self.addCleanup(self.temp.cleanup)
self.home=Path(self.temp.name);self.source=self.home/'installed';self.source.mkdir()
self.addons=self.home/'profile'/'addons';self.addons.mkdir(parents=True)
self.cache=self.home/'profile'/'cache.json';self.value=ext.policy(ROOT)
for guid,deps in ext.ALLOWED.items():
folder=self.source/guid;folder.mkdir()
manifest='GameProject { GUID "'+guid+'" Dependencies { '+' '.join('"'+d+'"' for d in sorted(deps))+' } }\n'
(folder/'addon.gproj').write_text(manifest)
self.value['addons'][guid]['project_sha256']=hashlib.sha256(manifest.encode()).hexdigest()
data=b'RDB synthetic fixture\0'
for path,entry in self.value['resources'].items():
if entry['addon']==guid:data+=path.encode()+b'\0\x06\0\0\0\0\0'+bytes.fromhex(entry['guid'])[::-1]
(folder/'resourceDatabase.rdb').write_bytes(data);(folder/'data.pak').write_bytes(b'fixture '+guid.encode())
def test_resource_identity_requires_visible_owner(self):
for path,item in self.value['resources'].items():
self.assertTrue(ext.resource_allowed(self.value,{item['addon']},item['guid'],path))
self.assertFalse(ext.resource_allowed(self.value,{'core'},item['guid'],path))
self.assertFalse(ext.resource_allowed(self.value,{item['addon']},'0000000000000000',path))
self.assertEqual(ext.direct_for('core'),set(ext.DIRECT));self.assertEqual(ext.direct_for('chat'),set())
def test_policy_rejects_original_transitive_dependency(self):
root=self.home/'root';(root/'docs').mkdir(parents=True)
value=copy.deepcopy(self.value);value['addons'][ext.DIRECT[0]]['dependencies'].append('5F16D7E4A1CBE075')
(root/'docs/external-addons.json').write_text(json.dumps(value))
with self.assertRaisesRegex(ValueError,'transitive'):ext.policy(root)
def test_only_exact_authorized_closure_is_exposed_and_hashed(self):
unrelated=self.source/'original';unrelated.mkdir();(unrelated/'addon.gproj').write_text('GameProject { GUID "5F16D7E4A1CBE075" }')
records=ext.prepare_profile(self.addons,self.source,self.value,self.cache)
self.assertEqual(set(records),set(ext.ALLOWED))
self.assertEqual({p.name for p in self.addons.iterdir()},{'ERRI_'+g for g in ext.ALLOWED})
self.assertTrue(all(p.is_symlink() for p in self.addons.iterdir()))
self.assertTrue(ext.verify_profile(records,self.source,self.value,self.cache,self.addons))
self.assertEqual(ext.prepare_profile(self.addons,self.source,self.value,self.cache),records)
(self.source/ext.DIRECT[0]/'data.pak').write_bytes(b'mutated runtime')
self.assertFalse(ext.verify_profile(records,self.source,self.value,self.cache,self.addons))
def test_missing_and_duplicate_identity_fail(self):
guid=ext.DIRECT[0];shutil.copytree(self.source/guid,self.source/'duplicate')
with self.assertRaisesRegex(ValueError,'Duplicate'):ext.installed(self.source,self.value)
shutil.rmtree(self.source/'duplicate');shutil.rmtree(self.source/guid)
with self.assertRaisesRegex(ValueError,'Missing installed'):ext.installed(self.source,self.value)
def test_manifest_and_database_mutations_fail(self):
guid=ext.DIRECT[0];manifest=self.source/guid/'addon.gproj';old=manifest.read_text();manifest.write_text(old+'// changed')
with self.assertRaisesRegex(ValueError,'manifest changed'):ext.installed(self.source,self.value)
manifest.write_text(old)
resource=next(iter(self.value['resources'].values()));(self.source/resource['addon']/'resourceDatabase.rdb').write_bytes(b'wrong GUID')
with self.assertRaisesRegex(ValueError,'resource identity'):ext.installed(self.source,self.value)
def test_unrelated_profile_addon_and_wrong_link_fail(self):
(self.addons/'original').mkdir()
with self.assertRaisesRegex(ValueError,'Unrelated'):ext.prepare_profile(self.addons,self.source,self.value,self.cache)
(self.addons/'original').rmdir();guid=ext.DIRECT[0]
(self.addons/('ERRI_'+guid)).symlink_to(self.source/ext.DIRECT[1],target_is_directory=True)
with self.assertRaisesRegex(ValueError,'Unexpected private'):ext.prepare_profile(self.addons,self.source,self.value,self.cache)
def test_symlinked_runtime_file_fails(self):
pak=self.source/ext.DIRECT[0]/'data.pak';pak.unlink();pak.symlink_to(self.source/ext.DIRECT[1]/'data.pak')
with self.assertRaisesRegex(ValueError,'regular file'):ext.installed(self.source,self.value)
if __name__=='__main__':unittest.main()
File diff suppressed because one or more lines are too long
@@ -0,0 +1,243 @@
#!/usr/bin/env python3
"""Preview or write a deterministic DEVELOPMENT SOURCE BUNDLE; never build/publish."""
import argparse
import hashlib
import io
import json
import os
from pathlib import Path, PurePosixPath
import re
import stat
import tempfile
import zipfile
import sys
sys.dont_write_bytecode = True
from source_manifest import addon_manifest
from project_schema import parse_project
from external_addons import policy as external_policy, direct_for, resource_allowed, ALLOWED
from release_check import audit, read_json, evidence_path
ROOT = Path(__file__).resolve().parents[1]
BASE = '58D0FB3206B6F859'
PROJECTS = {'core': ('ERR_Core', '2528466DE220D91C', ())}
DIRECTORIES = {'Scripts', 'Prefabs', 'Configs', 'Missions', 'Worlds', 'UI', 'Materials', 'AI'}
SUFFIXES = {'.c', '.et', '.conf', '.ent', '.layer', '.layout', '.emat', '.bt', '.meta', '.gproj'}
EXCLUDED = {'.git', '.build', '.local', '__pycache__', 'profiles', 'profile', 'cache',
'runtime', 'evidence', 'vendor', 'vanilla', 'original', 'node_modules', '.agents', '.codex'}
README = '''# DEVELOPMENT SOURCE BUNDLE — Co10 Escape Reintroduced
Author: Jakub Jim Žáček.
This ZIP contains the single Co10 Escape Reintroduced addon, with Core, AI,
Intel, Map and Chat script folders. It is NOT a native .pak or a published mod.
Third-party assets are not redistributed.
Extract into a NEW development folder and register addons/core/addon.gproj in
Workbench. Install and register the RHS/WCS dependencies in EXTERNAL-ADDONS.json.
Open addons/core/addon.gproj and use Missions/ERR_Everon.conf.
Scenario: {C1E20667DCE324ED}Missions/ERR_Everon.conf
Use the installed engine's normal project/scenario workflow. This archive has not
been compiled by its bundler; no native caches or binaries are included.
Target: Everon, up to 12 players. The mod requires the listed external addons. This remains development source.
Manual prison/scavenging/radio/extraction play, remote joins/reconnects and
ownership, twelve connected players, aircraft support, repeated companion cleanup,
matched-workload FPS improvement and release qualification require separate
source-bound validation. Existing scripted fixtures are not those acceptances.
WORKBENCH-only probes are source, not proof of a test run or normal gameplay.
BUNDLE-MANIFEST.json lists each payload file's SHA-256 and project dependencies.
Verify extracted bytes against it before editing. The manifest excludes its own
hash to avoid self-reference. These hashes identify bytes, not authenticity,
license grants or provenance proof. Source review remains required before sharing.
Evidence, acceptance records, runtime profiles and test tools are intentionally
absent; consult the maintained repository for current status and retained logs.
Even --mode release only adds the existing repository qualification audit before
this same development-source handoff; it does not build/publish a game release.
'''
def canonical(name):
p = PurePosixPath(name)
if (not name or p.is_absolute() or p.as_posix() != name or '\\' in name
or ':' in name or any(ord(c) < 32 for c in name)
or any(part in {'.', '..'} or part.endswith((' ', '.')) for part in p.parts)):
raise ValueError(f'unsafe bundle path: {name!r}')
return p
def inventory(root):
"""Reuse the evidence inventory; impose a narrower reviewed handoff schema."""
# Preflight directories without following links, including otherwise excluded
# entries themselves. Excluded trees are pruned from this preflight and
# excluded from the payload; the shared source manifest may still hash them.
addons = root / 'addons'
if addons.is_symlink() or not addons.is_dir():
raise ValueError('addons must be a real directory')
for base, dirs, files in os.walk(addons, followlinks=False):
for name in list(dirs) + files:
path = Path(base) / name
if path.is_symlink():
raise ValueError(f'symlink refused: {path.relative_to(root)}')
if not path.is_dir() and not stat.S_ISREG(path.lstat().st_mode):
raise ValueError(f'nonregular addon entry: {path.relative_to(root)}')
dirs[:] = [d for d in dirs if d.lower() not in EXCLUDED]
original = addon_manifest(root)
result = {}
folded = set()
for name, digest in original.items():
parts = canonical(name).parts
if any(part.lower() in EXCLUDED for part in parts):
continue
if any(part.startswith('.') for part in parts):
raise ValueError(f'unreviewed hidden addon content: {name}')
if len(parts) < 3 or parts[1] not in PROJECTS:
raise ValueError(f'unknown addon project: {name}')
path = root / name
if not stat.S_ISREG(path.lstat().st_mode):
raise ValueError(f'nonregular addon file: {name}')
if name.casefold() in folded:
raise ValueError(f'case-insensitive path collision: {name}')
folded.add(name.casefold())
if parts[2] == 'addon.gproj' and len(parts) == 3:
pass
elif len(parts) < 4 or parts[2] not in DIRECTORIES or path.suffix not in SUFFIXES - {'.gproj'}:
raise ValueError(f'unreviewed addon source/resource format: {name}')
result[name] = digest
return result
def validate(payload, root):
projects = {}
external = external_policy(root)
own_ids = read_json(evidence_path(root, 'docs/addons.json'))
if own_ids != {name: item[1] for name, item in PROJECTS.items()}:
raise ValueError('successor identity catalog differs from the single owned project')
for name, (identity, guid, deps) in PROJECTS.items():
text = payload.get(f'addons/{name}/addon.gproj', b'').decode('utf-8')
match = parse_project(text, allow_core_menu=name == 'core')
expected = {BASE, *(PROJECTS[d][1] for d in deps), *direct_for(name)}
found = re.findall(r'"([A-F0-9]{16})"', match[4]) if match else []
if not match or match[1] != identity or match[2] != guid or set(found) != expected or len(found) != len(expected):
raise ValueError(f'identity/dependency closure mismatch: {name}')
projects[name] = {'id': identity, 'guid': guid, 'dependencies': sorted(found)}
resources = {}
for name, data in payload.items():
if not name.endswith('.meta'):
continue
m = re.search(r'Name "\{([A-F0-9]{16})\}([^"\n]+)"', data.decode('utf-8'))
owner = name.split('/')[1]
if not m or f'addons/{owner}/{m[2]}.meta' != name or name[:-5] not in payload:
raise ValueError(f'mismatched/missing resource metadata: {name}')
if m[1] in resources or m[1] in own_ids.values() or m[1] == BASE:
raise ValueError(f'duplicate resource identity: {name}')
resources[m[1]] = (m[2], owner)
vanilla = read_json(evidence_path(root, 'docs/vanilla-resources.json'))
if set(resources) & set(vanilla.values()):
raise ValueError('successor resource reuses a vanilla identity')
for name, data in payload.items():
suffix = PurePosixPath(name).suffix
if suffix == '.meta':
continue
text = data.decode('utf-8') # Current reviewed handoff contains text assets only.
if re.search(r'\b(?:ESCT\w*|SCRTC_\w+|SCRT_\w+|EAI_\w+)\b', text):
raise ValueError(f'original ecosystem dependency: {name}')
if suffix not in {'.c', '.layer', '.gproj'} and name + '.meta' not in payload:
raise ValueError(f'resource lacks successor metadata: {name}')
owner = name.split('/')[1]
visible = {owner, *PROJECTS[owner][2]}
if 'core' in visible: visible.update(ALLOWED)
for guid, resource in re.findall(r'\{([A-F0-9]{16})\}([^"\n]+)', text):
if guid in resources:
path, target = resources[guid]
if path != resource or target not in visible:
raise ValueError(f'resource outside project dependency closure: {name}')
elif vanilla.get(resource) != guid and not resource_allowed(external,visible,guid,resource):
raise ValueError(f'unresolved or nonvanilla resource: {name}')
return projects
def build(root=ROOT, mode='development'):
root = Path(root).resolve()
if mode not in {'development', 'release'}:
raise ValueError('unknown bundle mode')
if mode == 'release':
blockers = audit(root)
if blockers:
raise ValueError('Not release-qualified:\n - ' + '\n - '.join(blockers))
before = inventory(root)
payload = {name: (root / name).read_bytes() for name in before}
if {name: hashlib.sha256(data).hexdigest() for name, data in payload.items()} != before:
raise ValueError('addon bytes changed during collection')
projects = validate(payload, root)
if mode == 'release':
blockers = audit(root)
if blockers:
raise ValueError('Not release-qualified after collection:\n - ' + '\n - '.join(blockers))
if inventory(root) != before:
raise ValueError('addon tree changed during collection')
payload['BUNDLE-README.md'] = (README + '\nThe combined scenario requires the separately installed authorized addons listed in EXTERNAL-ADDONS.json. Their assets are not bundled.\n').encode('utf-8')
payload['EXTERNAL-ADDONS.json'] = json.dumps(external_policy(root),indent=2,sort_keys=True).encode('utf-8')
manifest = {'schema_version': 1, 'kind': 'DEVELOPMENT SOURCE BUNDLE',
'native_build': False, 'release_package': False, 'projects': projects,
'files': {name: hashlib.sha256(data).hexdigest() for name, data in sorted(payload.items())}}
payload['BUNDLE-MANIFEST.json'] = (json.dumps(manifest, indent=2, sort_keys=True) + '\n').encode()
stream = io.BytesIO()
with zipfile.ZipFile(stream, 'w', compression=zipfile.ZIP_STORED) as archive:
for name, data in sorted(payload.items()):
entry = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
entry.create_system = 3
entry.external_attr = (stat.S_IFREG | 0o644) << 16
archive.writestr(entry, data)
return stream.getvalue(), manifest
def write_new(output, content, root=ROOT):
path = Path(output).absolute()
canonical(path.as_posix().lstrip('/'))
if path.suffix != '.zip' or path.resolve().is_relative_to(Path(root).resolve()):
raise ValueError('output must be a new .zip outside the source checkout')
for part in (path, *path.parents):
if part.is_symlink():
raise ValueError('output symlinks are refused')
if path.exists() or not path.parent.is_dir():
raise ValueError('output exists or parent directory is missing')
temporary = None
try:
with tempfile.NamedTemporaryFile(prefix='.erri-bundle-', dir=path.parent, delete=False) as file:
temporary = Path(file.name)
file.write(content)
file.flush()
os.fsync(file.fileno())
# Same-filesystem atomic publication; link refuses an existing destination.
os.link(temporary, path, follow_symlinks=False)
finally:
if temporary is not None:
temporary.unlink()
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--mode', choices=('development', 'release'), default='development')
parser.add_argument('--output', type=Path, help='explicit new .zip outside checkout; omitted means read-only preview')
args = parser.parse_args(argv)
try:
content, manifest = build(mode=args.mode)
if args.output:
write_new(args.output, content)
print(json.dumps({'kind': manifest['kind'], 'preview': args.output is None,
'payload_files': len(manifest['files']), 'archive_bytes': len(content),
'archive_sha256': hashlib.sha256(content).hexdigest(),
'projects': manifest['projects']}, indent=2, sort_keys=True))
return 0
except (OSError, ValueError, UnicodeError) as error:
print(f'Bundle refused: {error}')
return 1
if __name__ == '__main__':
raise SystemExit(main())