Пример #1
0
        public override List <AssetOp> GetInstallOps(ModContext context)
        {
            if (InstallAction == null)
            {
                throw new InvalidOperationException("Tried to install AssetsModComponent, but install action is null.");
            }
            if (InstallAction.Actions == null || InstallAction.Actions.Count < 1)
            {
                throw new InvalidOperationException("Install action has no asset actions defined!");
            }

            using (new LogTiming("preloading asset files for assets mod"))
            {
                if (InstallAction.PreloadFiles != null)
                {
                    InstallAction.PreloadFiles.ForEach(x => context.GetEngine().Manager.GetAssetsFile(x));
                }
            }
            List <AssetOp> ops = new List <AssetOp>();

            foreach (var action in InstallAction.Actions.OrderBy(x => x.StepNumber))
            {
                using (new LogTiming($"getting operations for asset mod action step {action.StepNumber}"))
                {
                    ops.AddRange(action.GetOps(context));
                }
            }
            Log.LogMsg($"Returning {ops.Count} for assets mod component installation...");
            return(ops);
        }
        public override IEnumerable <AssetOp> GetOps(ModContext context)
        {
            //TODO: I don't like the root path constant here.  Get rid of it.
            if (!context.Config.RootFileProvider.FileExists(context.ModPath.CombineFwdSlash(FromDataFile)))
            {
                throw new Exception($"ReplaceAssetsAction could not find the asset data file {FromDataFile}");
            }
            if (Locator == null)
            {
                throw new Exception("Locator is null for ReplaceAssetsAction!");
            }

            byte[] assetData;
            try
            {
                assetData = context.Config.RootFileProvider.Read(context.ModPath.CombineFwdSlash(FromDataFile));
            }
            catch (Exception ex)
            {
                Log.LogMsg($"Exception reading {FromDataFile} for ReplaceAssetAction.", ex);
                throw new Exception($"ReplaceAssetAction could not read data file {FromDataFile}", ex);
            }

            yield return(new ReplaceAssetOp(Locator, assetData, AllowOverwriteName));
        }
 public void LoadMods(string modsPath)
 {
     if (Directory.Exists(modsPath))
     {
         foreach (string localModPath in Directory.GetDirectories(modsPath))
         {
             if (!ModContext.IsValidModPath(localModPath))
             {
                 // Debug.LogWarning($"Invalid mod path, skipping {localModPath}"); // there are also blueprints in this folder
                 continue;
             }
             var mod = ModContext.FromFolderPath(localModPath);
             if (mods.ContainsKey(mod.Description.LocalId))
             {
                 Debug.LogWarning($"This mod is already loaded, skipping {localModPath}");
             }
             else
             {
                 mods.TryAdd(mod.Description.LocalId, mod);
                 mod.LoadModShapes();
                 LoadLanguageFiles(Path.Combine(mod.ModFolderPath, "Gui", "Language"));
             }
         }
     }
 }
Пример #4
0
 /// <summary>
 /// 获取当前数据上下文
 /// </summary>
 /// <returns></returns>
 public static ModContext GetCurrentContext()
 {
     var nContext = CallContext.GetData("ModContext") as ModContext;
     if (nContext == null)
     {
         nContext = new ModContext();
         CallContext.SetData("ModContext", nContext);
     }
     return nContext;
 }
Пример #5
0
        public Part(PartData partData, ModContext mod = null) : base(mod)
        {
            this.partData = partData;
            this.partData.LoadRenderableData(mod?.ModFolderPath); // load json file

            //importer.SetConfig(new Assimp.Configs.MeshVertexLimitConfig(60000));
            //importer.SetConfig(new Assimp.Configs.MeshTriangleLimitConfig(60000));
            //importer.SetConfig(new Assimp.Configs.RemoveDegeneratePrimitivesConfig(true));
            //importer.SetConfig(new Assimp.Configs.SortByPrimitiveTypeConfig(Assimp.PrimitiveType.Line | Assimp.PrimitiveType.Point));
            //Assimp.PostProcessSteps postProcessSteps = Assimp.PostProcessPreset.TargetRealTimeMaximumQuality | Assimp.PostProcessSteps.MakeLeftHanded | Assimp.PostProcessSteps.FlipWindingOrder;
        }
        public static void LoadShapes(PartListData partListData, ModContext mod = null) // modUuid can be "vanilla"
        {
            if (partListData.PartList != null)
            {
                Parallel.ForEach(partListData.PartList, partData =>
                {
                    var part = new Part(partData, mod);
                    loadedShapes.TryAdd(partData.Uuid, part);
                });
            }

            if (partListData.BlockList != null)
            {
                Parallel.ForEach(partListData.BlockList, blockData =>
                {
                    var block = new Block(blockData, mod);
                    loadedShapes.TryAdd(blockData.Uuid, block);
                });
            }
        }
Пример #7
0
        public IEnumerable <ModContext> Activate()
        {
            List <ModContext> mods = new List <ModContext>();

            var runtime = _modDirectory.EnumerateFiles().First(
                f => f.Extension == ".dll");
            var modAss = Assembly.LoadFile(runtime.FullName);

            var primaryModule = modAss.Modules.First();
            var modEntries    = primaryModule.GetTypes().Where(t => typeof(ISoLModV1).IsAssignableFrom(t));

            foreach (var modEntry in modEntries)
            {
                ISoLModV1  mod = (ISoLModV1)Activator.CreateInstance(modEntry);
                ModContext ctx = new ModContext(mod, _modDirectory);
                mod.Init(AnankeContext.Current, ctx);
                mods.Add(ctx);
            }

            return(mods);
        }
Пример #8
0
        public void LoadMods()
        {
            var knownMods = this.Config.KnownMods;

            GlobalLog.Default.PrintLine("Total known mods: " + knownMods.Count);

            foreach (var modInfo in knownMods)
            {
                var modIdentifier = ModUtils.GetReadableIdentifier(modInfo);

                if (modInfo.Active == false)
                {
                    GlobalLog.Debug.PrintLine("Skipping (not active): " + modIdentifier);
                    continue;
                }

                using (GlobalLog.Default.OpenScope("Loading: " + modIdentifier, "Finished loading: " + modIdentifier))
                {
                    var modContext = new ModContext(modInfo);
                    this.LoadedMods.Add(modContext);
                }
            }
        }
Пример #9
0
    public Mod(ModContext context)
    {
        _modLoader = context.ModLoader;
        _hooks     = context.Hooks;
        _logger    = context.Logger;
        _owner     = context.Owner;
#if (IncludeConfig)
        _configuration = context.Configuration;
#endif
        _modConfig = context.ModConfig;

#if DEBUG
        // Attaches debugger in debug mode; ignored in release.
        Debugger.Launch();
#endif

        // For more information about this template, please see
        // https://reloaded-project.github.io/Reloaded-II/ModTemplate/

        // If you want to implement e.g. unload support in your mod,
        // and some other neat features, override the methods in ModBase.

        // TODO: Implement some mod logic
    }
Пример #10
0
 public PathHelper(ModContext context)
 {
     localDir = context.ModDirectory;
 }
Пример #11
0
 public abstract IEnumerable <AssetOps.AssetOp> GetOps(ModContext context);
Пример #12
0
        internal static IEnumerable <IModContext <IFallout4Mod, IMajorRecordCommon, IMajorRecordCommonGetter> > EnumerateMajorRecordContexts(
            this IReadOnlyList <IWorldspaceBlockGetter> worldspaceBlocks,
            IWorldspaceGetter worldspace,
            ILinkCache linkCache,
            Type type,
            ModKey modKey,
            IModContext?parent,
            bool throwIfUnknown,
            Func <IFallout4Mod, IWorldspaceGetter, IWorldspace> getOrAddAsOverride,
            Func <IFallout4Mod, IWorldspaceGetter, string?, IWorldspace> duplicateInto)
        {
            foreach (var readOnlyBlock in worldspaceBlocks)
            {
                var blockNumX    = readOnlyBlock.BlockNumberX;
                var blockNumY    = readOnlyBlock.BlockNumberY;
                var blockContext = new ModContext <IWorldspaceBlockGetter>(
                    modKey: modKey,
                    parent: parent,
                    record: readOnlyBlock);
                foreach (var readOnlySubBlock in readOnlyBlock.Items)
                {
                    var subBlockNumY    = readOnlySubBlock.BlockNumberY;
                    var subBlockNumX    = readOnlySubBlock.BlockNumberX;
                    var subBlockContext = new ModContext <IWorldspaceSubBlockGetter>(
                        modKey: modKey,
                        parent: blockContext,
                        record: readOnlySubBlock);
                    foreach (var readOnlyCell in readOnlySubBlock.Items)
                    {
                        Func <IFallout4Mod, ICellGetter, bool, string?, ICell> cellGetter = (mod, copyCell, dup, edid) =>
                        {
                            var worldspaceCopy = getOrAddAsOverride(mod, worldspace);
                            var formKey        = copyCell.FormKey;
                            var retrievedBlock = worldspaceCopy.SubCells.FirstOrDefault(x => x.BlockNumberX == blockNumX && x.BlockNumberY == blockNumY);
                            if (retrievedBlock == null)
                            {
                                retrievedBlock = new WorldspaceBlock()
                                {
                                    BlockNumberX = blockNumX,
                                    BlockNumberY = blockNumY,
                                    GroupType    = GroupTypeEnum.ExteriorCellBlock,
                                };
                                worldspaceCopy.SubCells.Add(retrievedBlock);
                            }
                            var subBlock = retrievedBlock.Items.FirstOrDefault(x => x.BlockNumberX == subBlockNumX && x.BlockNumberY == subBlockNumY);
                            if (subBlock == null)
                            {
                                subBlock = new WorldspaceSubBlock()
                                {
                                    BlockNumberX = subBlockNumX,
                                    BlockNumberY = subBlockNumY,
                                    GroupType    = GroupTypeEnum.ExteriorCellSubBlock,
                                };
                                retrievedBlock.Items.Add(subBlock);
                            }
                            var cell = subBlock.Items.FirstOrDefault(cell => cell.FormKey == formKey);
                            if (cell == null)
                            {
                                if (dup)
                                {
                                    cell = copyCell.Duplicate(mod.GetNextFormKey(edid), CellCopyMask);
                                }
                                else
                                {
                                    cell = copyCell.DeepCopy(CellCopyMask);
                                }
                                subBlock.Items.Add(cell);
                            }
                            return(cell);
                        };

                        if (LoquiRegistration.TryGetRegister(type, out var regis) &&
                            regis.ClassType == typeof(Cell))
                        {
                            yield return(new ModContext <IFallout4Mod, IMajorRecordCommon, IMajorRecordCommonGetter>(
                                             modKey: modKey,
                                             record: readOnlyCell,
                                             getOrAddAsOverride: (m, r) => cellGetter(m, (ICellGetter)r, false, default(string?)),
                                             duplicateInto: (m, r, e) => cellGetter(m, (ICellGetter)r, true, e),
                                             parent: subBlockContext));
                        }
                        else
                        {
                            foreach (var con in CellCommon.Instance.EnumerateMajorRecordContexts(
                                         readOnlyCell,
                                         linkCache,
                                         type,
                                         modKey,
                                         subBlockContext,
                                         throwIfUnknown,
                                         (m, c) => cellGetter(m, c, false, default(string?)),
                                         (m, c, e) => cellGetter(m, c, true, e)))
                            {
                                yield return(con);
                            }
                        }
                    }
                }
            }
        }
Пример #13
0
        public override IEnumerable <AssetOp> GetOps(ModContext context)
        {
            if (Locator == null)
            {
                throw new Exception("Locator is null for RestoreAssetsAction!");
            }
            if (context.BackupEngine == null)
            {
                throw new Exception("BackupEngine is not set in the context!");
            }
            byte[] assetData = null;
            try
            {
                var bqae      = context.BackupEngine;
                var fromAsset = Locator.Locate(bqae.Manager);
                if (fromAsset == null)
                {
                    throw new Exception($"Unable to locate asset in backup apk for mod uninstallation on step {StepNumber}!");
                }
                using (var ms = new MemoryStream())
                {
                    using (var writer = new AssetsWriter(ms))
                    {
                        fromAsset.Write(writer);
                    }
                    ms.Seek(0, SeekOrigin.Begin);
                    assetData = ms.ToArray();
                }
            }
            catch (Exception ex)
            {
                Log.LogErr("Exception while restoring asset!", ex);
                throw;
            }
            AssetLocator locatorOverride = null;

            try
            {
                var res = Locator.Locate(context.GetEngine().Manager, false);
                if (res == null)
                {
                    throw new LocatorException("Unable to find asset.");
                }
            }
            catch (LocatorException ex)
            {
                Log.LogErr($"The locator for restore threw an exception, attempting to locate against backup and identify path.", ex);
                try
                {
                    var res = Locator.Locate(context.BackupEngine.Manager, false);
                    if (res == null)
                    {
                        throw new LocatorException("Unable to find asset, locator returned null");
                    }
                    locatorOverride = new AssetLocator()
                    {
                        PathIs = new PathLocator()
                        {
                            AssetFilename = res.ObjectInfo.ParentFile.AssetsFilename, PathID = res.ObjectInfo.ObjectID
                        }
                    };
                }
                catch (Exception ex2)
                {
                    Log.LogErr($"Unable to find path in backup for the locator either", ex2);
                    throw ex;
                }
            }
            yield return(new ReplaceAssetOp(locatorOverride ?? Locator, assetData, true));
        }
Пример #14
0
 public Block(BlockData blockData, ModContext mod = null) : base(mod)
 {
     this.blockData = blockData;
 }
Пример #15
0
        internal static IEnumerable <IModContext <IOblivionMod, IOblivionModGetter, IMajorRecordCommon, IMajorRecordCommonGetter> > EnumerateMajorRecordContexts(
            this IListGroupGetter <ICellBlockGetter> cellBlocks,
            ILinkCache linkCache,
            Type type,
            ModKey modKey,
            IModContext?parent,
            bool throwIfUnknown)
        {
            foreach (var readOnlyBlock in cellBlocks.Records)
            {
                var blockNum      = readOnlyBlock.BlockNumber;
                var blockModified = readOnlyBlock.LastModified;
                var blockContext  = new ModContext <ICellBlockGetter>(
                    modKey: modKey,
                    parent: parent,
                    record: readOnlyBlock);
                foreach (var readOnlySubBlock in readOnlyBlock.SubBlocks)
                {
                    var subBlockNum      = readOnlySubBlock.BlockNumber;
                    var subBlockModified = readOnlySubBlock.LastModified;
                    var subBlockContext  = new ModContext <ICellSubBlockGetter>(
                        modKey: modKey,
                        parent: blockContext,
                        record: readOnlySubBlock);
                    foreach (var readOnlyCell in readOnlySubBlock.Cells)
                    {
                        Func <IOblivionMod, ICellGetter, bool, string?, ICell> cellGetter = (mod, copyCell, dup, edid) =>
                        {
                            var formKey        = copyCell.FormKey;
                            var retrievedBlock = mod.Cells.Records.FirstOrDefault(x => x.BlockNumber == blockNum);
                            if (retrievedBlock == null)
                            {
                                retrievedBlock = new CellBlock()
                                {
                                    BlockNumber  = blockNum,
                                    GroupType    = GroupTypeEnum.InteriorCellBlock,
                                    LastModified = blockModified,
                                };
                                mod.Cells.Records.Add(retrievedBlock);
                            }
                            var subBlock = retrievedBlock.SubBlocks.FirstOrDefault(x => x.BlockNumber == subBlockNum);
                            if (subBlock == null)
                            {
                                subBlock = new CellSubBlock()
                                {
                                    BlockNumber  = subBlockNum,
                                    GroupType    = GroupTypeEnum.InteriorCellSubBlock,
                                    LastModified = subBlockModified,
                                };
                                retrievedBlock.SubBlocks.Add(subBlock);
                            }
                            var cell = subBlock.Cells.FirstOrDefault(cell => cell.FormKey == formKey);
                            if (cell == null)
                            {
                                if (dup)
                                {
                                    cell = copyCell.Duplicate(mod.GetNextFormKey(edid), CellCopyMask);
                                }
                                else
                                {
                                    cell = copyCell.DeepCopy(CellCopyMask);
                                }
                                subBlock.Cells.Add(cell);
                            }
                            return(cell);
                        };

                        if (LoquiRegistration.TryGetRegister(type, out var regis) &&
                            regis.ClassType == typeof(Cell))
                        {
                            yield return(new ModContext <IOblivionMod, IOblivionModGetter, IMajorRecordCommon, IMajorRecordCommonGetter>(
                                             modKey: modKey,
                                             record: readOnlyCell,
                                             getOrAddAsOverride: (m, r) => cellGetter(m, (ICellGetter)r, false, default(string?)),
                                             duplicateInto: (m, r, e) => cellGetter(m, (ICellGetter)r, true, e),
                                             parent: subBlockContext));
                        }
                        else
                        {
                            foreach (var con in CellCommon.Instance.EnumerateMajorRecordContexts(
                                         readOnlyCell,
                                         linkCache,
                                         type,
                                         modKey,
                                         subBlockContext,
                                         throwIfUnknown,
                                         (m, c) => cellGetter(m, c, false, default(string?)),
                                         (m, c, e) => cellGetter(m, c, true, e)))
                            {
                                yield return(con);
                            }
                        }
                    }
                }
            }
        }
Пример #16
0
        public override List <AssetOp> GetUninstallOps(ModContext context)
        {
            if (UninstallAction == null)
            {
                throw new InvalidOperationException("Tried to install AssetsModComponent, but uninstall action is null.");
            }
            if (UninstallAction.Actions == null || UninstallAction.Actions.Count < 1)
            {
                throw new InvalidOperationException("Uninstall action has no asset actions defined!");
            }
            if (string.IsNullOrEmpty(context.Config.BackupApkFileAbsolutePath))
            {
                throw new InvalidOperationException("Uninstall assets mod can't happen when the backup APK isn't set!");
            }
            string backup = null;

            if (!File.Exists(context.Config.BackupApkFileAbsolutePath))
            {
                Log.LogErr($"WARNING: primary APK backup doesn't exist at {context.Config.BackupApkFileAbsolutePath}, will attempt to fall back...");
                if (!File.Exists(context.Config.ModdedFallbackBackupPath))
                {
                    throw new Exception($"Backup APK file does not exist at {context.Config.BackupApkFileAbsolutePath} and even the fallback doesn't exist at {context.Config.ModdedFallbackBackupPath}");
                }
                else
                {
                    backup = context.Config.ModdedFallbackBackupPath;
                }
            }
            else
            {
                backup = context.Config.BackupApkFileAbsolutePath;
            }


            using (new LogTiming("preloading asset files for uninstall assets mod"))
            {
                if (UninstallAction.PreloadFiles != null)
                {
                    UninstallAction.PreloadFiles.ForEach(x => context.GetEngine().Manager.GetAssetsFile(x));
                }
            }
            Log.LogMsg($"Opening backup APK...");
            List <AssetOp> ops = new List <AssetOp>();

            using (var apk = new ZipFileProvider(backup, FileCacheMode.Memory, true, FileUtils.GetTempDirectory()))
            {
                var backupCfg = new QaeConfig()
                {
                    AssetsPath = BeatSaber.BSConst.KnownFiles.AssetsRootPath, SongsPath = "", ModsSourcePath = "", PlaylistArtPath = "", RootFileProvider = apk
                };
                using (var backupQae = new QuestomAssetsEngine(backupCfg))
                {
                    using (new LogTiming("preloading asset files for uninstall assets mod on BACKUP apk/qae"))
                    {
                        if (UninstallAction.PreloadFiles != null)
                        {
                            UninstallAction.PreloadFiles.ForEach(x => backupQae.Manager.GetAssetsFile(x));
                        }
                    }
                    using (new LogTiming("preloading asset files that are loaded in the main engine"))
                    {
                        context.GetEngine().Manager.OpenFiles.ForEach(x => backupQae.Manager.GetAssetsFile(x.AssetsFilename));
                    }
                    context.BackupEngine = backupQae;
                    foreach (var action in UninstallAction.Actions.OrderBy(x => x.StepNumber))
                    {
                        using (new LogTiming($"getting operations for asset mod uninstall action step {action.StepNumber}"))
                        {
                            ops.AddRange(action.GetOps(context));
                        }
                    }
                }
                context.BackupEngine = null;
            }

            Log.LogMsg($"Returning {ops.Count} for assets mod component uninstall...");
            return(ops);
        }
Пример #17
0
        internal static IEnumerable <IModContext <IOblivionMod, IMajorRecordCommon, IMajorRecordCommonGetter> > EnumerateMajorRecordContexts(
            this IReadOnlyList <IWorldspaceBlockGetter> worldspaceBlocks,
            IWorldspaceGetter worldspace,
            ILinkCache linkCache,
            Type type,
            ModKey modKey,
            IModContext?parent,
            bool throwIfUnknown,
            Func <IOblivionMod, IWorldspaceGetter, IWorldspace> getter)
        {
            foreach (var readOnlyBlock in worldspaceBlocks)
            {
                var blockNumX     = readOnlyBlock.BlockNumberX;
                var blockNumY     = readOnlyBlock.BlockNumberY;
                var blockModified = readOnlyBlock.LastModified;
                var blockContext  = new ModContext <IWorldspaceBlockGetter>(
                    modKey: modKey,
                    parent: parent,
                    record: readOnlyBlock);
                foreach (var readOnlySubBlock in readOnlyBlock.Items)
                {
                    var subBlockNumY     = readOnlySubBlock.BlockNumberY;
                    var subBlockNumX     = readOnlySubBlock.BlockNumberX;
                    var subBlockModified = readOnlySubBlock.LastModified;
                    var subBlockContext  = new ModContext <IWorldspaceSubBlockGetter>(
                        modKey: modKey,
                        parent: blockContext,
                        record: readOnlySubBlock);
                    foreach (var readOnlyCell in readOnlySubBlock.Items)
                    {
                        Func <IOblivionMod, ICellGetter, ICell> cellGetter = (mod, copyCell) =>
                        {
                            var worldspaceCopy = getter(mod, worldspace);
                            var formKey        = copyCell.FormKey;
                            var retrievedBlock = worldspaceCopy.SubCells.FirstOrDefault(x => x.BlockNumberX == blockNumX && x.BlockNumberY == blockNumY);
                            if (retrievedBlock == null)
                            {
                                retrievedBlock = new WorldspaceBlock()
                                {
                                    BlockNumberX = blockNumX,
                                    BlockNumberY = blockNumY,
                                    GroupType    = GroupTypeEnum.ExteriorCellBlock,
                                    LastModified = blockModified,
                                };
                                worldspaceCopy.SubCells.Add(retrievedBlock);
                            }
                            var subBlock = retrievedBlock.Items.FirstOrDefault(x => x.BlockNumberX == subBlockNumX && x.BlockNumberY == subBlockNumY);
                            if (subBlock == null)
                            {
                                subBlock = new WorldspaceSubBlock()
                                {
                                    BlockNumberX = subBlockNumX,
                                    BlockNumberY = subBlockNumY,
                                    GroupType    = GroupTypeEnum.ExteriorCellSubBlock,
                                    LastModified = readOnlySubBlock.LastModified,
                                };
                                retrievedBlock.Items.Add(subBlock);
                            }
                            var cell = subBlock.Items.FirstOrDefault(cell => cell.FormKey == formKey);
                            if (cell == null)
                            {
                                cell = copyCell.DeepCopy(CellCopyMask);
                                subBlock.Items.Add(cell);
                            }
                            return(cell);
                        };

                        if (LoquiRegistration.TryGetRegister(type, out var regis) &&
                            regis.ClassType == typeof(Cell))
                        {
                            yield return(new ModContext <IOblivionMod, IMajorRecordCommon, IMajorRecordCommonGetter>(
                                             modKey: modKey,
                                             record: readOnlyCell,
                                             getter: (m, r) => cellGetter(m, (ICellGetter)r),
                                             parent: subBlockContext));
                        }
                        else
                        {
                            foreach (var con in CellCommon.Instance.EnumerateMajorRecordContexts(readOnlyCell, linkCache, type, modKey, subBlockContext, throwIfUnknown, cellGetter))
                            {
                                yield return(con);
                            }
                        }
                    }
                }
            }
        }
Пример #18
0
 public Shape(ModContext mod)
 {
     this.mod = mod;
 }
Пример #19
0
        internal static IEnumerable <IModContext <ISkyrimMod, IMajorRecordCommon, IMajorRecordCommonGetter> > EnumerateMajorRecordContexts(
            this IListGroupGetter <ICellBlockGetter> cellBlocks,
            ILinkCache linkCache,
            Type type,
            ModKey modKey,
            IModContext?parent,
            bool throwIfUnknown)
        {
            foreach (var readOnlyBlock in cellBlocks.Records)
            {
                var blockNum     = readOnlyBlock.BlockNumber;
                var blockContext = new ModContext <ICellBlockGetter>(
                    modKey: modKey,
                    parent: parent,
                    record: readOnlyBlock);
                foreach (var readOnlySubBlock in readOnlyBlock.SubBlocks)
                {
                    var subBlockNum     = readOnlySubBlock.BlockNumber;
                    var subBlockContext = new ModContext <ICellSubBlockGetter>(
                        modKey: modKey,
                        parent: blockContext,
                        record: readOnlySubBlock);
                    foreach (var readOnlyCell in readOnlySubBlock.Cells)
                    {
                        Func <ISkyrimMod, ICellGetter, ICell> cellGetter = (mod, copyCell) =>
                        {
                            var formKey        = copyCell.FormKey;
                            var retrievedBlock = mod.Cells.Records.FirstOrDefault(x => x.BlockNumber == blockNum);
                            if (retrievedBlock == null)
                            {
                                retrievedBlock = new CellBlock()
                                {
                                    BlockNumber = blockNum,
                                    GroupType   = GroupTypeEnum.InteriorCellBlock,
                                };
                                mod.Cells.Records.Add(retrievedBlock);
                            }
                            var subBlock = retrievedBlock.SubBlocks.FirstOrDefault(x => x.BlockNumber == subBlockNum);
                            if (subBlock == null)
                            {
                                subBlock = new CellSubBlock()
                                {
                                    BlockNumber = subBlockNum,
                                    GroupType   = GroupTypeEnum.InteriorCellSubBlock,
                                };
                                retrievedBlock.SubBlocks.Add(subBlock);
                            }
                            var cell = subBlock.Cells.FirstOrDefault(cell => cell.FormKey == formKey);
                            if (cell == null)
                            {
                                cell = copyCell.DeepCopy(CellCopyMask);
                                subBlock.Cells.Add(cell);
                            }
                            return(cell);
                        };

                        if (LoquiRegistration.TryGetRegister(type, out var regis) &&
                            regis.ClassType == typeof(Cell))
                        {
                            yield return(new ModContext <ISkyrimMod, IMajorRecordCommon, IMajorRecordCommonGetter>(
                                             modKey: modKey,
                                             record: readOnlyCell,
                                             getter: (m, r) => cellGetter(m, (ICellGetter)r),
                                             parent: subBlockContext));
                        }
                        else
                        {
                            foreach (var con in CellCommon.Instance.EnumerateMajorRecordContexts(readOnlyCell, linkCache, type, modKey, subBlockContext, throwIfUnknown, cellGetter))
                            {
                                yield return(con);
                            }
                        }
                    }
                }
            }
        }