namespace XFEEliteDangerous.Core; /// Persisted explicit native state. The current private inventory supplies editable fields. public sealed record NativeCommanderSave { public required NativeSavegameSnapshot Snapshot { get; init; } public required NativeSavegameCatalogue Catalogue { get; init; } public required NativeFullSavegameProfile Profile { get; init; } public NativeSystemMetadata? SystemMetadata {get;init;} public NativeSavegameUploadState? UploadState {get;init;} public IReadOnlyList StoredModules {get;init;}=[]; private readonly IReadOnlyList materials=[]; private readonly IReadOnlyDictionary shipLabels=new Dictionary(); // The source-generated required-member constructor passes null for new // optional properties absent from older schema-1 files. public IReadOnlyList Materials {get=>materials;init=>materials=value??[];} public IReadOnlyDictionary ShipLabels {get=>shipLabels;init=>shipLabels=value??new Dictionary();} public CommanderState CreateInventory(AccountState account, long revision) { _ = NativeFullSavegameDocument.SerializeComplete(Snapshot, Catalogue, Profile); SystemMetadata?.Validate(Profile.CurrentSystem); NativeSavegameUpload.Validate(this); NativeStoredModule.Validate(StoredModules,Catalogue); NativeMaterials.Validate(Materials); if (Catalogue.PinnedVersion != "2026.09.03.332841") throw new ArgumentException("Native save targets an unsupported game build."); if ((Snapshot.Location.Address & 0x007F_FFFF_FFFF_FFFFUL) != Profile.CurrentSystem || (Snapshot.Vessels.Single(ship => ship.Id == Snapshot.CurrentVesselId).Location.Address & 0x007F_FFFF_FFFF_FFFFUL) != Profile.CurrentSystem) throw new ArgumentException("Current native vessel and commander must reference the declared current system."); var inventory=NativeCommanderProjection.CreateInventory(Snapshot, Catalogue, account.NativeUserId, account.Commander.Id, Profile.Name, revision); inventory.Materials=Materials.Where(row=>row.Quantity>0).Select(row=>new InventoryStack{Id=row.Id.ToString(System.Globalization.CultureInfo.InvariantCulture),Name=row.Name,Category=row.Type,Quantity=row.Quantity}).ToList(); if(ShipLabels is null||ShipLabels.Any(pair=>!Snapshot.Vessels.Any(ship=>ship.Id==pair.Key)||pair.Value is null ||string.IsNullOrWhiteSpace(pair.Value.Name)||pair.Value.Ident is null||pair.Value.Name.Length>100||pair.Value.Ident.Length>24||pair.Value.Name.Any(char.IsControl)||pair.Value.Ident.Any(char.IsControl))) throw new ArgumentException("Invalid private ship labels."); foreach(var ship in inventory.Ships) if(ShipLabels.TryGetValue(uint.Parse(ship.Id,System.Globalization.CultureInfo.InvariantCulture),out var label)){ship.Name=label.Name;ship.Ident=label.Ident;} return inventory; } public string SerializeCurrent(AccountState account) { var current = ExportCurrent(account); return NativeMaterials.Compose(NativeSavegameUpload.Compose(NativeFullSavegameDocument.SerializeComplete(current.Snapshot, current.Catalogue, current.Profile),current),current.Materials); } public NativeCommanderSave ExportCurrent(AccountState account) => this with { Snapshot = NativeCommanderProjection.ApplyInventory(Snapshot, Catalogue, account.NativeUserId, account.Commander,Materials.Count>0), Materials = NativeMaterials.Apply(Materials,account.Commander), Profile = Profile with { Name = account.Commander.Name } }; public void ValidateCurrent(AccountState account) { _ = CreateInventory(account, account.Commander.Revision); _ = SerializeCurrent(account); } } public sealed record NativeShipLabel(string Name,string Ident);