public ReadOnlySpan <byte> Invoke(ushort ordinal, bool offsetsOnly = false) { switch (ordinal) { case 248: return(_txtlen); } if (offsetsOnly) { var methodPointer = new IntPtr16(0xFFFC, ordinal); #if DEBUG //_logger.Info($"Returning Method Offset {methodPointer.Segment:X4}:{methodPointer.Offset:X4}"); #endif return(methodPointer.Data); } switch (ordinal) { case 30: oldsend(); break; case 123: simpsnd(); break; default: throw new ArgumentOutOfRangeException($"Unknown Exported Function Ordinal in GALME: {ordinal}"); } return(null); }
public ReadOnlySpan <byte> Invoke(ushort ordinal, bool offsetsOnly = false) { if (offsetsOnly) { var methodPointer = new IntPtr16(0xFFFC, ordinal); #if DEBUG //_logger.Info($"Returning Method Offset {methodPointer.Segment:X4}:{methodPointer.Offset:X4}"); #endif return(methodPointer.Data); } switch (ordinal) { case 49: DosRealIntr(); break; case 16: DosAllocRealSeg(); break; default: throw new ArgumentOutOfRangeException($"Unknown Exported Function Ordinal in PHAPI: {ordinal}"); } return(null); }
protected short fclose(IntPtr16 filep) { ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FCLOSE_ORDINAL, new List <IntPtr16> { filep }); return((short)mbbsEmuCpuRegisters.AX); }
private string GetFileFromFndblk(IntPtr16 fndblkPointer) { var fbs = new FndblkStruct(mbbsEmuMemoryCore.GetArray(fndblkPointer, FndblkStruct.StructSize)); Assert.Equal(9, fbs.Size); Assert.Equal(0, (byte)fbs.Attributes & (byte)~FndblkStruct.AttributeFlags.Archive); Assert.True(Math.Abs(ticksNow - fbs.DateTime.Ticks) <= FIVE_SECOND_TICKS); return(fbs.Name); }
/// <summary> /// Begins emulated x86 Execution at the given entry point /// </summary> /// <param name="entryPoint">Pointer to segment:offset emulation is to begin at</param> /// <param name="channelNumber">Channel Number code is being executed for (used to Set State of Exported Modules)</param> /// <param name="simulateCallFar">Simulating a CALL FAR pushes CS:IP to the stack and sets BP=SP</param> /// <param name="bypassState">Some method pointers don't require the Exported Module to have a state set</param> /// <param name="initialStackValues">Values to be on the stack at the start of emulation (arguments passed in)</param> /// <param name="initialStackPointer">Initial SP offset (used to shift SP as to not overlap memory space on nested execution)</param> /// <returns></returns> public CpuRegisters Execute(IntPtr16 entryPoint, ushort channelNumber, bool simulateCallFar = false, bool bypassState = false, Queue <ushort> initialStackValues = null, ushort initialStackPointer = CpuCore.STACK_BASE) { //Reset Registers to Startup State for the CPU ModuleCpu.Reset(initialStackPointer); //Reset Registers ModuleCpuRegisters.CS = entryPoint.Segment; ModuleCpuRegisters.IP = entryPoint.Offset; //Any parameters that need to be passed into the function if (initialStackValues != null) { //Push Parameters while (initialStackValues.TryDequeue(out var valueToPush)) { ModuleCpu.Push(valueToPush); } } //Simulating a CALL FAR if (simulateCallFar) { //Set stack to simulate CALL FAR ModuleCpuRegisters.BP = ModuleCpuRegisters.SP; ModuleCpu.Push(ushort.MaxValue); //CS ModuleCpu.Push(ushort.MaxValue); //IP } foreach (var em in ExportedModuleDictionary.Values) { //Things like TEXT_VARIABLES don't need us to re-setup the state, the Exported Functions are already setup properly if (!bypassState) { em.SetState(channelNumber); } em.SetRegisters(ModuleCpuRegisters); } //Run until complete while (!ModuleCpuRegisters.Halt) { ModuleCpu.Tick(); } //Update Session State if (!bypassState && channelNumber != ushort.MaxValue && initialStackValues == null) { ExportedModuleDictionary[Majorbbs.Segment].UpdateSession(channelNumber); } //Return Registers return(ModuleCpuRegisters); }
protected ushort fwrite(IntPtr16 destPtr, ushort size, ushort count, IntPtr16 filep) { ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FWRITE_ORDINAL, new List <ushort> { destPtr.Offset, destPtr.Segment, size, count, filep.Offset, filep.Segment, }); return(mbbsEmuCpuRegisters.AX); }
/// <summary> /// Tells the module to begin x86 execution at the specified Entry Point /// </summary> /// <param name="entryPoint">Entry Point where execution will begin</param> /// <param name="channelNumber">Channel Number calling for execution</param> /// <param name="simulateCallFar">Are we simulating a CALL FAR? This is usually when we're calling a local function pointer</param> /// <param name="bypassSetState">Should we bypass setting a startup state? This is true for execution of things like Text Variable processing</param> /// <param name="initialStackValues">Initial Stack Values to be pushed to the stack prior to executing (simulating parameters on a method call)</param> /// <param name="initialStackPointer"> /// Initial Stack Pointer Value. Because EU's share the same memory space, including stack space, we need to sometimes need to manually decrement the /// stack pointer enough to where the stack on the nested call won't overlap with the stack on the parent caller. /// </param> /// <returns></returns> public CpuRegisters Execute(IntPtr16 entryPoint, ushort channelNumber, bool simulateCallFar = false, bool bypassSetState = false, Queue <ushort> initialStackValues = null, ushort initialStackPointer = CpuCore.STACK_BASE) { //Try to dequeue an execution unit, if one doesn't exist, create a new one if (!ExecutionUnits.TryDequeue(out var executionUnit)) { _logger.Warn($"{ModuleIdentifier} exhausted execution Units, creating additional"); executionUnit = new ExecutionUnit(Memory, ExportedModuleDictionary); } var resultRegisters = executionUnit.Execute(entryPoint, channelNumber, simulateCallFar, bypassSetState, initialStackValues, initialStackPointer); ExecutionUnits.Enqueue(executionUnit); return(resultRegisters); }
public ReadOnlySpan <byte> Invoke(ushort ordinal, bool offsetsOnly = false) { if (offsetsOnly) { var methodPointer = new IntPtr16(0xFFFC, ordinal); #if DEBUG //_logger.Info($"Returning Method Offset {methodPointer.Segment:X4}:{methodPointer.Offset:X4}"); #endif return(methodPointer.ToSpan()); } switch (ordinal) { default: throw new ArgumentOutOfRangeException($"Unknown Exported Function Ordinal in GALMSG: {ordinal}"); } }
public ReadOnlySpan <byte> Invoke(ushort ordinal, bool onlyProperties = false) { switch (ordinal) { case 89: return(dossetvec); } if (onlyProperties) { var methodPointer = new IntPtr16(Segment, ordinal); #if DEBUG //_logger.Info($"Returning Method Offset {methodPointer.Segment:X4}:{methodPointer.Offset:X4}"); #endif return(methodPointer.ToSpan()); } switch (ordinal) { case 34: DosAllocSeg(); break; case 44: DosLoadModule(); break; case 45: DosGetProcAddr(); break; case 47: DosGetModHandle(); break; case 48: DosGetModName(); break; default: throw new ArgumentOutOfRangeException($"Unknown Exported Function Ordinal in DOSCALLS: {ordinal}"); } return(null); }
protected ushort f_printf(IntPtr16 filep, string formatString, params object[] values) { var fprintfParameters = new List <ushort> { filep.Offset, filep.Segment }; //Add Formatted String var inputStingParameterPointer = mbbsEmuMemoryCore.AllocateVariable(Guid.NewGuid().ToString(), (ushort)(formatString.Length + 1)); mbbsEmuMemoryCore.SetArray(inputStingParameterPointer, Encoding.ASCII.GetBytes(formatString)); fprintfParameters.Add(inputStingParameterPointer.Offset); fprintfParameters.Add(inputStingParameterPointer.Segment); //Add Parameters var parameterList = GenerateParameters(values); fprintfParameters.AddRange(parameterList); ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FPRINTF_ORDINAL, fprintfParameters); return(mbbsEmuCpuRegisters.AX); }
/// <summary> /// Printf Parsing and Encoding /// </summary> /// <param name="s"></param> /// <param name="stringToParse"></param> /// <param name="startingParameterOrdinal"></param> /// <returns></returns> private protected ReadOnlySpan <byte> FormatPrintf(ReadOnlySpan <byte> stringToParse, ushort startingParameterOrdinal, bool isVsPrintf = false) { using var msOutput = new MemoryStream(stringToParse.Length); var currentParameter = startingParameterOrdinal; var vsPrintfBase = new IntPtr16(); if (isVsPrintf) { vsPrintfBase = GetParameterPointer(currentParameter); currentParameter += 2; } stringToParse = ProcessEscapeCharacters(stringToParse); for (var i = 0; i < stringToParse.Length; i++) { //Handle escaped %% as a single % -- or if % is the last character in a string if (stringToParse[i] == '%') { switch ((char)stringToParse[i + 1]) { case '%': //escaped % msOutput.WriteByte((byte)'%'); i++; continue; case '\0': //last character is an invalid single %, just print it and move on msOutput.WriteByte((byte)'%'); msOutput.WriteByte(0); i++; continue; } } //Found a Control Character if (stringToParse[i] == '%' && stringToParse[i + 1] != '%') { using var msFormattedValue = new MemoryStream(); i++; //Found a Lone % as the last character in a string, consider it just outputting the string submitted if (stringToParse[i] == 0x0) { var parameterOffset = GetParameter(currentParameter++); var parameterSegment = GetParameter(currentParameter++); if (Module.Memory.HasSegment(parameterSegment)) { msOutput.Write(Module.Memory.GetString(parameterSegment, parameterOffset)); } else { msOutput.Write(Encoding.ASCII.GetBytes("Invalid Pointer")); _logger.Error($"Invalid Pointer: {parameterSegment:X4}:{parameterOffset:X4}"); } continue; } //Process Flags var stringFlags = EnumPrintfFlags.None; while (InSpan(PRINTF_FLAGS, stringToParse.Slice(i, 1))) { switch ((char)stringToParse[i]) { case '-': stringFlags |= EnumPrintfFlags.LeftJustify; break; case '+': stringFlags |= EnumPrintfFlags.Signed; break; case ' ': stringFlags |= EnumPrintfFlags.Space; break; case '#': stringFlags |= EnumPrintfFlags.DecimalOrHex; break; case '0': stringFlags |= EnumPrintfFlags.LeftPadZero; break; } i++; } //Process Width var stringWidth = 0; var stringWidthValue = string.Empty; while (InSpan(PRINTF_WIDTH, stringToParse.Slice(i, 1))) { switch ((char)stringToParse[i]) { case '*': stringWidth = -1; break; default: stringWidthValue += (char)stringToParse[i]; break; } i++; } if (!string.IsNullOrEmpty(stringWidthValue)) { stringWidth = int.Parse(stringWidthValue); } if (stringWidth == -1) { if (!isVsPrintf) { //printf stringWidth = GetParameter(currentParameter++); } else { //vsprintf stringWidth = Module.Memory.GetWord(vsPrintfBase.Segment, vsPrintfBase.Offset); vsPrintfBase.Offset += 2; } } //Process Precision var stringPrecision = 0; var stringPrecisionValue = string.Empty; while (InSpan(PRINTF_PRECISION, stringToParse.Slice(i, 1))) { switch ((char)stringToParse[i]) { case '.': break; case '*': stringPrecision = -1; break; default: stringPrecisionValue += (char)stringToParse[i]; break; } i++; } if (!string.IsNullOrEmpty(stringPrecisionValue)) { stringPrecision = int.Parse(stringPrecisionValue); } if (stringPrecision == -1) { if (!isVsPrintf) { //printf stringPrecision = GetParameter(currentParameter++); } else { //vsprintf stringPrecision = Module.Memory.GetWord(vsPrintfBase.Segment, vsPrintfBase.Offset); vsPrintfBase.Offset += 2; } } //Process Length //TODO -- We'll process it but ignore it for now var variableLength = 0; while (InSpan(PRINTF_LENGTH, stringToParse.Slice(i, 1))) { switch (stringToParse[i]) { case (byte)'l': variableLength = 4; break; default: throw new Exception("Unsupported printf Length Specified"); } i++; } //Finally i should be at the specifier if (!InSpan(PRINTF_SPECIFIERS, stringToParse.Slice(i, 1))) { _logger.Warn($"Invalid printf format: {Encoding.ASCII.GetString(stringToParse)}"); continue; } switch ((char)stringToParse[i]) { //Character case 'c': { byte charParameter; if (isVsPrintf) { charParameter = Module.Memory.GetByte(vsPrintfBase.Segment, vsPrintfBase.Offset); vsPrintfBase.Offset += 2; } else { charParameter = (byte)GetParameter(currentParameter++); } msFormattedValue.WriteByte(charParameter); break; } //String of characters case 's': { ReadOnlySpan <byte> parameter; if (isVsPrintf) { var stringPointer = Module.Memory.GetPointer(vsPrintfBase); parameter = Module.Memory.GetString(stringPointer); vsPrintfBase.Offset += 4; } else { var parameterOffset = GetParameter(currentParameter++); var parameterSegment = GetParameter(currentParameter++); if (Module.Memory.HasSegment(parameterSegment)) { parameter = Module.Memory.GetString(parameterSegment, parameterOffset); } else { parameter = Encoding.ASCII.GetBytes("Invalid Pointer"); _logger.Error($"Invalid Pointer: {parameterSegment:X4}:{parameterOffset:X4}"); } } if (parameter[^ 1] == 0x0)
public ReadOnlySpan <byte> Invoke(ushort ordinal, bool offsetsOnly = false) { switch (ordinal) { case 72: return(bturno()); case 65: return(ticker); } if (offsetsOnly) { var methodPointer = new IntPtr16(0xFFFE, ordinal); #if DEBUG //_logger.Info($"Returning Method Offset {methodPointer.Segment:X4}:{methodPointer.Offset:X4}"); #endif return(methodPointer.ToSpan()); } switch (ordinal) { case 36: btuoba(); break; case 49: btutrg(); break; case 21: btuinj(); break; case 60: btuxnf(); break; case 39: btupbc(); break; case 87: btuica(); break; case 6: btucli(); break; case 4: btuchi(); break; case 63: chious(); break; case 83: btueba(); break; case 19: btuibw(); break; case 59: btuxmt(); break; case 7: btuclo(); break; case 30: btumil(); break; case 3: btuche(); break; case 5: btuclc(); break; case 8: btucls(); break; case 52: btutru(); break; case 37: btuoes(); break; case 11: btuech(); break; case 53: btutsw(); break; case 58: btuxmn(); break; case 34: btumon2(); break; case 32: btumks2(); break; case 48: btusts(); break; case 29: btumds2(); break; case 41: bturst(); break; case 40: btupmt(); break; case 9: btucmd(); break; case 56: btuxct(); break; case 64: chiout(); break; case 61: chiinj(); break; case 15: btuhcr(); break; case 44: btuscr(); break; case 26: btulok(); break; default: throw new ArgumentOutOfRangeException($"Unknown Exported Function Ordinal in GALGSBL: {ordinal}"); } return(null); }
/// <summary> /// Constructor for MbbsModule /// /// Pass in an empty/blank moduleIdentifier for a Unit Test/Fake Module /// </summary> /// <param name="moduleIdentifier"></param> /// <param name="path"></param> /// <param name="memoryCore"></param> public MbbsModule(string moduleIdentifier, string path = "", MemoryCore memoryCore = null) { ModuleIdentifier = moduleIdentifier; //Sanitize and setup Path if (string.IsNullOrEmpty(path)) { path = Directory.GetCurrentDirectory(); } if (!path.EndsWith(Path.DirectorySeparatorChar)) { path += Path.DirectorySeparatorChar; } ModulePath = path; //Verify MDF File Exists if (!string.IsNullOrEmpty(ModuleIdentifier) && !System.IO.File.Exists($"{ModulePath}{ModuleIdentifier}.MDF")) { throw new FileNotFoundException($"Unable to locate Module: {ModulePath}{ModuleIdentifier}.MDF"); } Mdf = !string.IsNullOrEmpty(ModuleIdentifier) ? new MdfFile($"{ModulePath}{ModuleIdentifier}.MDF") : MdfFile.createForTest(); File = !string.IsNullOrEmpty(ModuleIdentifier) ? new NEFile($"{ModulePath}{Mdf.DLLFiles[0].Trim()}.DLL") : NEFile.createForTest(); 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 EntryPoints = new Dictionary <string, IntPtr16>(); RtkickRoutines = new PointerDictionary <RealTimeRoutine>(); RtihdlrRoutines = new PointerDictionary <RealTimeRoutine>(); TaskRoutines = new PointerDictionary <RealTimeRoutine>(); TextVariables = new Dictionary <string, IntPtr16>(); ExecutionUnits = new Queue <ExecutionUnit>(2); ExportedModuleDictionary = new Dictionary <ushort, IExportedModule>(4); GlobalCommandHandlers = new List <IntPtr16>(); Memory = memoryCore ?? new MemoryCore(); //If it's a Test, setup a fake _INIT_ if (string.IsNullOrEmpty(ModuleIdentifier)) { EntryPoints["_INIT_"] = null; return; } //Setup _INIT_ Entrypoint IntPtr16 initEntryPointPointer; var initResidentName = 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("Unable to locate _INIT_ in Resident Name Table, checking Non-Resident Name Table..."); var initNonResidentName = 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 = File.EntryTable.First(x => x.Ordinal == initNonResidentName.IndexIntoEntryTable); initEntryPointPointer = new IntPtr16(initEntryPoint.SegmentNumber, initEntryPoint.Offset); } else { var initEntryPoint = File.EntryTable.First(x => x.Ordinal == initResidentName.IndexIntoEntryTable); initEntryPointPointer = new IntPtr16(initEntryPoint.SegmentNumber, initEntryPoint.Offset); } _logger.Info($"Located _INIT__: {initEntryPointPointer}"); EntryPoints["_INIT_"] = initEntryPointPointer; }
/// <summary> /// Patches Relocation information from each Code Segment Relocation Records into the Segment Byte Code /// /// Because the compiler doesn't know the location in memory of the hosts Exported Modules (Imported when /// viewed from the standpoint of the DLL), it saves the information to the Relocation Records for the /// given Code Segment. /// /// The x86 Emulator knows that any CALL FAR to a Segment >= 0xFF00 is an emulated Exported Module and properly /// handles calling the correct Module using the Segment of the target, and the Ordinal of the call using the Offset. /// A relocation record for a call to MAJORBBS->ATOL() would be patched as: /// /// CALL FAR 0xFFFF:0x004D /// /// Segment & Offset meaning: /// 0xFFFF == MAJORBBS /// 0x004D == 77, Ordinal for ATOL() /// </summary> /// <param name="module"></param> private void PatchRelocation(MbbsModule module) { //Declare Host Functions var majorbbsHostFunctions = GetFunctions(module, "MAJORBBS"); var galsblHostFunctions = GetFunctions(module, "GALGSBL"); var doscallsHostFunctions = GetFunctions(module, "DOSCALLS"); var galmeFunctions = GetFunctions(module, "GALME"); var phapiFunctions = GetFunctions(module, "PHAPI"); var galmsgFunctions = GetFunctions(module, "GALMSG"); foreach (var s in module.File.SegmentTable) { if (s.RelocationRecords == null || s.RelocationRecords.Count == 0) { continue; } foreach (var relocationRecord in s.RelocationRecords.Values) { //Ignored Relocation Record if (relocationRecord.TargetTypeValueTuple == null) { continue; } switch (relocationRecord.TargetTypeValueTuple.Item1) { case EnumRecordsFlag.ImportOrdinalAdditive: case EnumRecordsFlag.ImportOrdinal: { var nametableOrdinal = relocationRecord.TargetTypeValueTuple.Item2; var functionOrdinal = relocationRecord.TargetTypeValueTuple.Item3; var relocationResult = module.File.ImportedNameTable[nametableOrdinal].Name switch { "MAJORBBS" => majorbbsHostFunctions.Invoke(functionOrdinal, true), "GALGSBL" => galsblHostFunctions.Invoke(functionOrdinal, true), "DOSCALLS" => doscallsHostFunctions.Invoke(functionOrdinal, true), "GALME" => galmeFunctions.Invoke(functionOrdinal, true), "PHAPI" => phapiFunctions.Invoke(functionOrdinal, true), "GALMSG" => galmsgFunctions.Invoke(functionOrdinal, true), _ => throw new Exception( $"Unknown or Unimplemented Imported Library: {module.File.ImportedNameTable[nametableOrdinal].Name}") }; var relocationPointer = new IntPtr16(relocationResult); //32-Bit Pointer if (relocationRecord.SourceType == 3) { Array.Copy(relocationPointer.ToArray(), 0, s.Data, relocationRecord.Offset, 4); continue; } //16-Bit Values var result = relocationRecord.SourceType switch { //Offset 2 => relocationPointer.Segment, 5 => relocationPointer.Offset, _ => throw new ArgumentOutOfRangeException( $"Unhandled Relocation Source Type: {relocationRecord.SourceType}") }; if (relocationRecord.Flag.HasFlag(EnumRecordsFlag.ImportOrdinalAdditive)) { result += BitConverter.ToUInt16(s.Data, relocationRecord.Offset); } Array.Copy(BitConverter.GetBytes(result), 0, s.Data, relocationRecord.Offset, 2); break; } case EnumRecordsFlag.InternalRef: { //32-Bit Pointer if (relocationRecord.SourceType == 3) { var relocationPointer = new IntPtr16(relocationRecord.TargetTypeValueTuple.Item2, relocationRecord.TargetTypeValueTuple.Item4); Array.Copy(relocationPointer.ToArray(), 0, s.Data, relocationRecord.Offset, 4); break; } Array.Copy(BitConverter.GetBytes(relocationRecord.TargetTypeValueTuple.Item2), 0, s.Data, relocationRecord.Offset, 2); break; } case EnumRecordsFlag.ImportNameAdditive: case EnumRecordsFlag.ImportName: { var nametableOrdinal = relocationRecord.TargetTypeValueTuple.Item2; var functionOrdinal = relocationRecord.TargetTypeValueTuple.Item3; var newSegment = module.File.ImportedNameTable[nametableOrdinal].Name switch { "MAJORBBS" => Majorbbs.Segment, "GALGSBL" => Galgsbl.Segment, "PHAPI" => Phapi.Segment, "GALME" => Galme.Segment, "DOSCALLS" => Doscalls.Segment, _ => throw new Exception( $"Unknown or Unimplemented Imported Module: {module.File.ImportedNameTable[nametableOrdinal].Name}") }; var relocationPointer = new IntPtr16(newSegment, functionOrdinal); //32-Bit Pointer if (relocationRecord.SourceType == 3) { Array.Copy(relocationPointer.ToArray(), 0, s.Data, relocationRecord.Offset, 4); continue; } //16-Bit Values var result = relocationRecord.SourceType switch { //Offset 2 => relocationPointer.Segment, 5 => relocationPointer.Offset, _ => throw new ArgumentOutOfRangeException( $"Unhandled Relocation Source Type: {relocationRecord.SourceType}") }; if (relocationRecord.Flag.HasFlag(EnumRecordsFlag.ImportNameAdditive)) { result += BitConverter.ToUInt16(s.Data, relocationRecord.Offset); } Array.Copy(BitConverter.GetBytes(result), 0, s.Data, relocationRecord.Offset, 2); break; } default: throw new Exception("Unsupported Records Flag for Relocation Value"); } } } }
/// <summary> /// Runs the specified routine in the specified module /// </summary> /// <param name="moduleName"></param> /// <param name="routine"></param> /// <param name="channelNumber"></param> /// <param name="initialStackValues"></param> private ushort Run(string moduleName, IntPtr16 routine, ushort channelNumber, bool simulateCallFar = false, Queue <ushort> initialStackValues = null) { var resultRegisters = _modules[moduleName].Execute(routine, channelNumber, simulateCallFar, false, initialStackValues); return(resultRegisters.AX); }
/// <summary> /// Generates a Real Mode Interrupt from Protected Mode /// /// Allows for calling/passing information to TSR's running in real mode. This is used by some MajorBBS modules /// for directly accessing the Btrieve Driver /// </summary> private void DosRealIntr() { var interruptNumber = GetParameter(0); var registerPointer = GetParameterPointer(1); var reserved = GetParameter(3); //Must Be Zero var wordCount = GetParameter(4); //Count of word arguments on stack var regs = new Regs16Struct(Module.Memory.GetArray(registerPointer, Regs16Struct.Size).ToArray()); switch (interruptNumber) { case 0x21: //INT 21h Call switch (regs.AX >> 8) //INT 21h Function { case 0x35: //Get Interrupt Vector { switch ((byte)(regs.AX & 0xFF)) { case 0x7B: //Btrieve Vector { //Modules will use this vector to see if Btrieve is running regs.BX = 0x33; break; } default: throw new Exception($"Unknown Interrupt Vector: {(byte)(regs.AX & 0xFF):X2}h"); } break; } default: throw new Exception($"Unknown INT 21h Function: {regs.AX >> 8:X2}h"); } break; case 0x7B: //Btrieve Interrupt { var btvda = new BtvdatStruct(Module.Memory.GetArray(regs.DS, 0, BtvdatStruct.Size)); switch ((EnumBtrieveOperationCodes)btvda.funcno) { case EnumBtrieveOperationCodes.Open: { //Get the File Name to oPen var fileName = Encoding.ASCII.GetString(Module.Memory.GetString(btvda.keyseg, 0, true)); var btvFile = new BtrieveFileProcessor(_fileFinder, Module.ModulePath, fileName); //Setup Pointers var btvFileStructPointer = new IntPtr16(btvda.posblkseg, btvda.posblkoff); var btvFileNamePointer = Module.Memory.AllocateVariable($"{fileName}-NAME", (ushort)(fileName.Length + 1)); var btvDataPointer = Module.Memory.AllocateVariable($"{fileName}-RECORD", (ushort)btvFile.RecordLength); var newBtvStruct = new BtvFileStruct { filenam = btvFileNamePointer, reclen = (ushort)btvFile.RecordLength, data = btvDataPointer }; BtrieveSaveProcessor(btvFileStructPointer, btvFile); Module.Memory.SetArray(btvFileStructPointer, newBtvStruct.Data); Module.Memory.SetArray(btvFileNamePointer, Encoding.ASCII.GetBytes(fileName + '\0')); //Set the active Btrieve file (BB) to the now open file Module.Memory.SetPointer("BB", btvFileStructPointer); #if DEBUG _logger.Info($"Opened file {fileName} and allocated it to {btvFileStructPointer}"); #endif Registers.AX = 0; break; } case EnumBtrieveOperationCodes.Stat: { var currentBtrieveFile = BtrieveGetProcessor(Module.Memory.GetPointer("BB")); var btvStats = new BtvstatfbStruct { fs = new BtvfilespecStruct() { numofr = (uint)currentBtrieveFile.GetRecordCount(), numofx = (ushort)currentBtrieveFile.Keys.Count, pagsiz = (ushort)currentBtrieveFile.PageLength, reclen = (ushort)currentBtrieveFile.RecordLength } }; var definedKeys = currentBtrieveFile.Keys.Values.SelectMany(k => k.Segments).Select(k => new BtvkeyspecStruct() { flags = (ushort)k.Attributes, keylen = k.Length, keypos = k.Position, numofk = k.Number }).ToList(); btvStats.keyspec = definedKeys.ToArray(); Module.Memory.SetArray(btvda.databufsegment, btvda.databufoffset, btvStats.Data); Registers.AX = 0; break; } case EnumBtrieveOperationCodes.SetOwner: //Ignore Registers.AX = 0; break; default: throw new Exception($"Unknown Btrieve Operation: {(EnumBtrieveOperationCodes)btvda.funcno}"); } break; } default: throw new Exception($"Unhandled Interrupt: {interruptNumber:X2}h"); } Module.Memory.SetArray(registerPointer, regs.Data); }