public static IEnumerable <LoadOrderListing> GetLoadOrder( GameRelease release, string loadOrderFilePath, string dataFolderPath, PatcherPreferences?userPrefs = null) { // This call will impliticly get Creation Club entries, too, as the Synthesis systems should be merging // things into a singular load order file for consumption here var loadOrderListing = Implicits.Get(release).Listings .Where(x => File.Exists(Path.Combine(dataFolderPath, x.FileName))) .Select(x => new LoadOrderListing(x, enabled: true)); if (!loadOrderFilePath.IsNullOrWhitespace()) { loadOrderListing = loadOrderListing.Concat(PluginListings.RawListingsFromPath(loadOrderFilePath, release)); } loadOrderListing = loadOrderListing.Distinct(x => x.ModKey); if (userPrefs?.InclusionMods != null) { var inclusions = userPrefs.InclusionMods.ToHashSet(); loadOrderListing = loadOrderListing .Where(m => inclusions.Contains(m.ModKey)); } if (userPrefs?.ExclusionMods != null) { var exclusions = userPrefs.ExclusionMods.ToHashSet(); loadOrderListing = loadOrderListing .Where(m => !exclusions.Contains(m.ModKey)); } return(loadOrderListing); }
public Test TestPex() { return(TestBattery.RunTest("Pex", GameRelease, Target, async(output) => { IEnumerable <FileName> bsas; if (Implicits.Get(GameRelease).BaseMasters.Contains(FilePath.ModKey)) { bsas = Archive.GetIniListings(GameRelease).ToList(); } else { bsas = new FileName($"{FilePath.ModKey.Name}.{Archive.GetExtension(GameRelease)}").AsEnumerable(); } foreach (var bsa in bsas) { var archive = Archive.CreateReader(GameRelease, Path.Combine(Path.GetDirectoryName(FilePath) !, bsa.String)); foreach (var file in archive.Files) { if (!Path.GetExtension(file.Path).Equals(".pex", StringComparison.OrdinalIgnoreCase)) { continue; } TestPex(GameRelease, file.GetMemorySlice()); } } })); }
public static void RunPatch(IPatcherState <ISkyrimMod, ISkyrimModGetter> state) { if (!state.LoadOrder.TryGetValue(BDSModKey, out IModListing <ISkyrimModGetter>?bdsMod) || bdsMod.Mod == null) { throw new ArgumentException("Unable to get Better Dynamic Snow.esp plugin"); } var materialMapping = MaterialMapping(bdsMod.Mod); var skipMods = Implicits.Get(state.PatchMod.GameRelease).Listings.ToHashSet(); skipMods.Add(USSEPModKey); Console.WriteLine("{0} STAT", state.LoadOrder.PriorityOrder.WinningOverrides <IStaticGetter>().Count <IStaticGetter>()); // skip STATs where winning override is from excluded mods foreach (var target in state.LoadOrder.PriorityOrder.WinningOverrides <IStaticGetter>(). Where(stat => !skipMods.Contains(stat.FormKey.ModKey))) { if (!materialMapping.TryGetValue(target.Material, out IMaterialObjectGetter? mapped) || mapped == null) { continue; } var matName = target.Material; Console.WriteLine("MATO {0:X8} mapped to BDS {1:X8} in STAT {2}:{3}/{4:X8} with flags {5}", matName.FormKey.ID, mapped.FormKey.ID, target.FormKey.ModKey.FileName, target.EditorID, target.FormKey.ID, target.Flags); var newStatic = state.PatchMod.Statics.GetOrAddAsOverride(target); newStatic.Material = new FormLink <IMaterialObjectGetter>(mapped.FormKey); //// TODO fix up SLAWF special case, data loss somehow ///// https://www.nexusmods.com/skyrimspecialedition/mods/26138 //if (newStatic.EditorID == "RockCliff08_HeavySN_lawf") //{ // newStatic.Flags = Static.Flag.ConsideredSnow; //} //else //{ // newStatic.Flags = target.Flags; //} } }
public static IReadOnlyCollection <ModKey> Oblivion(this ImplicitBaseMasters _) { return(Implicits.Get(GameRelease.Oblivion).BaseMasters); }
public static IReadOnlyCollection <FormKey> Oblivion(this ImplicitRecordFormKeys _) { return(Implicits.Get(GameRelease.Oblivion).RecordFormKeys); }
public static IReadOnlyCollection <ModKey> Oblivion(this ImplicitListings _) { return(Implicits.Get(GameRelease.Oblivion).Listings); }
private static void RunPatch(IPatcherState <ISkyrimMod, ISkyrimModGetter> state) { // 0. Initialize method contextual variables and configurations. ILinkCache cache = state.LinkCache; string configFilePath = Path.Combine(state.ExtraSettingsDataPath, "currency-replacer-config.json"); string errorMessage = ""; if (!File.Exists(configFilePath)) { errorMessage = "Cannot find currency-replacer-config.json for Currency Replacer Rules."; SynthesisLog(errorMessage); throw new FileNotFoundException(errorMessage, configFilePath); } SynthesisLog("**********************************", true); SynthesisLog("Currency Configuration File Used:"); SynthesisLog(File.ReadAllText(configFilePath)); SynthesisLog("**********************************"); CurrencyConfig config; try { config = JsonConvert.DeserializeObject <CurrencyConfig>(File.ReadAllText(configFilePath)); } catch (JsonSerializationException jsonException) { errorMessage = "Failed to parse currency-replacer-config.json, please review expected format."; SynthesisLog(errorMessage, true); throw new JsonSerializationException(errorMessage, jsonException); } // 1. Detect presence of Coins of Tamriel V2 Replacer plugin. if (!state.LoadOrder.TryGetValue(CoinsOfTamrielV2.ModKey, out var coinsOfTamrielContainer) || coinsOfTamrielContainer.Mod is null) { throw new MissingModException(CoinsOfTamrielV2.ModKey, $"Mod {CoinsOfTamrielV2.ModKey.Name} was not found in Load Order."); } var coinsIndex = state.LoadOrder.IndexOf(CoinsOfTamrielV2.ModKey); // 2. Patching ALL Containers for Currency Related information dynamically. var coinsOfTamrielMod = coinsOfTamrielContainer.Mod; var nativeContainers = coinsOfTamrielMod.Containers; // 2.a. Find Winning Containers we are allowed to clone, change and apply our algorithm to. var containersToPatch = state.LoadOrder .PriorityOrder .Container() .WinningContextOverrides() .Where(context => { // Detection Triggers ... return(!nativeContainers.Contains(context.Record) && (context.Record.Items?.Any(container => container.Item.Item.IsOneOf( LootGoldChange, LootPerkGoldenTouch, LootPerkGoldenTouchChange, LootImperialLuck, LootFalmerGoldBoss, Gold)) ?? false) && context.Record.Matches(MatchType.Nordic, config)); }); // TESTING SECTION // containersToPatch.ForEach(ctx => SynthesisLog( $"Container {ctx.Record.FormKey}: {ctx.Record.EditorID} - {ctx.Record.Name} from {ctx.ModKey.FileName} eligible.")); return; // TESTING SECTION // // 2.b. Patch up Native containers originating within Coins of Tamriel plugin that are left over. // NOTE: Will only patch against the most recent override, i.e. The load order provided must be already // conflict resolved for everything UPTO BUT NOT INCLUSIVE OF Coins of Tamriel V2 itself. #region Test 1 // Test 1 : Overriding containers proof of concept works as expected. var counter = 0; foreach (var container in nativeContainers) { //state.LoadOrder.ListedOrder.Take(coinsOfTamrielContainer.index) // .Do(x => Console.WriteLine(x.ModKey)); var containerContext = state.LoadOrder.ListedOrder.Take(coinsIndex) .Reverse() .Container() .WinningContextOverrides() .FirstOrDefault(recentContainer => container.FormKey == recentContainer.Record.FormKey); if (containerContext is null) { continue; } var closestWinningContainer = containerContext.Record; SynthesisLog($"{containerContext.ModKey} for {containerContext.Record.Name}"); //var cond2 = !Equals(closestWinningContainer, container); //var cond3 = !closestWinningContainer?.FormKey.ModKey.IsSkyrimBaseMod(); //SynthesisLog($"{cond1} {cond2} {cond3}"); if (!Equals(closestWinningContainer, container) && !Implicits.Get(state.PatchMod.GameRelease).BaseMasters.Contains(containerContext.ModKey)) { SynthesisLog("reached B"); var adjustedContainer = state.PatchMod.Containers.GetOrAddAsOverride(closestWinningContainer); //var goldenTouchChange = state.LinkCache.Lookup<ILeveledItemGetter>(Skyrim.LeveledItem.LootPerkGoldenTouchChange); var itemsToReplace = adjustedContainer.Items?.FindAll(i => i.Item.Item.Equals(LootPerkGoldenTouchChange)); ContainerEntry goldenTouchChangeNordic = new ContainerEntry { Item = new ContainerItem() { Count = 1, Item = CoinsOfTamrielV2.LeveledItem.LootPerkGoldenTouchChangeNordic } }; itemsToReplace?.ForEach(goldenTouchChange => adjustedContainer.Items.Replace <ContainerEntry>(goldenTouchChange, goldenTouchChangeNordic)); counter++; } } SynthesisLog($"Performed {counter} replacements in Containers", true); #endregion // 3. Patched Leveled List items based on predicate dynamically. // 4. Patch REFRs with randomized generated variations of coins/currencies across worldspaces and cells. // P.S: Activator, Flora, NAVI, Quest are either used in the above or are unrelated and don't need patching. }
public static IReadOnlyCollection <ModKey> Fallout4(this ImplicitBaseMasters _) { return(Implicits.Get(GameRelease.Fallout4).BaseMasters); }
public static IReadOnlyCollection <FormKey> Fallout4(this ImplicitRecordFormKeys _) { return(Implicits.Get(GameRelease.Fallout4).RecordFormKeys); }
public static IReadOnlyCollection <ModKey> Fallout4(this ImplicitListings _) { return(Implicits.Get(GameRelease.Fallout4).Listings); }
public static IReadOnlyCollection <ModKey> Skyrim( this ImplicitBaseMasters _, SkyrimRelease release) { return(Implicits.Get(release.ToGameRelease()).BaseMasters); }
public static IReadOnlyCollection <FormKey> Skyrim( this ImplicitRecordFormKeys _, SkyrimRelease release) { return(Implicits.Get(release.ToGameRelease()).RecordFormKeys); }
public static IReadOnlyCollection <ModKey> Skyrim( this ImplicitListings _, SkyrimRelease release) { return(Implicits.Get(release.ToGameRelease()).Listings); }
/// <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="mapping">Out parameter to store the resulting duplication mappings that were made</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, out Dictionary <FormKey, FormKey> mapping, params Type[] typesToInspect) where TModGetter : class, IModGetter where TMod : class, TModGetter, IMod { if (modKeyToDuplicateFrom == modToDuplicateInto.ModKey) { throw new ArgumentException("Cannot pass the target mod's Key as the one to extract and self contain"); } // Compile list of things to duplicate HashSet <IFormLinkGetter> identifiedLinks = new(); HashSet <FormKey> passedLinks = new(); var implicits = Implicits.Get(modToDuplicateInto.GameRelease); void AddAllLinks(IFormLinkGetter link) { if (link.FormKey.IsNull) { return; } if (!passedLinks.Add(link.FormKey)) { return; } if (implicits.RecordFormKeys.Contains(link.FormKey)) { return; } if (link.FormKey.ModKey == modKeyToDuplicateFrom) { identifiedLinks.Add(link); } if (!linkCache.TryResolve(link.FormKey, link.Type, out var linkRec)) { return; } foreach (var containedLink in linkRec.ContainedFormLinks) { if (containedLink.FormKey.ModKey != modKeyToDuplicateFrom) { continue; } AddAllLinks(containedLink); } } var enumer = typesToInspect == null || typesToInspect.Length == 0 ? modToDuplicateInto.EnumerateMajorRecords() : typesToInspect.SelectMany(x => modToDuplicateInto.EnumerateMajorRecords(x)); foreach (var rec in enumer) { AddAllLinks(new FormLinkInformation(rec.FormKey, rec.Registration.GetterType)); } // Duplicate in the records mapping = new(); foreach (var identifiedRec in identifiedLinks) { if (!linkCache.TryResolveContext(identifiedRec.FormKey, identifiedRec.Type, out var rec)) { throw new KeyNotFoundException($"Could not locate record to make self contained: {identifiedRec}"); } var dup = rec.DuplicateIntoAsNewRecord(modToDuplicateInto, rec.Record.EditorID); mapping[rec.Record.FormKey] = dup.FormKey; // ToDo // Move this out of loop, and remove off a new IEnumerable<IFormLinkGetter> call modToDuplicateInto.Remove(identifiedRec.FormKey, identifiedRec.Type); } // Remap links modToDuplicateInto.RemapLinks(mapping); }
public void LiveLoadOrder( [Frozen] IScheduler scheduler, [Frozen] MockFileSystemWatcher watcher, [Frozen] MockFileSystem fs, [Frozen] ILiveLoadOrderTimings timings, [Frozen] IPluginListingsPathProvider pluginPath, [Frozen] IDataDirectoryProvider dataDir, [Frozen] ICreationClubListingsPathProvider cccPath) { var implicitKey = Implicits.Get(GameRelease.SkyrimSE).Listings.First(); var lightMasterPath = Path.Combine(dataDir.Path, TestConstants.LightMasterModKey.FileName); var lightMaster2Path = Path.Combine(dataDir.Path, TestConstants.LightMasterModKey2.FileName); var master2Path = Path.Combine(dataDir.Path, TestConstants.MasterModKey2.FileName); fs.File.WriteAllText(lightMasterPath, string.Empty); fs.File.WriteAllText(Path.Combine(dataDir.Path, TestConstants.Skyrim.FileName), string.Empty); fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.MasterModKey2}", $"*{TestConstants.PluginModKey}", }); fs.File.WriteAllLines(cccPath.Path, new string[] { TestConstants.LightMasterModKey.ToString(), TestConstants.LightMasterModKey2.ToString(), }); var live = LoadOrder.GetLiveLoadOrder( GameRelease.SkyrimSE, pluginPath.Path, dataDir.Path, out var state, scheduler: scheduler, cccLoadOrderFilePath: cccPath.Path, timings: timings, fileSystem: fs); state.Subscribe(x => { if (x.Failed) { throw x.Exception ?? new Exception(); } }); var list = live.AsObservableList(); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.LightMasterModKey, TestConstants.MasterModKey, TestConstants.MasterModKey2, TestConstants.PluginModKey, }); fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.PluginModKey}", }); watcher.MarkChanged(pluginPath.Path); fs.File.WriteAllText(lightMaster2Path, string.Empty); watcher.MarkCreated(lightMaster2Path); fs.File.Delete(lightMasterPath); watcher.MarkDeleted(lightMasterPath); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.LightMasterModKey2, TestConstants.MasterModKey, TestConstants.PluginModKey, }); fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.MasterModKey2}", }); watcher.MarkChanged(pluginPath.Path); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.LightMasterModKey2, TestConstants.MasterModKey, TestConstants.MasterModKey2, }); fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.MasterModKey2}", $"*{TestConstants.PluginModKey}", }); watcher.MarkChanged(pluginPath.Path); fs.File.Delete(lightMaster2Path); watcher.MarkDeleted(lightMaster2Path); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.MasterModKey, TestConstants.MasterModKey2, TestConstants.PluginModKey, }); // Does not respect just data folder modification // Since ModListing doesn't specify whether data folder is present // Data folder is just used for Timestamp alignment for Oblivion fs.File.Delete(master2Path); watcher.MarkDeleted(master2Path); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.MasterModKey, TestConstants.MasterModKey2, TestConstants.PluginModKey, }); }
public void LiveLoadOrder_EnsureReaddRetainsOrder( [Frozen] IScheduler scheduler, [Frozen] MockFileSystemWatcher watcher, [Frozen] MockFileSystem fs, [Frozen] ILiveLoadOrderTimings timings, [Frozen] IPluginListingsPathProvider pluginPath, [Frozen] IDataDirectoryProvider dataDir, [Frozen] ICreationClubListingsPathProvider cccPath) { var implicitKey = Implicits.Get(GameRelease.SkyrimSE).Listings.First(); fs.File.WriteAllText(Path.Combine(dataDir.Path, TestConstants.LightMasterModKey.FileName), string.Empty); fs.File.WriteAllText(Path.Combine(dataDir.Path, implicitKey.FileName), string.Empty); fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.MasterModKey2}", $"*{TestConstants.MasterModKey3}", $"*{TestConstants.PluginModKey}", }); fs.File.WriteAllLines(cccPath.Path, new string[] { TestConstants.LightMasterModKey.ToString(), TestConstants.LightMasterModKey2.ToString(), }); var live = LoadOrder.GetLiveLoadOrder( GameRelease.SkyrimSE, pluginPath.Path, dataDir.Path, out var state, cccLoadOrderFilePath: cccPath.Path, scheduler: scheduler, fileSystem: fs, timings: timings); var list = live.AsObservableList(); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.LightMasterModKey, TestConstants.MasterModKey, TestConstants.MasterModKey2, TestConstants.MasterModKey3, TestConstants.PluginModKey, }); // Remove fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.MasterModKey3}", $"*{TestConstants.PluginModKey}", }); watcher.MarkChanged(pluginPath.Path); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.LightMasterModKey, TestConstants.MasterModKey, TestConstants.MasterModKey3, TestConstants.PluginModKey, }); // Then readd fs.File.WriteAllLines(pluginPath.Path, new string[] { $"*{TestConstants.MasterModKey}", $"*{TestConstants.MasterModKey2}", $"*{TestConstants.MasterModKey3}", $"*{TestConstants.PluginModKey}", }); watcher.MarkChanged(pluginPath.Path); list.Items.Select(x => x.ModKey).Should().Equal(new ModKey[] { implicitKey, TestConstants.LightMasterModKey, TestConstants.MasterModKey, TestConstants.MasterModKey2, TestConstants.MasterModKey3, TestConstants.PluginModKey, }); }