diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/FileTestBase.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/FileTestBase.cs index 40539908..7ed82c1c 100644 --- a/MBBSEmu.Tests/ExportedModules/Majorbbs/FileTestBase.cs +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/FileTestBase.cs @@ -20,6 +20,13 @@ public class FileTestBase : ExportedModuleTestBase, IDisposable protected const int CLOSE_ORDINAL = 110; protected const int FILELENGTH_ORDINAL = 211; + protected const int FPUTC_ORDINAL = 227; + protected const int FGETC_ORDINAL = 19; + protected const int UNGETC_ORDINAL = 615; + protected const int FSEEK_ORDINAL = 266; + protected const int FPUTS_ORDINAL = 1125; + protected const int FGETS_ORDINAL = 210; + protected FileTestBase() : base(Path.Join(Path.GetTempPath(), $"mbbsemu{RANDOM.Next()}")) { Directory.CreateDirectory(mbbsModule.ModulePath); @@ -154,6 +161,81 @@ protected int filelength(ushort fd) return mbbsEmuCpuRegisters.GetLong(); } + protected ushort fgetc(FarPtr srcPtr) + { + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FGETC_ORDINAL, new List + { + srcPtr + }); + + return mbbsEmuCpuRegisters.AX; + } + + protected ushort ungetc(ushort ungetChar, FarPtr srcPtr) + { + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, UNGETC_ORDINAL, new List + { + ungetChar, + srcPtr.Offset, + srcPtr.Segment + }); + + return mbbsEmuCpuRegisters.AX; + } + + protected ushort fputc(ushort putChar, FarPtr srcPtr) + { + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FPUTC_ORDINAL, new List + { + putChar, + srcPtr.Offset, + srcPtr.Segment + }); + + return mbbsEmuCpuRegisters.AX; + } + + protected FarPtr fgets(FarPtr putStringPtr, ushort numChars, FarPtr srcPtr) + { + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FGETS_ORDINAL, new List + { + putStringPtr.Offset, + putStringPtr.Segment, + numChars, + srcPtr.Offset, + srcPtr.Segment + }); + + return mbbsEmuCpuRegisters.GetPointer(); + } + + protected ushort fputs(FarPtr putStringPtr, FarPtr srcPtr) + { + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FPUTS_ORDINAL, new List + { + putStringPtr, + srcPtr + }); + + return mbbsEmuCpuRegisters.AX; + } + + protected ushort fseek(FarPtr srcPtr, int offset, ushort origin) + { + + + ExecuteApiTest(HostProcess.ExportedModules.Majorbbs.Segment, FSEEK_ORDINAL, new List + { + srcPtr.Offset, + srcPtr.Segment, + (ushort)offset, + (ushort)(offset >> 16), + origin + }); + + return mbbsEmuCpuRegisters.AX; + } + protected string CreateTextFile(string filename, string contents) { var filePath = Path.Join(mbbsModule.ModulePath, filename); diff --git a/MBBSEmu.Tests/ExportedModules/Majorbbs/file_Tests.cs b/MBBSEmu.Tests/ExportedModules/Majorbbs/file_Tests.cs index 712af160..abeff02c 100644 --- a/MBBSEmu.Tests/ExportedModules/Majorbbs/file_Tests.cs +++ b/MBBSEmu.Tests/ExportedModules/Majorbbs/file_Tests.cs @@ -1,3 +1,5 @@ +using MBBSEmu.Extensions; +using MBBSEmu.HostProcess.Structs; using MBBSEmu.Memory; using System; using System.IO; @@ -25,8 +27,6 @@ public void length_matches_constant() Assert.Equal(LOREM_IPSUM_LENGTH, LOREM_IPSUM.Length); } - // TODO(add fgetc/fputc fgets/fputs fseek tests) - [Theory] [InlineData("r")] [InlineData("r+")] @@ -300,5 +300,308 @@ public void fclose_invalidPointer() Assert.Equal(-1, fclose(FarPtr.Empty)); } + + [Theory] + [InlineData('A', 5)] + [InlineData('R', 10)] + [InlineData('!', 20)] + [InlineData('z', 25)] + [InlineData('l', 55)] + public void fputc_fgetc_fseek_origin0_file(byte inputChar, int fseekOffset) + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "w"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + Assert.Equal(0, fseek(filep, fseekOffset, 0)); + + Assert.Equal(1, fputc(inputChar, filep)); + + Assert.Equal(0,fseek(filep, fseekOffset, 0)); + + Assert.Equal(inputChar, fgetc(filep)); + + Assert.Equal(0, fclose(filep)); + } + + [Theory] + [InlineData(4, 'm', 'n')] + [InlineData(9, 'u', 'j')] + [InlineData(14, 'l', 'k')] + [InlineData(20, 't', 'r')] + [InlineData(22, 'a', '.')] + public void fgetc_ungetc_fseek_origin0_file(int fseekOffset, ushort getValue, ushort ungetValue) + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "r"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + Assert.Equal(0, fseek(filep, fseekOffset, 0)); + + Assert.Equal((byte)getValue, fgetc(filep)); + + Assert.Equal((byte)ungetValue, ungetc(ungetValue, filep)); + + var curFileStruct = new FileStruct(mbbsEmuMemoryCore.GetArray(filep, FileStruct.Size)); + var curFileStream = majorbbs.FilePointerDictionary[curFileStruct.curp.Offset]; + Assert.Equal(ungetValue, curFileStream.ReadByte()); + + Assert.Equal(0, fclose(filep)); + } + + [Theory] + [InlineData("Mary Had a Little Lamb\0", 0)] + [InlineData("---+++---111\0", 10)] + [InlineData("((((jumbo)))))\0", 20)] + [InlineData(" yeah \0", 25)] + [InlineData("\0", 40)] + [InlineData("A\0", 0)] + public void fputs_fgets_fseek_origin0_file(string stringPut, int fseekOffset) + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var stringPutPtr = mbbsEmuMemoryCore.AllocateVariable("STRING_PUT", (ushort)stringPut.Length); + mbbsEmuMemoryCore.SetArray(stringPutPtr, Encoding.ASCII.GetBytes(stringPut)); + + var stringGetPtr = mbbsEmuMemoryCore.AllocateVariable("STRING_GET", (ushort)stringPut.Length); + + var filep = fopen("FILE.TXT", "w"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + Assert.Equal(0, fseek(filep, fseekOffset, 0)); + + Assert.Equal(1, fputs(stringPutPtr, filep)); + + Assert.Equal(0, fseek(filep, fseekOffset, 0)); + + Assert.Equal(stringGetPtr, fgets(stringGetPtr, (ushort)stringPut.Length, filep)); + Assert.Equal(stringPut, Encoding.ASCII.GetString(mbbsEmuMemoryCore.GetString("STRING_GET"))); + + Assert.Equal(0, fclose(filep)); + } + + [Theory] + [InlineData(4, 'm', 1)] + [InlineData(10, 'm', 1)] + [InlineData(20, 't', 1)] + [InlineData(25, 't', 1)] + [InlineData(55, ',', 1)] + [InlineData(-1, '.', 2)] + [InlineData(-10, 'c', 2)] + [InlineData(-20, 'u', 2)] + [InlineData(-25, 't', 2)] + [InlineData(-55, 'c', 2)] + public void fgetc_fseek_origin1and2_file(int fseekOffset, byte expectedChar, ushort originNum) + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "r"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + Assert.Equal(0, fseek(filep, 0, 0)); + + Assert.Equal(0, fseek(filep, fseekOffset, originNum)); + + Assert.Equal(expectedChar, fgetc(filep)); + + Assert.Equal(0, fclose(filep)); + } + + [Fact] + public void fgetc_fseek_EOF_file() + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "r"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + Assert.Equal(0, fseek(filep, 0, 2)); + + Assert.Equal(0xFFFF, fgetc(filep)); + + var curFileStruct = new FileStruct(mbbsEmuMemoryCore.GetArray(filep, FileStruct.Size)); + var curFileStream = majorbbs.FilePointerDictionary[curFileStruct.curp.Offset]; + + Assert.Equal(LOREM_IPSUM_LENGTH, curFileStream.Position); + Assert.True(curFileStruct.flags.IsFlagSet((ushort)FileStruct.EnumFileFlags.EOF)); + + Assert.Equal(0, fclose(filep)); + } + + [Fact] + public void fputc_fseek_EOF_file() + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "a"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + Assert.Equal(0, fseek(filep, 0, 2)); + + Assert.Equal(1, fputc(0, filep)); + + var curFileStruct = new FileStruct(mbbsEmuMemoryCore.GetArray(filep, FileStruct.Size)); + var curFileStream = majorbbs.FilePointerDictionary[curFileStruct.curp.Offset]; + + Assert.Equal(LOREM_IPSUM_LENGTH + 1, curFileStream.Position); + Assert.True(curFileStruct.flags.IsFlagSet((ushort)FileStruct.EnumFileFlags.EOF)); + + Assert.Equal(0, fclose(filep)); + } + + [Fact] + public void fgets_fseek_EOF_file() + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "r"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + var stringGetPtr = mbbsEmuMemoryCore.AllocateVariable("STRING_GET", (ushort)"TEST".Length); + mbbsEmuMemoryCore.SetArray(stringGetPtr, Encoding.ASCII.GetBytes("TEST")); + + Assert.Equal(0, fseek(filep, 0, 2)); + + Assert.Equal(new FarPtr(), fgets(stringGetPtr, 4, filep)); + + var curFileStruct = new FileStruct(mbbsEmuMemoryCore.GetArray(filep, FileStruct.Size)); + var curFileStream = majorbbs.FilePointerDictionary[curFileStruct.curp.Offset]; + Assert.Equal(LOREM_IPSUM_LENGTH, curFileStream.Position ); + Assert.True(curFileStruct.flags.IsFlagSet((ushort)FileStruct.EnumFileFlags.EOF)); + + Assert.Equal(0, fclose(filep)); + } + + [Fact] + public void fputs_fseek_EOF_file() + { + //Reset State + Reset(); + + var filePath = CreateTextFile("file.txt", LOREM_IPSUM); + + Assert.Equal(LOREM_IPSUM.Length, new FileInfo(filePath).Length); + + var filep = fopen("FILE.TXT", "a"); + Assert.NotEqual(0, filep.Segment); + Assert.NotEqual(0, filep.Offset); + + var stringPutPtr = mbbsEmuMemoryCore.AllocateVariable("STRING_PUT", (ushort)"TEST".Length); + mbbsEmuMemoryCore.SetArray(stringPutPtr, Encoding.ASCII.GetBytes("TEST")); + + Assert.Equal(0, fseek(filep, 0, 2)); + + Assert.Equal(1, fputs(stringPutPtr, filep)); + + var curFileStruct = new FileStruct(mbbsEmuMemoryCore.GetArray(filep, FileStruct.Size)); + var curFileStream = majorbbs.FilePointerDictionary[curFileStruct.curp.Offset]; + Assert.Equal(LOREM_IPSUM_LENGTH + 5, curFileStream.Position); + Assert.True(curFileStruct.flags.IsFlagSet((ushort)FileStruct.EnumFileFlags.EOF)); + + Assert.Equal(0, fclose(filep)); + } + + [Fact] + public void fgetc_InvalidStream_Throw() + { + //Reset State + Reset(); + + //Pass empty pointer + Assert.Throws(() => fgetc(new FarPtr())); + } + + [Fact] + public void fputc_InvalidStream_Throw() + { + //Reset State + Reset(); + + //Pass empty pointer + Assert.Throws(() => fputc(0, new FarPtr())); + } + + [Fact] + public void fgets_InvalidStream_Throw() + { + //Reset State + Reset(); + + //Pass empty pointer + Assert.Throws(() => fgets(new FarPtr(), 0, new FarPtr())); + } + + [Fact] + public void fputs_InvalidStream_Throw() + { + //Reset State + Reset(); + + //Pass empty pointer + Assert.Throws(() => fputs(new FarPtr(), new FarPtr())); + } + + [Fact] + public void ungetc_InvalidStream_Throw() + { + //Reset State + Reset(); + + //Pass empty pointer + Assert.Throws(() => ungetc(0, new FarPtr())); + } + + [Fact] + public void fseek_InvalidStream_Throw() + { + //Reset State + Reset(); + + //Pass empty pointer + Assert.Throws(() => fseek(new FarPtr(), 0, 0)); + } } } diff --git a/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs b/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs index e6f762e9..2a0cd7cc 100644 --- a/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs +++ b/MBBSEmu/HostProcess/ExportedModules/ExportedModuleBase.cs @@ -59,7 +59,7 @@ public LeadingNumberFromStringResult() /// /// Pointers to files opened using FOPEN /// - private protected readonly PointerDictionary FilePointerDictionary; + public readonly PointerDictionary FilePointerDictionary; public readonly PointerDictionary McvPointerDictionary; private protected readonly ILogger _logger; diff --git a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs index 3b125710..c3565274 100644 --- a/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs +++ b/MBBSEmu/HostProcess/ExportedModules/Majorbbs.cs @@ -950,7 +950,7 @@ public ReadOnlySpan Invoke(ushort ordinal, bool offsetsOnly = false) galmalloc(); break; case 615: - fungetc(); + ungetc(); break; case 230: galfree(); @@ -3873,7 +3873,7 @@ private void fgets() var fileStruct = new FileStruct(Module.Memory.GetArray(fileStructPointer, FileStruct.Size)); if (!FilePointerDictionary.TryGetValue(fileStruct.curp.Offset, out var fileStream)) - throw new Exception($"Unable to locate FileStream for {fileStructPointer} (Stream: {fileStruct.curp})"); + throw new FileNotFoundException($"Unable to locate FileStream for {fileStructPointer} (Stream: {fileStruct.curp})"); if (fileStream.Position == fileStream.Length) { @@ -3974,13 +3974,13 @@ private void f_printf() private void fseek() { var fileStructPointer = GetParameterPointer(0); - var offset = GetParameterULong(2); + var offset = GetParameterLong(2); var origin = GetParameter(4); var fileStruct = new FileStruct(Module.Memory.GetArray(fileStructPointer, FileStruct.Size)); if (!FilePointerDictionary.TryGetValue(fileStruct.curp.Offset, out var fileStream)) - throw new Exception($"Unable to locate FileStream for {fileStructPointer} (Stream: {fileStruct.curp})"); + throw new FileNotFoundException($"Unable to locate FileStream for {fileStructPointer} (Stream: {fileStruct.curp})"); switch (origin) { @@ -5160,7 +5160,7 @@ private void galmalloc() /// /// Signature: int ungetc(int character,FILE *stream ) /// - private void fungetc() + private void ungetc() { var character = GetParameter(0); var fileStructPointer = GetParameterPointer(1); @@ -5175,7 +5175,7 @@ private void fungetc() fileStream.WriteByte((byte)character); fileStream.Position -= 1; - //Update EOF Flag if required + //Update EOF Flag if required -- TODO DO WE NEED? if (fileStream.Position == fileStream.Length) { fileStruct.flags |= (ushort)FileStruct.EnumFileFlags.EOF;