Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
 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());
             }
         }
     }));
 }
Exemplo n.º 3
0
        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;
                //}
            }
        }
Exemplo n.º 4
0
 public static IReadOnlyCollection <ModKey> Oblivion(this ImplicitBaseMasters _)
 {
     return(Implicits.Get(GameRelease.Oblivion).BaseMasters);
 }
Exemplo n.º 5
0
 public static IReadOnlyCollection <FormKey> Oblivion(this ImplicitRecordFormKeys _)
 {
     return(Implicits.Get(GameRelease.Oblivion).RecordFormKeys);
 }
Exemplo n.º 6
0
 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.
        }
Exemplo n.º 8
0
 public static IReadOnlyCollection <ModKey> Fallout4(this ImplicitBaseMasters _)
 {
     return(Implicits.Get(GameRelease.Fallout4).BaseMasters);
 }
Exemplo n.º 9
0
 public static IReadOnlyCollection <FormKey> Fallout4(this ImplicitRecordFormKeys _)
 {
     return(Implicits.Get(GameRelease.Fallout4).RecordFormKeys);
 }
Exemplo n.º 10
0
 public static IReadOnlyCollection <ModKey> Fallout4(this ImplicitListings _)
 {
     return(Implicits.Get(GameRelease.Fallout4).Listings);
 }
Exemplo n.º 11
0
 public static IReadOnlyCollection <ModKey> Skyrim(
     this ImplicitBaseMasters _,
     SkyrimRelease release)
 {
     return(Implicits.Get(release.ToGameRelease()).BaseMasters);
 }
Exemplo n.º 12
0
 public static IReadOnlyCollection <FormKey> Skyrim(
     this ImplicitRecordFormKeys _,
     SkyrimRelease release)
 {
     return(Implicits.Get(release.ToGameRelease()).RecordFormKeys);
 }
Exemplo n.º 13
0
 public static IReadOnlyCollection <ModKey> Skyrim(
     this ImplicitListings _,
     SkyrimRelease release)
 {
     return(Implicits.Get(release.ToGameRelease()).Listings);
 }
Exemplo n.º 14
0
        /// <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,
            });
        }