/// <summary> /// Aligns a mod's ModKey to a path's implied ModKey. /// Will adjust its logic based on the MasterFlagSync option: /// - ThrowIfMisaligned: If the path and mod do not match, throw. /// - CorrectToPath: If the path and mod do not match, use path's key. /// </summary> /// <param name="mod">Mod to check and adjust</param> /// <param name="path">Path to compare to</param> /// <returns>ModKey to use</returns> /// <exception cref="ArgumentException">If misaligned and set to ThrowIfMisaligned</exception> public ModKey RunMasterMatch(IModGetter mod, string path) { if (ModKeySync == ModKeySyncOption.NoCheck) { return(mod.ModKey); } if (!ModKey.TryFactory(Path.GetFileName(path), out var pathModKey)) { throw new ArgumentException($"Could not convert path to a ModKey to compare against: {Path.GetFileName(path)}"); } switch (ModKeySync) { case ModKeySyncOption.ThrowIfMisaligned: if (mod.ModKey != pathModKey) { throw new ArgumentException($"ModKeys were misaligned: {mod.ModKey} != {pathModKey}"); } return(mod.ModKey); case ModKeySyncOption.CorrectToPath: return(pathModKey); default: throw new NotImplementedException(); } }
/// <summary> /// Asserts a given mod is in the collection of keys. /// </summary> /// <param name="keys">Listings to look through</param> /// <param name="modKey">ModKey to look for</param> /// <param name="message">Message to attach to exception if mod doesn't exist</param> /// <exception cref="MissingModException"> /// Thrown if given mod is not on the collection of listings /// </exception> public static void AssertHasMod(this IEnumerable <ModKey> keys, ModKey modKey, string?message = null) { if (!HasMod(keys, modKey)) { throw new MissingModException(keys, message: message); } }
/// <summary> /// Parses a stream to retrieve all ModKeys in expected plugin file format /// </summary> /// <param name="stream">Stream to read from</param> /// <returns>List of modkeys representing a load order</returns> /// <exception cref="ArgumentException">Line in plugin stream is unexpected</exception> public static IExtendedList <ModKey> ProcessLoadOrder(Stream stream) { var ret = new ExtendedList <ModKey>(); using var streamReader = new StreamReader(stream); while (!streamReader.EndOfStream) { var str = streamReader.ReadLine(); var commentIndex = str.IndexOf('#'); if (commentIndex != -1) { str = str.Substring(0, commentIndex); } if (string.IsNullOrWhiteSpace(str)) { continue; } str = str.Trim(); if (!ModKey.TryFactory(str, out var key)) { throw new ArgumentException($"Load order file had malformed line: {str}"); } ret.Add(key); } return(ret); }
/// <summary> /// Asserts a given mod is in the collection of keys. /// </summary> /// <param name="loadOrder">LoadOrder to query</param> /// <param name="modKey">ModKey to look for</param> /// <param name="message">Message to attach to exception if mod doesn't exist</param> /// <exception cref="MissingModException"> /// Thrown if given mod is not on the collection of listings /// </exception> public static void AssertHasMod(this ILoadOrderGetter loadOrder, ModKey modKey, string?message = null) { if (!HasMod(loadOrder, modKey)) { throw new MissingModException(loadOrder.ListedOrder, message: message); } }
public static string GetFileName( StringsLanguageFormat languageFormat, ModKey modKey, Language language, StringsSource source) { return($"{modKey.Name}_{GetLanguageString(languageFormat, language)}.{GetSourceString(source)}"); }
public static RecordException Create(string message, ModKey modKey, Exception?innerException = null) { return(new RecordException( formKey: null, modKey: modKey, edid: null, recordType: null, message: message, innerException: innerException)); }
public static IEnumerable <LoadOrderListing> ListingsFromPath( FilePath cccFilePath, DirectoryPath dataPath) { return(File.ReadAllLines(cccFilePath.Path) .Select(x => ModKey.FromNameAndExtension(x)) .Where(x => File.Exists(Path.Combine(dataPath.Path, x.FileName))) .Select(x => new LoadOrderListing(x, enabled: true)) .ToList()); }
public static IObservable <IChangeSet <LoadOrderListing> > GetLiveLoadOrder( FilePath cccFilePath, DirectoryPath dataFolderPath, out IObservable <ErrorResponse> state, bool orderListings = true) { var raw = ObservableExt.WatchFile(cccFilePath.Path) .StartWith(Unit.Default) .Select(_ => { try { return(GetResponse <IObservable <IChangeSet <ModKey> > > .Succeed( File.ReadAllLines(cccFilePath.Path) .Select(x => ModKey.FromNameAndExtension(x)) .AsObservableChangeSet())); } catch (Exception ex) { return(GetResponse <IObservable <IChangeSet <ModKey> > > .Fail(ex)); } }) .Replay(1) .RefCount(); state = raw .Select(r => (ErrorResponse)r); var ret = ObservableListEx.And( raw .Select(r => { return(r.Value ?? Observable.Empty <IChangeSet <ModKey> >()); }) .Switch(), ObservableExt.WatchFolderContents(dataFolderPath.Path) .Transform(x => { if (ModKey.TryFromNameAndExtension(Path.GetFileName(x), out var modKey)) { return(TryGet <ModKey> .Succeed(modKey)); } return(TryGet <ModKey> .Failure); }) .Filter(x => x.Succeeded) .Transform(x => x.Value) .RemoveKey()) .Transform(x => new LoadOrderListing(x, true)); if (orderListings) { ret = ret.OrderListings(); } return(ret); }
/// <summary> /// Checks whether a given mod is in the collection of listings. /// </summary> /// <param name="listings">Listings to look through</param> /// <param name="modKey">ModKey to look for</param> /// <param name="enabled">Whether the ModKey should be enabled/disabled. Default is no preference</param> /// <returns>True if ModKey is in the listings, with the desired enabled state</returns> public static bool HasMod(this IEnumerable <LoadOrderListing> listings, ModKey modKey, bool?enabled = null) { foreach (var listing in listings) { if (listing.ModKey == modKey && (enabled == null || listing.Enabled == enabled)) { return(true); } } return(false); }
public static RecordException Factory(Exception ex, ModKey modKey) { if (ex is RecordException rec) { rec.ModKey = modKey; return(rec); } return(new RecordException( formKey: null, modKey: modKey, edid: null, innerException: ex)); }
/// <summary> /// Duplicates records into a given mod 'modToDuplicateInto', which originated from target ModKey 'modKeyToDuplicateFrom'.<br /> /// Only considers records that are currently within the target modToDuplicateInto, which are then duplicated. <br/> /// Records from the modKeyToDuplicateFrom that are not within or referenced by records in the target mod are skipped.<br /> /// <br /> /// End result will be that all records that the given modToDuplicateInto contains or references that originate from the target modKeyToDuplicateFrom will be duplicated in /// and replace the records they duplicated. No references to the modKeyToDuplicateFrom will remain. /// </summary> /// <typeparam name="TMod"></typeparam> /// <typeparam name="TModGetter"></typeparam> /// <param name="modToDuplicateInto">Mod to duplicate into and originate new records from</param> /// <param name="linkCache">LinkCache for lookup</param> /// <param name="modKeyToDuplicateFrom">ModKey to search modToDuplicateInto for, and duplicate records that originate from modKeyToDuplicateFrom</param> /// <param name="typesToInspect"> /// Types to iterate and look at within modToDuplicateInto for references to modKeyToDuplicateFrom<br /> /// Only use if you know specifically the types that can reference modKeyToDuplicateFrom, and want a little bit of speed /// by not checking uninteresting records. /// </param> public static void DuplicateFromOnlyReferenced <TMod, TModGetter>( this TMod modToDuplicateInto, ILinkCache <TMod, TModGetter> linkCache, ModKey modKeyToDuplicateFrom, params Type[] typesToInspect) where TModGetter : class, IModGetter where TMod : class, TModGetter, IMod { DuplicateFromOnlyReferenced( modToDuplicateInto, linkCache, modKeyToDuplicateFrom, out _, typesToInspect); }
public static void TypicalPatch <TMod>( string[] mainArgs, ModKey outputMod, IReadOnlyList <ModKey> loadOrderList, LoadOrder <TMod> .Importer importer, Func <ModKey, LoadOrder <TMod>, TMod> processor) where TMod : class, IMod { TypicalPatch( dataFolder: new Noggog.DirectoryPath(mainArgs[0]), outModKey: outputMod, loadOrderList: loadOrderList, processor: processor, importer: importer); }
public static RecordException Enrich(Exception ex, ModKey modKey) { if (ex is RecordException rec) { if (rec.ModKey == null) { rec.ModKey = modKey; } return(rec); } return(new RecordException( formKey: null, modKey: modKey, edid: null, recordType: null, innerException: ex)); }
public static void TypicalPatch <TMod>( DirectoryPath dataFolder, ModKey outModKey, IReadOnlyList <ModKey> loadOrderList, LoadOrder <TMod> .Importer importer, Func <ModKey, LoadOrder <TMod>, TMod> processor) where TMod : class, IMod { var loadOrderInternal = loadOrderList.ToList(); loadOrderInternal.Remove(outModKey); var loadOrder = new LoadOrder <TMod>(); loadOrder.Import( dataFolder, loadOrderInternal, importer); var outMod = processor(outModKey, loadOrder); foreach (var npc in outMod.EnumerateMajorRecords()) { npc.IsCompressed = false; } var linkCache = loadOrder.CreateLinkCache(); outMod.MasterReferences.SetTo( outMod.LinkFormKeys .Where(fk => !fk.IsNull) .Select(s => s.ModKey) .Where(modKey => modKey != outModKey) .Distinct() .OrderBy(modKey => loadOrder.IndexOf(modKey)) .Select(modKey => new MasterReference() { Master = modKey })); outMod.SyncRecordCount(); outMod.WriteToBinary( path: Path.Combine(dataFolder.Path, outModKey.FileName)); }
/// <summary> /// Enumerates all applicable BSAs for a given release and ModKey that are within a given dataFolderPath. /// Orders the results to a BSA load order driven by the ini /// </summary> /// <param name="release">GameRelease to query for</param> /// <param name="dataFolderPath">Folder to query within</param> /// <param name="modKey">ModKey to query about</param> /// <param name="bsaOrdering">How to order the bsa archive paths. Null for no ordering</param> /// <returns></returns> public static IEnumerable <string> GetApplicableArchivePaths(GameRelease release, string dataFolderPath, ModKey modKey, IComparer <string>?bsaOrdering) { var ret = Directory.EnumerateFiles(dataFolderPath, $"*{GetArchiveExtension(release)}"); if (bsaOrdering != null) { var ret2 = ret.OrderBy(x => x, bsaOrdering); return(ret2); } return(ret); }
/// <summary> /// Enumerates all applicable BSAs for a given release and ModKey that are within a given dataFolderPath. /// </summary> /// <param name="release">GameRelease to query for</param> /// <param name="dataFolderPath">Folder to query within</param> /// <param name="modKey">ModKey to query about</param> /// <param name="bsaOrdering">BSA ordering overload. Empty enumerable means no ordering.</param> /// <returns></returns> public static IEnumerable <string> GetApplicableArchivePaths(GameRelease release, string dataFolderPath, ModKey modKey, IEnumerable <string> bsaOrdering) { return(GetApplicableArchivePaths(release, dataFolderPath, modKey, GetPriorityOrderComparer(release, bsaOrdering))); }
/// <summary> /// Constructor taking a ModKey and ID as separate parameters /// </summary> /// <param name="modKey">ModKey to use</param> /// <param name="id">Record ID to use. Must be less than 0x00FFFFFF.</param> /// <exception cref="ArgumentException">ID needs to contain no data in upper two bytes, or it will throw.</exception> public FormKey(ModKey modKey, uint id) { this.ModKey = modKey; this.ID = id & 0xFFFFFF; }
public ModPath(ModKey modKey, string path) { ModKey = modKey; Path = path; }
/// <summary> /// Constructor /// </summary> /// <param name="modKey">Key to assign the mod</param> public AMod(ModKey modKey) { this.ModKey = modKey; this._allocator = new SimpleFormKeyAllocator(this); }
public ModContext(ModKey modKey, IModContext?parent, T record) { ModKey = modKey; Parent = parent; Record = record; }
public static RecordException Factory(string message, ModKey modKey, Exception?innerException = null) { return(Create(message, modKey, innerException)); }
/// <summary> /// Constructor /// </summary> /// <param name="modKey">Key to assign the mod</param> /// <param name="allocator">Optional custom FormKey allocator logic</param> public AMod(ModKey modKey, IFormKeyAllocator?allocator = null) { this.ModKey = modKey; this._allocator = allocator ?? new SimpleFormKeyAllocator(this); }
public static TMod GetIfEnabledAndExists <TMod>(this ILoadOrderGetter <IModListingGetter <TMod> > loadOrder, ModKey modKey) where TMod : class, IModGetter { if (TryGetIfEnabledAndExists <TMod>(loadOrder, modKey, out var mod)) { return(mod); } throw new MissingModException(modKey); }
public static StringsFolderLookupOverlay?TypicalFactory(string referenceModPath, StringsReadParameters?instructions, ModKey modKey) { var ret = new StringsFolderLookupOverlay(); var stringsFolderPath = instructions?.StringsFolderOverride; var dir = Path.GetDirectoryName(referenceModPath); if (stringsFolderPath == null) { stringsFolderPath = Path.Combine(dir, "Strings"); } if (stringsFolderPath.Value.Exists) { foreach (var file in stringsFolderPath.Value.Info.EnumerateFiles($"{modKey.Name}*{StringsUtility.StringsFileExtension}")) { if (!StringsUtility.TryRetrieveInfoFromString(file.Name, out var type, out var lang, out _)) { continue; } var dict = ret.Get(type); dict[lang] = new Lazy <IStringsLookup>(() => new StringsLookupOverlay(file.FullName, type), LazyThreadSafetyMode.ExecutionAndPublication); } } foreach (var bsaFile in Directory.EnumerateFiles(dir, "*.bsa")) { var bsaReader = BSAReader.Load(new AbsolutePath(bsaFile, skipValidation: true)); foreach (var item in bsaReader.Files) { if (!StringsUtility.TryRetrieveInfoFromString(Path.GetFileName(item.Path.ToString()), out var type, out var lang, out var modName)) { continue; } if (!MemoryExtensions.Equals(modKey.Name, modName, StringComparison.OrdinalIgnoreCase)) { continue; } var dict = ret.Get(type); if (dict.ContainsKey(lang)) { continue; } dict[lang] = new Lazy <IStringsLookup>(() => { byte[] bytes = new byte[item.Size]; using var stream = new MemoryStream(bytes); item.CopyDataTo(stream).AsTask().Wait(); return(new StringsLookupOverlay(bytes, type)); }, LazyThreadSafetyMode.ExecutionAndPublication); } } return(ret); }
public static RecordException Factory(Exception ex, ModKey modKey) { return(Enrich(ex, modKey)); }
public StringsWriter(ModKey modKey, DirectoryPath writeDirectory) { _modKey = modKey; WriteDir = writeDirectory; }
public MissingModException(ModKey key) { ModPath = new ModPath(key, string.Empty); }
public static bool TryGetIfEnabledAndExists <TMod>(this ILoadOrderGetter <IModListingGetter <TMod> > loadOrder, ModKey modKey, [MaybeNullWhen(false)] out TMod item) where TMod : class, IModGetter { if (!TryGetIfEnabled(loadOrder, modKey, out var listing)) { item = default; return(false); } item = listing.Mod; return(item != null); }
public MissingModException(ModKey key, string message) : base(message) { ModPath = new ModPath(key, string.Empty); }
public StringsWriter(GameRelease release, ModKey modKey, DirectoryPath writeDirectory) { _modKey = modKey; _languageFormat = release.GetLanguageFormat(); WriteDir = writeDirectory; }