"""Copy verified original galaxy UI frames; no tracing, recolouring or redrawing."""
import hashlib
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from PIL import Image
root=Path('artifacts/galaxy-marker-research')
out=Path('XFEEDToolBox/Assets/GalaxyIcons')
out.mkdir(exist_ok=True)
icons=[]
current=[]
dependencies=json.loads((root/'map-bitmap-dependencies.json').read_text())
if not dependencies['allDependenciesResolved'] or dependencies['unknown']:
raise ValueError('Current map icon bitmap dependencies are incomplete')
def copy(key,source,resource,sprite,frame,label,note=None):
data=source.read_bytes()
if source.suffix=='.svg':
svg=ET.fromstring(data)
if any(x.tag.endswith('}image') or any('bitmap' in k.lower() for k in x.attrib) for x in svg.iter()):
raise ValueError('Unresolved original bitmap dependency: '+key)
if not any(x.tag.endswith('}path') for x in svg.iter()):
raise ValueError('Missing original vector geometry: '+key)
path=out/(key+source.suffix)
path.write_bytes(data)
row=dict(key=key,file=path.name,resource=resource,spriteId=sprite,frame=frame,label=label,
sha256=hashlib.sha256(data).hexdigest(),exportBytesUnchanged=True)
if note:row['note']=note
icons.append(row)
bookmarks=[('Planet',1,'planet'),('System',11,'system'),('Coriolis',21,'station_coriolis'),
('Bernal',31,'station_bernal'),('Orbis',41,'station_orbis'),('Outpost',51,'station_outpost'),
('SurfacePort',61,'surface_port'),('SurfaceSettlement',71,'surface_settlement'),
('LandablePlanet',81,'planet_landable'),('Default',91,'default'),('Star',101,'star'),
('Asteroid',111,'asteroid'),('DockableMegaship',121,'megaShip_dockable'),
('UndockableMegaship',130,'megaShip_undockable'),('Installation',140,'installation')]
for name,frame,label in bookmarks:
copy('Bookmark'+name,root/'ui-svg/DefineSprite_304_BookmarkButtonIcon'/f'{frame}.svg','ui',304,frame,label)
for key,sprite,name in [('TabBookmarks',251,'TabIconBookmarks'),('SelectedDestination',289,'NavMarkerIconSelected'),('CurrentLocation',291,'NavMarkerIconCurrent')]:
copy(key,root/f'ui-svg/DefineSprite_{sprite}_{name}/1.svg','ui',sprite,1,name)
for key,frame,label in [('Focus',9,'focused'),('Selection',19,'selection'),('Location',29,'location'),('ConflictZones',39,'conflictZones'),('Taxi',49,'taxi'),
('FocusThin',58,'focusedThin'),('SelectionThin',68,'selectionThin'),('LocationThin',78,'locationThin'),('ConflictZonesThin',88,'conflictZonesThin'),('TaxiThin',98,'taxiThin')]:
copy('Marker'+key,root/'labels-final-png/DefineSprite_41_GalaxyMapLabels_fla.ItemMarker_Graphics_8'/f'{frame}.png','labels',41,frame,label,
'Original timeline stop frame. BC7 source atlas decoded losslessly to RGBA with Pillow, then rendered by JPEXS; no red missing-bitmap placeholder. These are label rings, not category pin icons.')
for item in json.loads((root/'map-662-frames.json').read_text()):
label=item['label'];frame=item['frame']
if label=='none':continue
source=root/'map-svg/DefineSprite_662_Icons'/f'{frame}.svg'
svg=ET.parse(source).getroot()
raster=any(x.tag.endswith('}image') or any('bitmap' in k.lower() for k in x.attrib) for x in svg.iter())
if raster:source=root/'map-final-png/DefineSprite_662_Icons'/f'{frame}.png'
key='Map_'+label
copy(key,source,'map',662,frame,label,'Current map UI Icons timeline; marker legend resolves its DS icon string directly to this frame. Original colours retained.')
current.append(dict(label=label,key=key,frame=frame,extension=source.suffix,sprite=662))
copy('Map_CurrentLocationMarker',root/'map-svg/DefineSprite_1617_assets.CurrentLocationMarker/1.svg','map',1617,1,'assets.CurrentLocationMarker')
current.append(dict(label='CurrentLocationMarker',key='Map_CurrentLocationMarker',frame=1,extension='.svg',sprite=1617))
manifest=dict(generator='research/Build-GalaxyIconAssets.py',extractor='research/Extract-GalaxyUiResources.py',
exporter='JPEXS Free Flash Decompiler 26.4.0',gameProcessAccess=False,
exportBytesUnchanged=True,sources=json.loads((root/'resources.json').read_text()),
rasterDecode=dict(source='labels.bin',atlas='GalaxyMapLabels_I5.dds',width=2048,height=256,
gpuOffset=47519,gpuBytes=524288,gpuSha256=hashlib.sha256((root/'labels.bin').read_bytes()[47519:]).hexdigest(),
rgbaSha256=hashlib.sha256(Image.open(root/'labels-atlas.png').convert('RGBA').tobytes()).hexdigest(),
note='Same BC7 decoder as Build-GameIconAssets.py. Private labels-render.gfx changes only the external texture filename to the lossless decoded atlas PNG to avoid the JPEXS DDS decoder limitation.'),
currentMapRasterDecode=json.loads((root/'map-atlas-evidence.json').read_text()),
currentMapBitmapDependencies={key:dependencies[key] for key in ('allDependenciesResolved','framesWithBitmap','uniqueSubImages','sourceAtlasImageIds')},
currentMapNotes='Current mapui has Colonisation/Powerplay/Modes classes. Older galaxymapui BookmarkButtonIcon is retained as legacy UI artwork only; it does not prove world marker selection.',
nativeBookmarkMapping=dict(functionRva='26020A0',decoderRva='261F820',field='row+4C iconName uint',
values={'2':'station_bernal','3':'station_coriolis','4':'station_orbis','5':'station_outpost','6':'surface_port',
'7':'surface_settlement','8':'planet_landable','9':'planet','10':'star','11':'asteroid','12':'system',
'13':'megaShip_dockable','14':'megaShip_undockable','17':'station_dodec'},default='default',
unavailableFrame='station_dodec has no frame in this original BookmarkButtonIcon timeline; explicit default fallback'),icons=icons)
(out/'source.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
# Generate a closed, ordinal name catalogue from the actual timeline labels.
entries=[]
for item in current:
entries.append(' ["'+item['label']+'"] = new("'+item['label']+'","Assets/GalaxyIcons/'+item['key']+item['extension']+'",'+str(item['sprite'])+','+str(item['frame'])+'),')
code=('// Generated by research/Build-GalaxyIconAssets.py from original mapui timeline labels.\n'
'using System.Collections.Frozen;\n\nnamespace XFEEDToolBox.Core.Galaxy;\n\n'
'public static partial class GalaxyOriginalIcons\n{\n'
' /// <summary>Current original map UI icon names; colours and geometry are retained.</summary>\n'
' public static IReadOnlyDictionary<string,GalaxyOriginalIcon> MapFrames { get; } =\n'
' new Dictionary<string,GalaxyOriginalIcon>(StringComparer.Ordinal)\n {\n'+ '\n'.join(entries)+
'\n }.ToFrozenDictionary(StringComparer.Ordinal);\n\n'
' /// <summary>Returns null for names absent from the original movie. No semantic guessing.</summary>\n'
' public static GalaxyOriginalIcon? Map(string frameName)=>MapFrames.GetValueOrDefault(frameName);\n}\n')
Path('XFEEDToolBox.Core/Galaxy/GalaxyOriginalIcons.Map.cs').write_text(code,encoding='utf-8')
print(json.dumps(dict(imported=len(icons),output=str(out),sourceFramesUnchanged=True)))
"""Copy verified original galaxy UI frames; no tracing, recolouring or redrawing."""
import hashlib
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from PIL import Image
root=Path('artifacts/galaxy-marker-research')
out=Path('XFEEDToolBox/Assets/GalaxyIcons')
out.mkdir(exist_ok=True)
icons=[]
current=[]
dependencies=json.loads((root/'map-bitmap-dependencies.json').read_text())
if not dependencies['allDependenciesResolved'] or dependencies['unknown']:
raise ValueError('Current map icon bitmap dependencies are incomplete')
def copy(key,source,resource,sprite,frame,label,note=None):
data=source.read_bytes()
if source.suffix=='.svg':
svg=ET.fromstring(data)
if any(x.tag.endswith('}image') or any('bitmap' in k.lower() for k in x.attrib) for x in svg.iter()):
raise ValueError('Unresolved original bitmap dependency: '+key)
if not any(x.tag.endswith('}path') for x in svg.iter()):
raise ValueError('Missing original vector geometry: '+key)
path=out/(key+source.suffix)
path.write_bytes(data)
row=dict(key=key,file=path.name,resource=resource,spriteId=sprite,frame=frame,label=label,
sha256=hashlib.sha256(data).hexdigest(),exportBytesUnchanged=True)
if note:row['note']=note
icons.append(row)
bookmarks=[('Planet',1,'planet'),('System',11,'system'),('Coriolis',21,'station_coriolis'),
('Bernal',31,'station_bernal'),('Orbis',41,'station_orbis'),('Outpost',51,'station_outpost'),
('SurfacePort',61,'surface_port'),('SurfaceSettlement',71,'surface_settlement'),
('LandablePlanet',81,'planet_landable'),('Default',91,'default'),('Star',101,'star'),
('Asteroid',111,'asteroid'),('DockableMegaship',121,'megaShip_dockable'),
('UndockableMegaship',130,'megaShip_undockable'),('Installation',140,'installation')]
for name,frame,label in bookmarks:
copy('Bookmark'+name,root/'ui-svg/DefineSprite_304_BookmarkButtonIcon'/f'{frame}.svg','ui',304,frame,label)
for key,sprite,name in [('TabBookmarks',251,'TabIconBookmarks'),('SelectedDestination',289,'NavMarkerIconSelected'),('CurrentLocation',291,'NavMarkerIconCurrent')]:
copy(key,root/f'ui-svg/DefineSprite_{sprite}_{name}/1.svg','ui',sprite,1,name)
for key,frame,label in [('Focus',9,'focused'),('Selection',19,'selection'),('Location',29,'location'),('ConflictZones',39,'conflictZones'),('Taxi',49,'taxi'),
('FocusThin',58,'focusedThin'),('SelectionThin',68,'selectionThin'),('LocationThin',78,'locationThin'),('ConflictZonesThin',88,'conflictZonesThin'),('TaxiThin',98,'taxiThin')]:
copy('Marker'+key,root/'labels-final-png/DefineSprite_41_GalaxyMapLabels_fla.ItemMarker_Graphics_8'/f'{frame}.png','labels',41,frame,label,
'Original timeline stop frame. BC7 source atlas decoded losslessly to RGBA with Pillow, then rendered by JPEXS; no red missing-bitmap placeholder. These are label rings, not category pin icons.')
for item in json.loads((root/'map-662-frames.json').read_text()):
label=item['label'];frame=item['frame']
if label=='none':continue
source=root/'map-svg/DefineSprite_662_Icons'/f'{frame}.svg'
svg=ET.parse(source).getroot()
raster=any(x.tag.endswith('}image') or any('bitmap' in k.lower() for k in x.attrib) for x in svg.iter())
if raster:source=root/'map-final-png/DefineSprite_662_Icons'/f'{frame}.png'
key='Map_'+label
copy(key,source,'map',662,frame,label,'Current map UI Icons timeline; marker legend resolves its DS icon string directly to this frame. Original colours retained.')
current.append(dict(label=label,key=key,frame=frame,extension=source.suffix,sprite=662))
copy('Map_CurrentLocationMarker',root/'map-svg/DefineSprite_1617_assets.CurrentLocationMarker/1.svg','map',1617,1,'assets.CurrentLocationMarker')
current.append(dict(label='CurrentLocationMarker',key='Map_CurrentLocationMarker',frame=1,extension='.svg',sprite=1617))
manifest=dict(generator='research/Build-GalaxyIconAssets.py',extractor='research/Extract-GalaxyUiResources.py',
exporter='JPEXS Free Flash Decompiler 26.4.0',gameProcessAccess=False,
exportBytesUnchanged=True,sources=json.loads((root/'resources.json').read_text()),
rasterDecode=dict(source='labels.bin',atlas='GalaxyMapLabels_I5.dds',width=2048,height=256,
gpuOffset=47519,gpuBytes=524288,gpuSha256=hashlib.sha256((root/'labels.bin').read_bytes()[47519:]).hexdigest(),
rgbaSha256=hashlib.sha256(Image.open(root/'labels-atlas.png').convert('RGBA').tobytes()).hexdigest(),
note='Same BC7 decoder as Build-GameIconAssets.py. Private labels-render.gfx changes only the external texture filename to the lossless decoded atlas PNG to avoid the JPEXS DDS decoder limitation.'),
currentMapRasterDecode=json.loads((root/'map-atlas-evidence.json').read_text()),
currentMapBitmapDependencies={key:dependencies[key] for key in ('allDependenciesResolved','framesWithBitmap','uniqueSubImages','sourceAtlasImageIds')},
currentMapNotes='Current mapui has Colonisation/Powerplay/Modes classes. Older galaxymapui BookmarkButtonIcon is retained as legacy UI artwork only; it does not prove world marker selection.',
nativeBookmarkMapping=dict(functionRva='26020A0',decoderRva='261F820',field='row+4C iconName uint',
values={'2':'station_bernal','3':'station_coriolis','4':'station_orbis','5':'station_outpost','6':'surface_port',
'7':'surface_settlement','8':'planet_landable','9':'planet','10':'star','11':'asteroid','12':'system',
'13':'megaShip_dockable','14':'megaShip_undockable','17':'station_dodec'},default='default',
unavailableFrame='station_dodec has no frame in this original BookmarkButtonIcon timeline; explicit default fallback'),icons=icons)
(out/'source.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
# Generate a closed, ordinal name catalogue from the actual timeline labels.
entries=[]
for item in current:
entries.append(' ["'+item['label']+'"] = new("'+item['label']+'","Assets/GalaxyIcons/'+item['key']+item['extension']+'",'+str(item['sprite'])+','+str(item['frame'])+'),')
code=('// Generated by research/Build-GalaxyIconAssets.py from original mapui timeline labels.\n'
'using System.Collections.Frozen;\n\nnamespace XFEEDToolBox.Core.Galaxy;\n\n'
'public static partial class GalaxyOriginalIcons\n{\n'
' /// <summary>Current original map UI icon names; colours and geometry are retained.</summary>\n'
' public static IReadOnlyDictionary<string,GalaxyOriginalIcon> MapFrames { get; } =\n'
' new Dictionary<string,GalaxyOriginalIcon>(StringComparer.Ordinal)\n {\n'+ '\n'.join(entries)+
'\n }.ToFrozenDictionary(StringComparer.Ordinal);\n\n'
' /// <summary>Returns null for names absent from the original movie. No semantic guessing.</summary>\n'
' public static GalaxyOriginalIcon? Map(string frameName)=>MapFrames.GetValueOrDefault(frameName);\n}\n')
Path('XFEEDToolBox.Core/Galaxy/GalaxyOriginalIcons.Map.cs').write_text(code,encoding='utf-8')
print(json.dumps(dict(imported=len(icons),output=str(out),sourceFramesUnchanged=True)))