/// <summary> /// Loads the specified DLL, and then inspects that DLLs Module Reference Table to import any additional /// references it might require recursively. /// </summary> /// <param name="dllToLoad"></param> private void LoadModuleDll(string dllToLoad) { var requiredDll = new MbbsDll(_fileUtility, _logger); if (!requiredDll.Load(dllToLoad, ModulePath, ModuleConfig.Patches?.Where(x => x.AbsoluteOffset > 0).ToList())) { _logger.Error($"Unable to load {dllToLoad}"); return; } requiredDll.SegmentOffset = (ushort)ModuleDlls.Sum(x => x.File.SegmentTable.Count); ModuleDlls.Add(requiredDll); _logger.Info($"Loaded {dllToLoad}"); //Pull records from ModuleReferenceTable that aren't of References handled internally by the emulator foreach (var import in ModuleDlls[0].File.ModuleReferenceTable .Where(x => GetAllExportedModules().All(e => e != x.Name)).Select(x => x.Name)) { //Only load a dependency if it's not already loaded by a previous library if (ModuleDlls.All(x => x.File.FileName.ToUpper().Split('.')[0] != import.ToUpper())) { LoadModuleDll(import); } } }
/// <summary> /// Constructor for MbbsModule /// /// Pass in an empty/blank moduleIdentifier for a Unit Test/Fake Module /// </summary> /// <param name="logger"></param> /// <param name="moduleIdentifier">Will be null in a test</param> /// <param name="path"></param> /// <param name="memoryCore"></param> /// <param name="fileUtility"></param> public MbbsModule(IFileUtility fileUtility, IClock clock, ILogger logger, string moduleIdentifier, string path = "", MemoryCore memoryCore = null) { _fileUtility = fileUtility; _logger = logger; _clock = clock; ModuleIdentifier = moduleIdentifier; ModuleDlls = new List <MbbsDll>(); //Sanitize and setup Path if (string.IsNullOrEmpty(path)) { path = Directory.GetCurrentDirectory(); } if (!Path.EndsInDirectorySeparator(path)) { path += Path.DirectorySeparatorChar; } ModulePath = path; // will be null in tests if (string.IsNullOrEmpty(ModuleIdentifier)) { Mdf = MdfFile.createForTest(); ModuleDlls.Add(new MbbsDll(fileUtility, logger) { File = NEFile.createForTest() }); } else { //Verify MDF File Exists var mdfFile = fileUtility.FindFile(ModulePath, $"{ModuleIdentifier}.MDF"); var fullMdfFilePath = Path.Combine(ModulePath, mdfFile); if (!System.IO.File.Exists(fullMdfFilePath)) { throw new FileNotFoundException($"Unable to locate Module: {fullMdfFilePath}"); } Mdf = new MdfFile(fullMdfFilePath); var moduleDll = new MbbsDll(fileUtility, logger); moduleDll.Load(Mdf.DLLFiles[0].Trim(), ModulePath); ModuleDlls.Add(moduleDll); if (Mdf.Requires.Count > 0) { foreach (var r in Mdf.Requires) { var requiredDll = new MbbsDll(fileUtility, logger); if (requiredDll.Load(r.Trim(), ModulePath)) { requiredDll.SegmentOffset = (ushort)(ModuleDlls.Sum(x => x.File.SegmentTable.Count) + 1); ModuleDlls.Add(requiredDll); } } } if (Mdf.MSGFiles.Count > 0) { Msgs = new List <MsgFile>(Mdf.MSGFiles.Count); foreach (var m in Mdf.MSGFiles) { Msgs.Add(new MsgFile(ModulePath, m)); } } } //Set Initial Values RtkickRoutines = new PointerDictionary <RealTimeRoutine>(); RtihdlrRoutines = new PointerDictionary <RealTimeRoutine>(); TaskRoutines = new PointerDictionary <RealTimeRoutine>(); TextVariables = new Dictionary <string, FarPtr>(); GlobalCommandHandlers = new List <FarPtr>(); ExportedModuleDictionary = new Dictionary <ushort, IExportedModule>(6); ExecutionUnits = new Queue <ExecutionUnit>(2); Memory = memoryCore ?? new MemoryCore(); //Declare PSP Segment var psp = new PSPStruct { NextSegOffset = 0x9FFF, EnvSeg = 0xFFFF }; Memory.AddSegment(0x4000); Memory.SetArray(0x4000, 0, psp.Data); Memory.AllocateVariable("Int21h-PSP", sizeof(ushort)); Memory.SetWord("Int21h-PSP", 0x4000); //Find _INIT_ values if any foreach (var dll in ModuleDlls) { //If it's a Test, setup a fake _INIT_ if (string.IsNullOrEmpty(ModuleIdentifier)) { dll.EntryPoints["_INIT_"] = null; return; } //Setup _INIT_ Entrypoint FarPtr initEntryPointPointer; var initResidentName = dll.File.ResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initResidentName == null) { //This only happens with MajorMUD -- I have no idea why it's a special little snowflake ¯\_(ツ)_/¯ _logger.Warn($"({moduleIdentifier}) Unable to locate _INIT_ in Resident Name Table, checking Non-Resident Name Table..."); var initNonResidentName = dll.File.NonResidentNameTable.FirstOrDefault(x => x.Name.StartsWith("_INIT__")); if (initNonResidentName == null) { throw new Exception("Unable to locate _INIT__ entry in Resident Name Table"); } var initEntryPoint = dll.File.EntryTable.First(x => x.Ordinal == initNonResidentName.IndexIntoEntryTable); initEntryPointPointer = new FarPtr((ushort)(initEntryPoint.SegmentNumber + dll.SegmentOffset), initEntryPoint.Offset); } else { var initEntryPoint = dll.File.EntryTable.First(x => x.Ordinal == initResidentName.IndexIntoEntryTable); initEntryPointPointer = new FarPtr((ushort)(initEntryPoint.SegmentNumber + dll.SegmentOffset), initEntryPoint.Offset); } _logger.Debug($"({ModuleIdentifier}) Located _INIT__: {initEntryPointPointer}"); dll.EntryPoints["_INIT_"] = initEntryPointPointer; } }