From 8f056b9c641f5e9b7e6b8f4111d6a207c29c4f05 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Fri, 12 Jul 2024 21:31:37 -0400 Subject: [PATCH 01/14] Fix for `CHROPT` We should read the _LAST_ character in the string, not the first. --- MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs index 95980f9b..c26b6d08 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs @@ -5310,7 +5310,8 @@ private void chropt() { var msgnum = GetParameter(0); - var outputValue = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgnum)[0]; + //Because the string is null terminated, we get the second to last character + var outputValue = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgnum)[^2]; #if DEBUG _logger.Debug($"({Module.ModuleIdentifier}) Retrieved option {msgnum} from {McvPointerDictionary[_currentMcvFile.Offset].FileName} (MCV Pointer: {_currentMcvFile}): {outputValue}"); From 0be8d96ac98ca90fcea4ab58e8957364f774f605 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Fri, 12 Jul 2024 21:40:20 -0400 Subject: [PATCH 02/14] Change `samto` to be `.StartsWith` Fixes GE Command Parsing, Tests still pass --- MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs index c26b6d08..5416a4a9 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs @@ -3177,7 +3177,7 @@ private void sameto() var string1Buffer = GetParameterString(0, true); var string2Buffer = GetParameterString(2, true); - Registers.AX = (ushort)(string2Buffer.Contains(string1Buffer, StringComparison.CurrentCultureIgnoreCase) ? 1 : 0); + Registers.AX = (ushort)(string2Buffer.StartsWith(string1Buffer, StringComparison.CurrentCultureIgnoreCase) ? 1 : 0); } /// From be1658bb5a02deaf6ce6227299b795455095a26b Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sat, 13 Jul 2024 11:38:41 -0400 Subject: [PATCH 03/14] Add Unit Test for TOKOPT --- .../ExportedModules/Majorbbs/tokopt_Tests.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs new file mode 100644 index 00000000..a107cf63 --- /dev/null +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs @@ -0,0 +1,62 @@ +using MBBSEmu.Memory; +using MBBSEmu.Module; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Xunit; + +namespace MBBSEmu.Tests.ExportedModules.Majorbbs +{ + public class tokopt_Tests : ExportedModuleTestBase + { + private const int TOKPT_ORDINAL = 602; + + [Theory] + [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "TEST", 0)] + [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "TEST2", 2)] + [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "Testing Multiple Words: TEST", 0)] + [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "Testing Multiple Words: TEST3", 3)] + public void stgopt_Test(string[] tokens, string valueToTest, ushort expectedResult) + { + //Reset State + Reset(); + + //Allocate Memory for each Token which will be passed in as params to the function + var tokenPointers = new List(); + foreach(var token in tokens) + { + var tokenPointer = + mbbsEmuMemoryCore.AllocateVariable($"TOKEN{tokenPointers.Count + 1}", (ushort)(token.Length + 1)); + mbbsEmuMemoryCore.SetArray(tokenPointer, Encoding.ASCII.GetBytes(token)); + + tokenPointers.Add(tokenPointer); + } + + + //Set Argument Values to be Passed In + var mcvPointer = (ushort)majorbbs.McvPointerDictionary.Allocate(new McvFile("TEST.MCV", + new Dictionary { { 0, Encoding.ASCII.GetBytes(valueToTest) } })); + + mbbsEmuMemoryCore.SetPointer("CURRENT-MCV", new FarPtr(0xFFFF, mcvPointer)); + + //Build Parameters List + var parameters = new List(); + parameters.Add(0); //Ordinal of MCV Value + //Add Token Pointers + foreach (var tokenPointer in tokenPointers) + { + parameters.Add(tokenPointer.Offset); + parameters.Add(tokenPointer.Segment); + } + //Terminate with Null Pointer + parameters.Add(0); + parameters.Add(0); + + //Execute Test + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, TOKPT_ORDINAL, parameters); + + //Verify Results + Assert.Equal(expectedResult, mbbsEmuCpuRegisters.AX); + } + } +} From 01370ad1a798b95924afdd33a410f5e4e5122fd2 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sat, 13 Jul 2024 11:39:27 -0400 Subject: [PATCH 04/14] Fix test name --- MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs index a107cf63..14ea8168 100644 --- a/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/tokopt_Tests.cs @@ -16,7 +16,7 @@ public class tokopt_Tests : ExportedModuleTestBase [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "TEST2", 2)] [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "Testing Multiple Words: TEST", 0)] [InlineData(new[] { "TEST1", "TEST2", "TEST3" }, "Testing Multiple Words: TEST3", 3)] - public void stgopt_Test(string[] tokens, string valueToTest, ushort expectedResult) + public void tokopt_Test(string[] tokens, string valueToTest, ushort expectedResult) { //Reset State Reset(); From a38a7013f0824d4fae124d69ff0a91543f28b1d0 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sat, 13 Jul 2024 11:40:07 -0400 Subject: [PATCH 05/14] Update NuGet Packages --- MBBSEmu.Tests/MBBSEmu.Tests.csproj | 8 ++++---- MBBSEmu/MBBSEmu.csproj | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MBBSEmu.Tests/MBBSEmu.Tests.csproj b/MBBSEmu.Tests/MBBSEmu.Tests.csproj index 60ddaf2f..ffd7b41d 100644 --- a/MBBSEmu.Tests/MBBSEmu.Tests.csproj +++ b/MBBSEmu.Tests/MBBSEmu.Tests.csproj @@ -20,13 +20,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/MBBSEmu/MBBSEmu.csproj b/MBBSEmu/MBBSEmu.csproj index 2029c7df..efb8e7c9 100644 --- a/MBBSEmu/MBBSEmu.csproj +++ b/MBBSEmu/MBBSEmu.csproj @@ -72,15 +72,15 @@ - + - + - + - + From f03652451c9a07b4f5523534ced44dde908334cf Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sat, 13 Jul 2024 14:03:49 -0400 Subject: [PATCH 06/14] Updated `TOKOPT` to work with ASCII Strings --- MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs index 5416a4a9..0c3b6b84 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs @@ -6114,7 +6114,7 @@ private void tokopt() { var msgNum = GetParameter(0); - var tokenList = new List(); + var tokenList = new List(); for (var i = 1; i < ushort.MaxValue; i += 2) { @@ -6126,7 +6126,7 @@ private void tokopt() try { - tokenList.Add(Module.Memory.GetString(messagePointer).ToArray()); + tokenList.Add(Encoding.ASCII.GetString(Module.Memory.GetString(messagePointer).ToArray())); } catch { @@ -6137,9 +6137,12 @@ private void tokopt() var message = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgNum); + //Get the last word from the message and cast it back to a byte array + var lastWord = Encoding.ASCII.GetString(message).Split(' ').Last(); + for (var i = 0; i < tokenList.Count; i++) { - if (message.SequenceEqual(tokenList[i])) + if (lastWord == tokenList[i]) { Registers.AX = (ushort)(i + 1); return; From 04806e9053de76089aa3a866bd09e232138e1061 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sat, 13 Jul 2024 14:05:06 -0400 Subject: [PATCH 07/14] Update PHAPI 0x10 to be `DosAllocSeg()` - Fixes GE --- MBBSEmu/HostProcess/ExportedModules/Phapi.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MBBSEmu/HostProcess/ExportedModules/Phapi.cs b/MBBSEmu/HostProcess/ExportedModules/Phapi.cs index 50d40126..6edf5da3 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Phapi.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Phapi.cs @@ -51,8 +51,8 @@ public ReadOnlySpan Invoke(ushort ordinal, bool offsetsOnly = false) DosRealIntr(); break; case 16: - //DosAllocRealSeg(); - DosCreatedAlias(); + DosAllocRealSeg(); + //DosCreatedAlias(); break; default: From ac312ccea5840285bed9a2dc6058ff8301d1c077 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sat, 13 Jul 2024 14:27:56 -0400 Subject: [PATCH 08/14] Handle `NULL` in `CHROPT` --- MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs index 0c3b6b84..2740403b 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs @@ -5311,7 +5311,16 @@ private void chropt() var msgnum = GetParameter(0); //Because the string is null terminated, we get the second to last character - var outputValue = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgnum)[^2]; + var inputValue = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgnum); + + //If it's just a null string + if (inputValue.Length <= 1) + { + Registers.AX = 0; + return; + } + + var outputValue = inputValue[^2]; #if DEBUG _logger.Debug($"({Module.ModuleIdentifier}) Retrieved option {msgnum} from {McvPointerDictionary[_currentMcvFile.Offset].FileName} (MCV Pointer: {_currentMcvFile}): {outputValue}"); From bf1af5db2d2f5ddc65597d53f492410389418d2d Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sun, 14 Jul 2024 10:24:09 -0400 Subject: [PATCH 09/14] 3OP IMUL Fixes - Fixes 3 Operand IMUL Operations - Unit Tests for IMUL --- MBBSEmu.Tests/CPU/IMUL_Tests.cs | 113 ++++++++++++++++++++++++++++++++ MBBSEmu.Tests/CPU/MUL_Tests.cs | 2 - MBBSEmu/CPU/CPUCore.cs | 6 +- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 MBBSEmu.Tests/CPU/IMUL_Tests.cs diff --git a/MBBSEmu.Tests/CPU/IMUL_Tests.cs b/MBBSEmu.Tests/CPU/IMUL_Tests.cs new file mode 100644 index 00000000..53634c31 --- /dev/null +++ b/MBBSEmu.Tests/CPU/IMUL_Tests.cs @@ -0,0 +1,113 @@ +using Iced.Intel; +using Xunit; +using static Iced.Intel.AssemblerRegisters; + +namespace MBBSEmu.Tests.CPU +{ + public class IMUL_Tests : CpuTestBase + { + [Theory] + [InlineData(1, -1, -1, false, false)] + [InlineData(-1, -1, 1, false, false)] + [InlineData(-127, -1, 127, false, false)] + [InlineData(127, -1, -127, false, false)] + public void IMUL_8_R8_Test(sbyte alValue, sbyte valueToMultiply, short expectedValue, bool carryFlag, + bool overflowFlag) + { + Reset(); + mbbsEmuCpuRegisters.AL = (byte)alValue; + mbbsEmuCpuRegisters.BL = (byte)valueToMultiply; + + var instructions = new Assembler(16); + instructions.imul(bl); + CreateCodeSegment(instructions); + + mbbsEmuCpuCore.Tick(); + + //Verify Results + Assert.Equal(expectedValue, (sbyte)mbbsEmuCpuRegisters.AL); + Assert.Equal(carryFlag, mbbsEmuCpuRegisters.CarryFlag); + Assert.Equal(overflowFlag, mbbsEmuCpuRegisters.OverflowFlag); + } + + [Theory] + [InlineData(1, -1, -1, false, false)] + [InlineData(-1, -1, 1, false, false)] + [InlineData(-127, -1, 127, false, false)] + [InlineData(127, -1, -127, false, false)] + [InlineData(short.MaxValue, -1, short.MinValue + 1, false, false)] + public void IMUL_16_R16_Test(short axValue, short valueToMultiply, short expectedValue, bool carryFlag, + bool overflowFlag) + { + Reset(); + mbbsEmuCpuRegisters.AX = (ushort)axValue; + mbbsEmuCpuRegisters.BX = (ushort)valueToMultiply; + + var instructions = new Assembler(16); + instructions.imul(bx); + CreateCodeSegment(instructions); + + mbbsEmuCpuCore.Tick(); + + //Verify Results + Assert.Equal(expectedValue, (short)mbbsEmuCpuRegisters.AX); + Assert.Equal(carryFlag, mbbsEmuCpuRegisters.CarryFlag); + Assert.Equal(overflowFlag, mbbsEmuCpuRegisters.OverflowFlag); + } + + + [Theory] + [InlineData(1, -1, -1, false, false)] + [InlineData(-1, -1, 1, false, false)] + [InlineData(-127, -1, 127, false, false)] + [InlineData(127, -1, -127, false, false)] + [InlineData(short.MaxValue, -1, short.MinValue + 1, false, false)] + public void IMUL_16_R16_3OP_Test(short bxValue, short valueToMultiply, short expectedValue, bool carryFlag, + bool overflowFlag) + { + Reset(); + mbbsEmuCpuRegisters.BX = (ushort)bxValue; + + var instructions = new Assembler(16); + //AX = BX * ValueToMultiply + instructions.imul(ax, bx, valueToMultiply); + CreateCodeSegment(instructions); + + mbbsEmuCpuCore.Tick(); + + //Verify Results + Assert.Equal(expectedValue, (short)mbbsEmuCpuRegisters.AX); + Assert.Equal(carryFlag, mbbsEmuCpuRegisters.CarryFlag); + Assert.Equal(overflowFlag, mbbsEmuCpuRegisters.OverflowFlag); + } + + [Theory] + [InlineData(1, -1, -1, false, false)] + [InlineData(-1, -1, 1, false, false)] + [InlineData(-127, -1, 127, false, false)] + [InlineData(127, -1, -127, false, false)] + [InlineData(short.MaxValue, -1, short.MinValue + 1, false, false)] + public void IMUL_16_M16_3OP_Test(short memoryValue, short valueToMultiply, short expectedValue, bool carryFlag, + bool overflowFlag) + { + Reset(); + + //Setup Memory + CreateDataSegment(new byte[ushort.MaxValue]); + mbbsEmuMemoryCore.SetWord(2,0, (ushort)memoryValue); + mbbsEmuCpuRegisters.DS = 2; + + var instructions = new Assembler(16); + //AX == DS:[0] * ValueToMultiply + instructions.imul(ax, __word_ptr[0], valueToMultiply); + CreateCodeSegment(instructions); + + mbbsEmuCpuCore.Tick(); + + //Verify Results + Assert.Equal(expectedValue, (short)mbbsEmuCpuRegisters.AX); + Assert.Equal(carryFlag, mbbsEmuCpuRegisters.CarryFlag); + Assert.Equal(overflowFlag, mbbsEmuCpuRegisters.OverflowFlag); + } + } +} diff --git a/MBBSEmu.Tests/CPU/MUL_Tests.cs b/MBBSEmu.Tests/CPU/MUL_Tests.cs index 0df1be4a..4b99c96a 100644 --- a/MBBSEmu.Tests/CPU/MUL_Tests.cs +++ b/MBBSEmu.Tests/CPU/MUL_Tests.cs @@ -1,6 +1,4 @@ using Iced.Intel; -using MBBSEmu.CPU; -using MBBSEmu.Extensions; using Xunit; using static Iced.Intel.AssemblerRegisters; diff --git a/MBBSEmu/CPU/CPUCore.cs b/MBBSEmu/CPU/CPUCore.cs index 29ff9c12..820fc732 100644 --- a/MBBSEmu/CPU/CPUCore.cs +++ b/MBBSEmu/CPU/CPUCore.cs @@ -2737,9 +2737,9 @@ private void Op_Imul_3operand() { var operand2 = _currentOperationSize switch { - 1 => GetOperandValueUInt8(_currentInstruction.Op1Kind, EnumOperandType.Destination), - 2 => GetOperandValueUInt16(_currentInstruction.Op1Kind, EnumOperandType.Destination), - 4 => GetOperandValueUInt32(_currentInstruction.Op1Kind, EnumOperandType.Destination), + 1 => GetOperandValueUInt8(_currentInstruction.Op1Kind, EnumOperandType.Source), + 2 => GetOperandValueUInt16(_currentInstruction.Op1Kind, EnumOperandType.Source), + 4 => GetOperandValueUInt32(_currentInstruction.Op1Kind, EnumOperandType.Source), _ => throw new Exception("Unsupported Operation Size") }; From 777cad90ee7f81e4de4eccdd886fd423d235f06d Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sun, 14 Jul 2024 11:28:48 -0400 Subject: [PATCH 10/14] Clear Compiler Warnings --- MBBSEmu/Module/CrashReport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MBBSEmu/Module/CrashReport.cs b/MBBSEmu/Module/CrashReport.cs index 04d03dfd..27a4ae7d 100644 --- a/MBBSEmu/Module/CrashReport.cs +++ b/MBBSEmu/Module/CrashReport.cs @@ -65,7 +65,7 @@ public string Save(string fileName = "") { crashReportVariables.Add(_moduleToReport.Memory.GetInstruction(_registers.CS, _registers.IP).ToString()); } - catch (Exception e) + catch { crashReportVariables.Add("Exception retrieving CPU Instruction"); } From e0981ed40a17decbdeb19604b62f286a2675c15c Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sun, 14 Jul 2024 15:00:46 -0400 Subject: [PATCH 11/14] Enhance IMUL 3 Operand - Enhanced 3 Operand `IMUL` to use width specific helper functions - Properly Set OF/CF on Overflow --- MBBSEmu.Tests/CPU/IMUL_Tests.cs | 3 ++ MBBSEmu/CPU/CPUCore.cs | 52 ++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/MBBSEmu.Tests/CPU/IMUL_Tests.cs b/MBBSEmu.Tests/CPU/IMUL_Tests.cs index 53634c31..f2373ee2 100644 --- a/MBBSEmu.Tests/CPU/IMUL_Tests.cs +++ b/MBBSEmu.Tests/CPU/IMUL_Tests.cs @@ -62,6 +62,7 @@ public void IMUL_16_R16_Test(short axValue, short valueToMultiply, short expecte [InlineData(-127, -1, 127, false, false)] [InlineData(127, -1, -127, false, false)] [InlineData(short.MaxValue, -1, short.MinValue + 1, false, false)] + [InlineData(short.MaxValue, -2, 2, true, true)] public void IMUL_16_R16_3OP_Test(short bxValue, short valueToMultiply, short expectedValue, bool carryFlag, bool overflowFlag) { @@ -83,10 +84,12 @@ public void IMUL_16_R16_3OP_Test(short bxValue, short valueToMultiply, short exp [Theory] [InlineData(1, -1, -1, false, false)] + [InlineData(5, 10, 50, false, false)] [InlineData(-1, -1, 1, false, false)] [InlineData(-127, -1, 127, false, false)] [InlineData(127, -1, -127, false, false)] [InlineData(short.MaxValue, -1, short.MinValue + 1, false, false)] + [InlineData(short.MaxValue, -2, 2, true, true)] public void IMUL_16_M16_3OP_Test(short memoryValue, short valueToMultiply, short expectedValue, bool carryFlag, bool overflowFlag) { diff --git a/MBBSEmu/CPU/CPUCore.cs b/MBBSEmu/CPU/CPUCore.cs index 820fc732..372892fc 100644 --- a/MBBSEmu/CPU/CPUCore.cs +++ b/MBBSEmu/CPU/CPUCore.cs @@ -360,9 +360,9 @@ public void Tick() { _showDebug = true; //Set to log Register values to console after execution _logger.Debug($"{Registers.CS:X4}:{_currentInstruction.IP16:X4} {_currentInstruction}"); - foreach(var l in Registers.ToString().Split("\n")) + foreach (var l in Registers.ToString().Split("\n")) _logger.Debug(l); - for(var i = 0; i < 8; i++) + for (var i = 0; i < 8; i++) _logger.Debug($"FPU[{i}]: {FpuStack[i]} {(i == Registers.Fpu.GetStackTop() ? " <--" : string.Empty)}"); } else @@ -954,6 +954,7 @@ private uint GetOperandValueUInt32(OpKind opKind, EnumOperandType operandType) } } + /// /// /// This is a helper method which takes the resulting value from GetOperandValueUInt64 and signs it depending on the underlying /// OpKind and MemorySize @@ -983,7 +984,6 @@ private long GetOperandValueInt64(OpKind opKind) MemorySize.Int32 => (int)value, MemorySize.UInt32 => (uint)value, MemorySize.Int64 => (long)value, - MemorySize.UInt64 => (long)value, _ => throw new Exception($"Invalid Operand Size: {_currentInstruction.MemorySize}") }, _ => throw new Exception($"Unsupported OpKind: {opKind}") @@ -1733,7 +1733,7 @@ private byte Op_Rcl_8() } //For 1 Bit Rotations, we evaluate Overflow - if(source == 1) + if (source == 1) Registers.OverflowFlag = result.IsBitSet(7) ^ Registers.CarryFlag; return result; @@ -2749,16 +2749,54 @@ private void Op_Imul_3operand() 2 => GetOperandValueUInt16(_currentInstruction.Op2Kind, EnumOperandType.Source), 4 => GetOperandValueUInt32(_currentInstruction.Op2Kind, EnumOperandType.Source), _ => throw new Exception("Unsupported Operation Size") - }; uint result; unchecked { - result = operand2 * operand3; + case 1: + Op_Imul_3operand_8(); + return; + case 2: + Op_Imul_3operand_16(); + return; + case 4: + Op_Imul_3operand_32(); + return; + default: + throw new Exception("Unsupported Operation Size"); } + } WriteToDestination(result); + var operand3 = GetOperandValueInt8(_currentInstruction.Op2Kind, EnumOperandType.Source); } + [MethodImpl(OpcodeSubroutineCompilerOptimizations)] + private void Op_Imul_3operand_16() + { + var operand2 = GetOperandValueInt16(_currentInstruction.Op1Kind, EnumOperandType.Source); + var operand3 = GetOperandValueInt16(_currentInstruction.Op2Kind, EnumOperandType.Source); + var result = operand2 * operand3; + + //Set CarryFlag and OverflowFlag if the result is too large to fit in the destination + Registers.OverflowFlag = Registers.CarryFlag = result is > short.MaxValue or < short.MinValue; + + WriteToDestination((ushort)result); + } + + [MethodImpl(OpcodeSubroutineCompilerOptimizations)] + private void Op_Imul_3operand_32() + { + var operand2 = GetOperandValueInt32(_currentInstruction.Op1Kind, EnumOperandType.Source); + var operand3 = GetOperandValueInt32(_currentInstruction.Op2Kind, EnumOperandType.Source); + long result = operand2 * operand3; + + //Set CarryFlag and OverflowFlag if the result is too large to fit in the destination + Registers.OverflowFlag = Registers.CarryFlag = result is > int.MaxValue or < int.MinValue; + + WriteToDestination((uint)result); + } + + [MethodImpl(OpcodeCompilerOptimizations)] private void Op_Mul() { @@ -4041,7 +4079,7 @@ private byte Op_Ror_8() Registers.CarryFlag = result.IsNegative(); //If Bits 7 & 6 are not the same, then we overflowed for 1 bit rotations - if(source == 1) + if (source == 1) Registers.OverflowFlag = result.IsBitSet(7) != result.IsBitSet(6); return result; From 2e8566041cf3ab11996a9ca1f80a020c7a9efd06 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sun, 14 Jul 2024 15:01:58 -0400 Subject: [PATCH 12/14] Missing Code --- MBBSEmu/CPU/CPUCore.cs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/MBBSEmu/CPU/CPUCore.cs b/MBBSEmu/CPU/CPUCore.cs index 372892fc..22ddf700 100644 --- a/MBBSEmu/CPU/CPUCore.cs +++ b/MBBSEmu/CPU/CPUCore.cs @@ -955,6 +955,7 @@ private uint GetOperandValueUInt32(OpKind opKind, EnumOperandType operandType) } /// + /// OpKind and MemorySize /// /// This is a helper method which takes the resulting value from GetOperandValueUInt64 and signs it depending on the underlying /// OpKind and MemorySize @@ -2735,22 +2736,7 @@ private void Op_Imul_1operand() [MethodImpl(OpcodeSubroutineCompilerOptimizations)] private void Op_Imul_3operand() { - var operand2 = _currentOperationSize switch - { - 1 => GetOperandValueUInt8(_currentInstruction.Op1Kind, EnumOperandType.Source), - 2 => GetOperandValueUInt16(_currentInstruction.Op1Kind, EnumOperandType.Source), - 4 => GetOperandValueUInt32(_currentInstruction.Op1Kind, EnumOperandType.Source), - _ => throw new Exception("Unsupported Operation Size") - }; - - var operand3 = _currentOperationSize switch - { - 1 => GetOperandValueUInt8(_currentInstruction.Op2Kind, EnumOperandType.Source), - 2 => GetOperandValueUInt16(_currentInstruction.Op2Kind, EnumOperandType.Source), - 4 => GetOperandValueUInt32(_currentInstruction.Op2Kind, EnumOperandType.Source), - _ => throw new Exception("Unsupported Operation Size") - uint result; - unchecked + switch (_currentOperationSize) { case 1: Op_Imul_3operand_8(); @@ -2766,8 +2752,12 @@ private void Op_Imul_3operand() } } - WriteToDestination(result); + [MethodImpl(OpcodeSubroutineCompilerOptimizations)] + private void Op_Imul_3operand_8() + { + var operand2 = GetOperandValueInt8(_currentInstruction.Op1Kind, EnumOperandType.Source); var operand3 = GetOperandValueInt8(_currentInstruction.Op2Kind, EnumOperandType.Source); + //Set CarryFlag and OverflowFlag if the result is too large to fit in the destination } [MethodImpl(OpcodeSubroutineCompilerOptimizations)] From 2155df3b0a51fbcef6064539324477e79f1eec13 Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Sun, 14 Jul 2024 15:02:40 -0400 Subject: [PATCH 13/14] Missing Code (Again?) --- MBBSEmu/CPU/CPUCore.cs | 87 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/MBBSEmu/CPU/CPUCore.cs b/MBBSEmu/CPU/CPUCore.cs index 22ddf700..d54d7be4 100644 --- a/MBBSEmu/CPU/CPUCore.cs +++ b/MBBSEmu/CPU/CPUCore.cs @@ -955,7 +955,89 @@ private uint GetOperandValueUInt32(OpKind opKind, EnumOperandType operandType) } /// + /// This is a helper method which takes the resulting value from GetOperandValueUInt8 and signs it depending on the underlying /// OpKind and MemorySize + /// + /// + /// + [MethodImpl(OpcodeCompilerOptimizations)] + private sbyte GetOperandValueInt8(OpKind opKind, EnumOperandType operandType) + { + var value = GetOperandValueUInt8(opKind, operandType); + + return opKind switch + { + OpKind.Immediate8 => (sbyte)value, + OpKind.Register => (sbyte)value, + OpKind.Memory => _currentInstruction.MemorySize switch + { + MemorySize.Int8 => (sbyte)value, + _ => throw new Exception($"Invalid Operand Size: {_currentInstruction.MemorySize}") + }, + _ => throw new Exception($"Unsupported OpKind: {opKind}") + }; + } + + /// + /// This is a helper method which takes the resulting value from GetOperandValueUInt16 and signs it depending on the underlying + /// OpKind and MemorySize + /// + /// + /// + [MethodImpl(OpcodeCompilerOptimizations)] + private short GetOperandValueInt16(OpKind opKind, EnumOperandType operandType) + { + var value = GetOperandValueUInt16(opKind, operandType); + + return opKind switch + { + OpKind.Immediate8 => (sbyte)value, + OpKind.Immediate16 => (short)value, + OpKind.Immediate8to16 => (short)value, + OpKind.Register => (short)value, + OpKind.Memory => _currentInstruction.MemorySize switch + { + MemorySize.Int8 => (sbyte)value, + MemorySize.UInt8 => (byte)value, + MemorySize.Int16 => (short)value, + _ => throw new Exception($"Invalid Operand Size: {_currentInstruction.MemorySize}") + }, + _ => throw new Exception($"Unsupported OpKind: {opKind}") + }; + } + + /// + /// This is a helper method which takes the resulting value from GetOperandValueUInt64 and signs it depending on the underlying + /// OpKind and MemorySize + /// + /// + /// + [MethodImpl(OpcodeCompilerOptimizations)] + private int GetOperandValueInt32(OpKind opKind, EnumOperandType operandType) + { + var value = GetOperandValueUInt32(opKind, operandType); + + return opKind switch + { + OpKind.Immediate8 => (sbyte)value, + OpKind.Immediate16 => (short)value, + OpKind.Immediate8to16 => (short)value, + OpKind.Immediate32 => (int)value, + OpKind.Immediate8to32 => (int)value, + OpKind.Register => (int)value, + OpKind.Memory => _currentInstruction.MemorySize switch + { + MemorySize.Int8 => (sbyte)value, + MemorySize.UInt8 => (byte)value, + MemorySize.Int16 => (short)value, + MemorySize.UInt16 => (ushort)value, + MemorySize.Int32 => (int)value, + _ => throw new Exception($"Invalid Operand Size: {_currentInstruction.MemorySize}") + }, + _ => throw new Exception($"Unsupported OpKind: {opKind}") + }; + } + /// /// This is a helper method which takes the resulting value from GetOperandValueUInt64 and signs it depending on the underlying /// OpKind and MemorySize @@ -2757,7 +2839,12 @@ private void Op_Imul_3operand_8() { var operand2 = GetOperandValueInt8(_currentInstruction.Op1Kind, EnumOperandType.Source); var operand3 = GetOperandValueInt8(_currentInstruction.Op2Kind, EnumOperandType.Source); + var result = operand2 * operand3; + //Set CarryFlag and OverflowFlag if the result is too large to fit in the destination + Registers.OverflowFlag = Registers.CarryFlag = result is > sbyte.MaxValue or < sbyte.MinValue; + + WriteToDestination((byte)result); } [MethodImpl(OpcodeSubroutineCompilerOptimizations)] From dfc6c75f1119703b7b16d7e3e4dcbda4995a283b Mon Sep 17 00:00:00 2001 From: "Eric P. Nusbaum" Date: Mon, 22 Jul 2024 20:31:01 -0400 Subject: [PATCH 14/14] Fix for IMUL and PR Fixes - Verified Unit Tests + Results against DOSBox --- MBBSEmu.Tests/CPU/IMUL_Tests.cs | 31 +++++++++----- MBBSEmu/CPU/CPUCore.cs | 75 ++++++++++++++++++++++++++++----- 2 files changed, 84 insertions(+), 22 deletions(-) diff --git a/MBBSEmu.Tests/CPU/IMUL_Tests.cs b/MBBSEmu.Tests/CPU/IMUL_Tests.cs index f2373ee2..26e63ce9 100644 --- a/MBBSEmu.Tests/CPU/IMUL_Tests.cs +++ b/MBBSEmu.Tests/CPU/IMUL_Tests.cs @@ -7,11 +7,14 @@ namespace MBBSEmu.Tests.CPU public class IMUL_Tests : CpuTestBase { [Theory] - [InlineData(1, -1, -1, false, false)] - [InlineData(-1, -1, 1, false, false)] - [InlineData(-127, -1, 127, false, false)] - [InlineData(127, -1, -127, false, false)] - public void IMUL_8_R8_Test(sbyte alValue, sbyte valueToMultiply, short expectedValue, bool carryFlag, + [InlineData(127, 127, 1, 63, true, true)] + [InlineData(-1, -1, 1, 0, false, false)] + [InlineData(-127, -1, 127, 0, false, false)] + [InlineData(-127, 2, 2, -1, true, true)] + [InlineData(-127, -127, 1, 63, true, true)] + [InlineData(127, -1, -127, -1, false, false)] + [InlineData(5, 5, 25, 0, false, false)] + public void IMUL_8_R8_Test(sbyte alValue, sbyte valueToMultiply, sbyte expectedALValue, sbyte expectedAHValue, bool carryFlag, bool overflowFlag) { Reset(); @@ -25,17 +28,23 @@ public void IMUL_8_R8_Test(sbyte alValue, sbyte valueToMultiply, short expectedV mbbsEmuCpuCore.Tick(); //Verify Results - Assert.Equal(expectedValue, (sbyte)mbbsEmuCpuRegisters.AL); + Assert.Equal(expectedALValue, (sbyte)mbbsEmuCpuRegisters.AL); + Assert.Equal(expectedAHValue, (sbyte)mbbsEmuCpuRegisters.AH); Assert.Equal(carryFlag, mbbsEmuCpuRegisters.CarryFlag); Assert.Equal(overflowFlag, mbbsEmuCpuRegisters.OverflowFlag); } [Theory] - [InlineData(1, -1, -1, false, false)] - [InlineData(-1, -1, 1, false, false)] - [InlineData(-127, -1, 127, false, false)] - [InlineData(127, -1, -127, false, false)] - [InlineData(short.MaxValue, -1, short.MinValue + 1, false, false)] + [InlineData(32767, 1, 32767, false, false)] // Max positive * 1 + [InlineData(-32768, 1, -32768, false, false)] // Max negative * 1 + [InlineData(100, 100, 10000, false, false)] // Positive * Positive + [InlineData(-100, -100, 10000, false, false)] // Negative * Negative + [InlineData(100, -100, -10000, false, false)] // Positive * Negative + [InlineData(32767, 2, -2, true, true)] // Positive overflow case + [InlineData(-32768, 2, 0, true, true)] // Negative overflow case + [InlineData(0, 32767, 0, false, false)] // Zero * Positive + [InlineData(0, -32768, 0, false, false)] // Zero * Negative + [InlineData(0, 0, 0, false, false)] // Zero * Zero public void IMUL_16_R16_Test(short axValue, short valueToMultiply, short expectedValue, bool carryFlag, bool overflowFlag) { diff --git a/MBBSEmu/CPU/CPUCore.cs b/MBBSEmu/CPU/CPUCore.cs index d54d7be4..744bd489 100644 --- a/MBBSEmu/CPU/CPUCore.cs +++ b/MBBSEmu/CPU/CPUCore.cs @@ -1008,6 +1008,7 @@ private short GetOperandValueInt16(OpKind opKind, EnumOperandType operandType) /// /// This is a helper method which takes the resulting value from GetOperandValueUInt64 and signs it depending on the underlying + /// This is a helper method which takes the resulting value from GetOperandValueUInt32 and signs it depending on the underlying /// OpKind and MemorySize /// /// @@ -2799,20 +2800,72 @@ private void Op_Imul() [MethodImpl(OpcodeSubroutineCompilerOptimizations)] private void Op_Imul_1operand() { - var operand2 = Registers.AX; - var operand3 = _currentOperationSize switch - { - 1 => GetOperandValueUInt8(_currentInstruction.Op0Kind, EnumOperandType.Destination), - 2 => GetOperandValueUInt16(_currentInstruction.Op0Kind, EnumOperandType.Destination), - _ => throw new Exception("Unsupported Operation Size") - }; - ushort result; - unchecked + switch (_currentOperationSize) { - result = (ushort)(operand2 * operand3); + case 1: + Op_Imul_1operand_8(); + return; + case 2: + Op_Imul_1operand_16(); + return; + case 4: + Op_Imul_1operand_32(); + return; + default: + throw new Exception("Unsupported Operation Size"); } + } + + [MethodImpl(OpcodeSubroutineCompilerOptimizations)] + private void Op_Imul_1operand_8() + { + // Get the values and ensure they are sign-extended properly + var operand2 = (sbyte)Registers.AL; + var operand3 = GetOperandValueInt8(_currentInstruction.Op0Kind, EnumOperandType.Destination); + + // Perform the multiplication + var result = (short)operand2 * operand3; + + // Set AH to the high 8 bits and AL to the low 8 bits of the result + Registers.AH = (byte)(result >> 8); + Registers.AL = (byte)(result & 0xFF); - Registers.AX = result; + // Calculate the flags + Registers.OverflowFlag = Registers.CarryFlag = (short)Registers.AL.ToUshortSignExtended() != result; + } + + [MethodImpl(OpcodeSubroutineCompilerOptimizations)] + private void Op_Imul_1operand_16() + { + // Get the value from AX and the operand, ensuring they are sign-extended properly + var operand2 = (short)Registers.AX; + var operand3 = GetOperandValueInt16(_currentInstruction.Op0Kind, EnumOperandType.Destination); + + // Perform the multiplication + var result = operand2 * operand3; + + // Set DX to the high 16 bits and AX to the low 16 bits of the result + Registers.DX = (ushort)(result >> 16); + Registers.AX = (ushort)(result & 0xFFFF); + + // Calculate the flags + Registers.OverflowFlag = Registers.CarryFlag = Registers.AX.ToUintSignExtended() != (uint)result; + } + + [MethodImpl(OpcodeSubroutineCompilerOptimizations)] + private void Op_Imul_1operand_32() + { + var operand2 = Registers.EAX; + var operand3 = GetOperandValueInt32(_currentInstruction.Op0Kind, EnumOperandType.Destination); + long result = operand2 * operand3; + + + //EDX is High 32-bits, EAX is low 32-bits + Registers.EDX = (uint)(result >> 32); + Registers.EAX = (uint)(result & 0xFFFFFFFF); + + //Set CarryFlag and OverflowFlag if the result is too large to fit in the destination + Registers.OverflowFlag = Registers.CarryFlag = Register.EDX != 0; } [MethodImpl(OpcodeSubroutineCompilerOptimizations)]