diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/nctime_Tests.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/nctime_Tests.cs index c7557efc..16b458d9 100644 --- a/MBBSEmu.Tests/ExportedModules/Majorbbs/nctime_Tests.cs +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/nctime_Tests.cs @@ -31,13 +31,14 @@ public void nctime_Test(int hour, int minutes, int seconds, string expectedTime, //Execute Test ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, NCTIME_ORDINAL, new List { (ushort) packedTime }); + var resultPointer = mbbsEmuCpuRegisters.GetPointer(); //Put Garbage After it to ensure the null terminator catches var garbagePointer = mbbsEmuMemoryCore.AllocateVariable("GARBAGE", 10); mbbsEmuMemoryCore.SetArray(garbagePointer, Encoding.ASCII.GetBytes(new string('C', 10))); //Verify Results - Assert.Equal(expectedTime, Encoding.ASCII.GetString(mbbsEmuMemoryCore.GetString("NCTIME"))); + Assert.Equal(expectedTime, Encoding.ASCII.GetString(mbbsEmuMemoryCore.GetString(resultPointer))); } } } diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/rawmsg_Tests.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/rawmsg_Tests.cs new file mode 100644 index 00000000..ab789057 --- /dev/null +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/rawmsg_Tests.cs @@ -0,0 +1,66 @@ +using MBBSEmu.Memory; +using MBBSEmu.Module; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace MBBSEmu.Tests.ExportedModules.Majorbbs +{ + public class rawmsg_Tests : ExportedModuleTestBase + { + private const int RAWMSG_ORDINAL = 487; + + [Theory] + [InlineData(0, "Normal")] + [InlineData(1, "")] + [InlineData(2, "123456")] + [InlineData(3, "--==---")] + [InlineData(4, "!@)#!*$")] + public void rawmsg_Test(ushort ordinal, string msgValue) + { + //Reset State + Reset(); + + //Build a Test Dictionary containing all incorrect values + var incorrectValues = new Dictionary + { + { 0, "INCORRECT"u8.ToArray() }, + { 1, "INCORRECT"u8.ToArray() }, + { 2, "INCORRECT"u8.ToArray() }, + { 3, "INCORRECT"u8.ToArray() }, + { 4, "INCORRECT"u8.ToArray() }, + { 5, "INCORRECT"u8.ToArray() }, + { 6, "INCORRECT"u8.ToArray() }, + { 7, "INCORRECT"u8.ToArray() }, + { 8, "INCORRECT"u8.ToArray() }, + { 9, "INCORRECT"u8.ToArray() }, + { 10, "INCORRECT"u8.ToArray() }, + { 11, "INCORRECT"u8.ToArray() }, + { 12, "INCORRECT"u8.ToArray() }, + { 13, "INCORRECT"u8.ToArray() }, + { 14, "INCORRECT"u8.ToArray() }, + { 15, "INCORRECT"u8.ToArray() }, + { 16, "INCORRECT"u8.ToArray() }, + { 17, "INCORRECT"u8.ToArray() }, + { 18, "INCORRECT"u8.ToArray() }, + { 19, "INCORRECT"u8.ToArray() }, + { 20, "INCORRECT"u8.ToArray() } + }; + + //Set Input Ordinal In Dictionary to Correct Value + incorrectValues[ordinal] = Encoding.ASCII.GetBytes(msgValue); + + //Set Argument Values to be Passed In + var mcvPointer = (ushort)majorbbs.McvPointerDictionary.Allocate(new McvFile("TEST.MCV", + incorrectValues)); + + mbbsEmuMemoryCore.SetPointer("CURRENT-MCV", new FarPtr(0xFFFF, mcvPointer)); + + //Execute Test + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, RAWMSG_ORDINAL, new List { ordinal }); + + //Verify Results + Assert.Equal(msgValue, Encoding.ASCII.GetString(mbbsEmuMemoryCore.GetString(mbbsEmuCpuRegisters.DX, mbbsEmuCpuRegisters.AX, true))); + } + } +} \ No newline at end of file diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/stpans_Tests.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/stpans_Tests.cs index 8025b625..aa14c277 100644 --- a/MBBSEmu.Tests/ExportedModules/Majorbbs/stpans_Tests.cs +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/stpans_Tests.cs @@ -39,6 +39,7 @@ public void STPANS_Test(string inputString, string expectedString) //Execute Test ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, STPANS_ORDINAL, new List { stringPointer }); + var resultPointer = mbbsEmuCpuRegisters.GetPointer(); //Verify Results Assert.Equal(expectedString, diff --git a/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs b/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs index 0accaff1..f8a927ca 100644 --- a/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs +++ b/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs @@ -56,8 +56,7 @@ public LeadingNumberFromStringResult() /// it lives in. /// private protected PointerDictionary ChannelDictionary; - - + /// /// Pointers to files opened using FOPEN /// @@ -84,6 +83,13 @@ public LeadingNumberFromStringResult() /// private protected ushort ChannelNumber; + /// + /// Dictionary that will hold the ordinals used for local variables. This allows methods that + /// return pointers to result values in memory (Strings, etc.) to have unique addresses so that + /// results aren't overwritten in the same execution cycle. + /// + private protected readonly Dictionary LocalVariableOrdinalDictionary = new(); + //Constants private protected static readonly char[] SSCANF_SEPARATORS = { ' ', ',', '\r', '\n', '\0', ':' }; private protected static readonly char[] PRINTF_SPECIFIERS = { 'c', 'd', 's', 'e', 'E', 'f', 'g', 'G', 'o', 'x', 'X', 'u', 'i', 'P', 'N', '%' }; @@ -122,6 +128,42 @@ private protected ExportedModuleBase(IClock clock, IMessageLogger logger, AppSet McvPointerDictionary = new PointerDictionary(); } + /// + /// Resets Local Variable Ordinals all back to Zero + /// + private protected void ResetLocalVariableOrdinals() + { + foreach (var k in LocalVariableOrdinalDictionary.Keys) + { + LocalVariableOrdinalDictionary[k] = 0; + } + } + + /// + /// Retrieves a Local Variable Ordinal based on the ordinal name + /// + /// This is tracked in a dictionary with each value incremented on each get + /// + /// + /// + private protected int GetLocalVariableOrdinal(string variableName) + { + if (!LocalVariableOrdinalDictionary.ContainsKey(variableName)) + LocalVariableOrdinalDictionary[variableName] = 0; + + return LocalVariableOrdinalDictionary[variableName]++; + } + + /// + /// Creates a distinct variable name string to use internally when allocating variables + /// to store return results for methods. This gives each method call a distinct memory + /// pointer to be used to store the results based on the ordinal. + /// + /// + /// String containing the variable name and ordinal (ie: "MyVar1") + private protected string GetLocalVariableName(string variableName) => + $"{variableName}{GetLocalVariableOrdinal(variableName)}"; + /// /// Sets the parameter by ordinal passed into the routine /// diff --git a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs index 2740403b..deba0a92 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs @@ -308,6 +308,7 @@ public void SetRegisters(ICpuRegisters registers) /// public void SetState(ushort channelNumber) { + ResetLocalVariableOrdinals(); ChannelNumber = channelNumber; _previousMcvFile.Clear(); @@ -1897,7 +1898,7 @@ private void l2as() var outputValue = $"{highByte << 16 | lowByte}\0"; //Pre-allocate space for the maximum number of characters for a ulong - var variablePointer = Module.Memory.GetOrAllocateVariablePointer("L2AS", 0xFF); + var variablePointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("L2AS"), 0x20); //32 Byte Buffer Module.Memory.SetArray(variablePointer, Encoding.Default.GetBytes(outputValue)); @@ -2379,7 +2380,7 @@ private void ncdate() var month = (packedDate >> 5) & 0x000F; var day = packedDate & 0x001F; var outputDate = $"{month:D2}/{day:D2}/{year % 100}\0"; - var variablePointer = Module.Memory.GetOrAllocateVariablePointer("NCDATE", (ushort)outputDate.Length); + var variablePointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("NCDATE"), (ushort)outputDate.Length); Module.Memory.SetArray(variablePointer.Segment, variablePointer.Offset, Encoding.Default.GetBytes(outputDate)); @@ -2885,7 +2886,7 @@ private void scnmdf() var lineprefix = Encoding.ASCII.GetString(lineprefixBytes); //Setup Host Memory Variables Pointer - var variablePointer = Module.Memory.GetOrAllocateVariablePointer("SCNMDF", 0xFF); + var variablePointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("SCNMDF"), 0xFF); var recordFound = false; foreach (var line in File.ReadAllLines(Path.Combine(Module.ModulePath, mdfName))) @@ -2894,7 +2895,7 @@ private void scnmdf() { var result = Encoding.ASCII.GetBytes(line.Split(':')[1] + "\0"); - if (result.Length > 256) + if (result.Length > 0xFF) throw new OverflowException("SCNMDF result is > 256 bytes"); Module.Memory.SetArray(variablePointer.Segment, variablePointer.Offset, result); @@ -3789,7 +3790,7 @@ private void getmsg() { var msgnum = GetParameter(0); - var variablePointer = Module.Memory.GetOrAllocateVariablePointer("GETMSG", 0x1000); + var variablePointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("GETMSG"), 0x1000); var outputValue = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgnum); if (outputValue.Length > 0x1000) @@ -4238,7 +4239,7 @@ private void nctime() unpackedMinutes, unpackedSeconds); var timeString = $"{unpackedTime.ToString("HH:mm:ss")}\0"; - var variablePointer = Module.Memory.GetOrAllocateVariablePointer("NCTIME", (ushort)timeString.Length); + var variablePointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("NCTIME"), (ushort)timeString.Length); Module.Memory.SetArray(variablePointer.Segment, variablePointer.Offset, Encoding.Default.GetBytes(timeString)); @@ -5043,7 +5044,7 @@ private void stpans() var stringToStripPointer = GetParameterPointer(0); var inputString = Module.Memory.GetString(stringToStripPointer); - Module.Memory.GetOrAllocateVariablePointer("STPANS", 1920); //Max Screen Size of 80x24 + var resultPointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("STPANS"), 1920); //Max Screen Size of 80x24 if (inputString.Length > 1920) { @@ -5099,13 +5100,13 @@ private void stpans() } } - Module.Memory.SetArray("STPANS", Encoding.ASCII.GetBytes(cleanedStringBuilder.ToString())); + Module.Memory.SetArray(resultPointer, Encoding.ASCII.GetBytes(cleanedStringBuilder.ToString())); #if DEBUG _logger.Debug($"({Module.ModuleIdentifier}) Ignoring, not stripping ANSI"); #endif - Registers.SetPointer(Module.Memory.GetVariablePointer("STPANS")); + Registers.SetPointer(resultPointer); } /// @@ -5286,7 +5287,7 @@ private void rawmsg() { var msgnum = GetParameter(0); - var variablePointer = Module.Memory.GetOrAllocateVariablePointer("RAWMSG", 0x1000); + var variablePointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("RAWMSG"), 0x1000); var outputValue = McvPointerDictionary[_currentMcvFile.Offset].GetString(msgnum); if (outputValue.Length > 0x1000) @@ -6331,7 +6332,7 @@ private void msgscan() return; } - var msgScanResultPointer = Module.Memory.GetOrAllocateVariablePointer("MSGSCAN", 0x1000); + var msgScanResultPointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("MSGSCAN"), 0x1000); Module.Memory.SetArray(msgScanResultPointer, new byte[0x1000]); //Zero it out Module.Memory.SetArray(msgScanResultPointer, msgVariableValue); //Write @@ -6417,7 +6418,7 @@ private void getenv() { var name = GetParameterFilename(0); - var resultPointer = Module.Memory.GetOrAllocateVariablePointer("GETENV", 0xFF); + var resultPointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("GETENV"), 0xFF); switch (name) { @@ -7519,7 +7520,7 @@ private void ul2as() var outputValue = $"{(uint)(highByte << 16 | lowByte)}\0"; - var resultPointer = Module.Memory.GetOrAllocateVariablePointer("UL2AS", 0xF); + var resultPointer = Module.Memory.GetOrAllocateVariablePointer(GetLocalVariableName("UL2AS"), 0x20); //32 Byte Buffer Module.Memory.SetArray(resultPointer, Encoding.Default.GetBytes(outputValue));