From 292d828ac8b3a5b1956ff9867194d489b49f0249 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 13 Jun 2024 10:00:04 -0400 Subject: [PATCH 1/4] begin conversion from Universal Editor 4.0 to MBS Editor 5 --- .gitignore | 1 + editor-dotnet.sln | 121 + .../MBS.Editor.TestProject.csproj | 15 + .../src/app/MBS.Editor.TestProject/Program.cs | 51 + editor-dotnet/src/app/MBS.Editor/Program.cs | 18 +- editor-dotnet/src/install-plugins.sh | 30 + .../MBS.Editor.Core/Checksum/CRCUtilities.cs | 157 ++ .../Checksum/ChecksumModule.cs | 49 + .../BZip2CRC/BZip2CRCChecksumModule.cs | 172 ++ .../Compression/CompressionException.cs | 9 + .../Compression/CompressionModule.cs | 43 + .../DualStreamCompressionModule.cs | 29 + .../Modules/BZip2/BZip2CompresssionModule.cs | 38 + .../Modules/BZip2/BZip2Constants.cs | 116 + .../Modules/BZip2/BZip2Exception.cs | 36 + .../Modules/BZip2/BZip2InputStream.cs | 1052 +++++++++ .../Modules/BZip2/BZip2OutputStream.cs | 2033 +++++++++++++++++ .../Deflate/DeflateCompressionModule.cs | 16 + .../Modules/GZip/GZipCompressionModule.cs | 37 + .../Modules/LZW/Internal/LzwConstants.cs | 62 + .../Modules/LZW/Internal/LzwInputStream.cs | 571 +++++ .../Modules/LZW/LZWCompressionModule.cs | 18 + .../Compression/Modules/LZW/LZWException.cs | 36 + .../ZlibBuiltinCompressionModule.cs | 14 + .../Compression/SystemCompressionLevel.cs | 26 + .../Compression/SystemCompressionModule.cs | 44 + .../src/lib/MBS.Editor.Core/DataFormat.cs | 34 +- .../lib/MBS.Editor.Core/DataFormatMetadata.cs | 10 + .../FileSystem/ZIP/ZIPDataFormat.cs | 13 + .../src/lib/MBS.Editor.Core/Document.cs | 41 + .../Hosting/HostApplicationMessage.cs | 138 ++ .../Hosting/HostApplicationMessageSeverity.cs | 9 + .../Hosting/HostApplicationOutputWindow.cs | 64 + .../MBS.Editor.Core/Hosting/HostServices.cs | 7 + .../Hosting/IHostApplication.cs | 6 + .../src/lib/MBS.Editor.Core/IO/Endianness.cs | 7 + .../lib/MBS.Editor.Core/IO/NewLineSequence.cs | 11 + .../src/lib/MBS.Editor.Core/IO/Reader.cs | 1848 +++++++++++++++ .../MBS.Editor.Core/IO/ReaderWriterBase.cs | 79 + .../src/lib/MBS.Editor.Core/IO/Writer.cs | 1023 +++++++++ .../InvalidDataFormatException.cs | 29 + .../MBS.Editor.Core/MBS.Editor.Core.csproj | 19 +- .../src/lib/MBS.Editor.Core/ObjectModel.cs | 29 + .../MBS.Editor.Core/ObjectModelMetadata.cs | 6 + .../ObjectModelNotSupportedException.cs | 37 + .../ObjectModels/Database/DatabaseField.cs | 89 + .../Database/DatabaseObjectModel.cs | 92 + .../ObjectModels/Database/DatabaseRecord.cs | 70 + .../ObjectModels/Database/DatabaseTable.cs | 95 + .../ObjectModels/FileSystem/FileSource.cs | 25 + .../FileSources/ByteArrayFileSource.cs | 55 + .../CompressedEmbeddedFileSource.cs | 54 + .../FileSources/EmbeddedFileSource.cs | 34 + .../FileSources/StreamFileSource.cs | 59 + .../FileSystemCustomDetailCollection.cs | 29 + .../ObjectModels/FileSystem/FileSystemFile.cs | 20 + .../FileSystem/FileSystemFolder.cs | 12 + .../ObjectModels/FileSystem/FileSystemItem.cs | 8 + .../FileSystem/FileSystemItemCollection.cs | 181 ++ .../FileSystem/FileSystemObjectModel.cs | 20 + .../FileSystem/IFileSystemItemContainer.cs | 7 + .../Database/UTF/Internal/UTFTableInfo.cs | 17 + .../Database/UTF/UTFColumnDataType.cs | 77 + .../Database/UTF/UTFColumnStorageType.cs | 45 + .../DataFormats/Database/UTF/UTFDataFormat.cs | 644 ++++++ .../FileSystem/AFS/AFSDataFormat.cs | 293 +++ .../DataFormats/FileSystem/AFS/AFSFileInfo.cs | 41 + .../FileSystem/AFS/AFSFormatVersion.cs | 37 + .../FileSystem/CPK/CPKCompressionModule.cs | 193 ++ .../FileSystem/CPK/CPKDataFormat.cs | 897 ++++++++ .../DataFormats/FileSystem/CPK/CPKFileMode.cs | 32 + .../FileSystem/CPK/Internal/IDOFFSET.cs | 41 + .../MBS.Editor.Plugins.CRI.csproj | 11 + .../AssemblyInfo.cs | 3 + .../MBS.Editor.Plugins.Multimedia.csproj | 12 + .../PositionVector2.cs | 123 + .../Compression/CompressionTests.cs | 141 ++ .../MBS.Editor.Core.Tests/GlobalUsings.cs | 1 + .../MBS.Editor.Core.Tests.csproj | 19 + .../FileSystemItemCollectionTests.cs | 45 + .../FileSystem/FileSystemTests.cs | 36 + .../DataFormats/AFS/AFSDataFormatTests.cs | 35 + .../DataFormats/CPK/CPKDataFormatTests.cs | 170 ++ .../GlobalUsings.cs | 1 + .../MBS.Editor.Plugins.CRI.Tests.csproj | 26 + .../Resources/TestData/sample_data.cpk | Bin 0 -> 148232 bytes .../sample_data_uncompressed_masked.cpk | Bin 0 -> 208136 bytes .../sample_data_uncompressed_unmasked.cpk | Bin 0 -> 208136 bytes ...sample_data_uncompressed_unmasked_idfn.cpk | Bin 0 -> 208648 bytes 89 files changed, 11983 insertions(+), 11 deletions(-) create mode 100644 editor-dotnet.sln create mode 100644 editor-dotnet/src/app/MBS.Editor.TestProject/MBS.Editor.TestProject.csproj create mode 100644 editor-dotnet/src/app/MBS.Editor.TestProject/Program.cs create mode 100755 editor-dotnet/src/install-plugins.sh create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Checksum/CRCUtilities.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Checksum/ChecksumModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Checksum/Modules/BZip2CRC/BZip2CRCChecksumModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionException.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/DualStreamCompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2CompresssionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Constants.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Exception.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2InputStream.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2OutputStream.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/Deflate/DeflateCompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/GZip/GZipCompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwConstants.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwInputStream.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWCompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWException.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/ZlibBuiltin/ZlibBuiltinCompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionLevel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionModule.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormatMetadata.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/ZIP/ZIPDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Document.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessage.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessageSeverity.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationOutputWindow.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostServices.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/Hosting/IHostApplication.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/IO/Endianness.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/IO/NewLineSequence.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/IO/Writer.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/InvalidDataFormatException.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelMetadata.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseField.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseObjectModel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseRecord.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseTable.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/ByteArrayFileSource.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/CompressedEmbeddedFileSource.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/StreamFileSource.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemCustomDetailCollection.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFile.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFolder.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItem.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemObjectModel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/IFileSystemItemContainer.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/Internal/UTFTableInfo.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnDataType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnStorageType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFileInfo.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFormatVersion.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKCompressionModule.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKFileMode.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/Internal/IDOFFSET.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/MBS.Editor.Plugins.CRI.csproj create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/AssemblyInfo.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/MBS.Editor.Plugins.Multimedia.csproj create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/PositionVector2.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/Compression/CompressionTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/GlobalUsings.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/AFS/AFSDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/GlobalUsings.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/Resources/TestData/sample_data.cpk create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/Resources/TestData/sample_data_uncompressed_masked.cpk create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/Resources/TestData/sample_data_uncompressed_unmasked.cpk create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/Resources/TestData/sample_data_uncompressed_unmasked_idfn.cpk diff --git a/.gitignore b/.gitignore index a9e05e3..a0eb733 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # ---> VisualStudioCode +.vscode .vscode/* !.vscode/settings.json !.vscode/tasks.json diff --git a/editor-dotnet.sln b/editor-dotnet.sln new file mode 100644 index 0000000..499bead --- /dev/null +++ b/editor-dotnet.sln @@ -0,0 +1,121 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "editor-dotnet", "editor-dotnet", "{75210F45-D690-4A61-9CD8-96B09E5DAAC5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F05001E1-6FFB-48AE-BF7F-7F39A24D3B70}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{C86F60F9-BBC1-4554-A3B0-D553F9C157A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor.Core", "editor-dotnet\src\lib\MBS.Editor.Core\MBS.Editor.Core.csproj", "{8FFB417A-2CDC-429F-ABE0-19B3015530D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor.UserInterface", "editor-dotnet\src\lib\MBS.Editor.UserInterface\MBS.Editor.UserInterface.csproj", "{C4316562-555A-4A79-9D71-15737976DF8B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{4ED8C38B-47EF-4368-9965-CF627465B45A}" + ProjectSection(SolutionItems) = preProject + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor", "editor-dotnet\src\app\MBS.Editor\MBS.Editor.csproj", "{A936C411-0184-43F8-A343-0DE8C3B7B42E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "framework-dotnet", "framework-dotnet", "{CC86007D-8193-4EAA-932D-A96B5F09847E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "framework-dotnet", "framework-dotnet", "{B9747AFE-160D-4807-B989-B3F0ACCA3634}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BDC147D8-4D97-4663-9408-BC822E1E0B3C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{80A728D5-7C00-4B59-A37E-321C54CC554F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Desktop", "framework-dotnet\framework-dotnet\src\lib\MBS.Desktop\MBS.Desktop.csproj", "{4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Core", "framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj", "{7565CFB4-9761-4064-B18F-5E2644730BC0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{451AD529-16B4-4049-9D0C-0C79B3DDFA52}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Multimedia", "editor-dotnet\src\plugins\MBS.Editor.Plugins.Multimedia\MBS.Editor.Plugins.Multimedia.csproj", "{5978938E-19F6-42AE-B588-7719A65ABCA7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.TestProject", "editor-dotnet\src\app\MBS.Editor.TestProject\MBS.Editor.TestProject.csproj", "{CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.CRI", "editor-dotnet\src\plugins\MBS.Editor.Plugins.CRI\MBS.Editor.Plugins.CRI.csproj", "{78B11A3E-1371-48D8-9B8E-AE6ED2380A50}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{74AD8C3F-B0B8-472F-A847-1FFFB1667B34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Core.Tests", "editor-dotnet\src\tests\MBS.Editor.Core.Tests\MBS.Editor.Core.Tests.csproj", "{7A349FC6-BCE7-465D-ADBC-7A21242E2C78}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.CRI.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.CRI.Tests\MBS.Editor.Plugins.CRI.Tests.csproj", "{2747FFC9-55AA-4A76-B0E9-D8A839E94E47}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FFB417A-2CDC-429F-ABE0-19B3015530D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FFB417A-2CDC-429F-ABE0-19B3015530D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FFB417A-2CDC-429F-ABE0-19B3015530D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FFB417A-2CDC-429F-ABE0-19B3015530D3}.Release|Any CPU.Build.0 = Release|Any CPU + {C4316562-555A-4A79-9D71-15737976DF8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4316562-555A-4A79-9D71-15737976DF8B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4316562-555A-4A79-9D71-15737976DF8B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4316562-555A-4A79-9D71-15737976DF8B}.Release|Any CPU.Build.0 = Release|Any CPU + {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Release|Any CPU.Build.0 = Release|Any CPU + {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Release|Any CPU.Build.0 = Release|Any CPU + {7565CFB4-9761-4064-B18F-5E2644730BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7565CFB4-9761-4064-B18F-5E2644730BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7565CFB4-9761-4064-B18F-5E2644730BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7565CFB4-9761-4064-B18F-5E2644730BC0}.Release|Any CPU.Build.0 = Release|Any CPU + {5978938E-19F6-42AE-B588-7719A65ABCA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5978938E-19F6-42AE-B588-7719A65ABCA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5978938E-19F6-42AE-B588-7719A65ABCA7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5978938E-19F6-42AE-B588-7719A65ABCA7}.Release|Any CPU.Build.0 = Release|Any CPU + {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF}.Release|Any CPU.Build.0 = Release|Any CPU + {78B11A3E-1371-48D8-9B8E-AE6ED2380A50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78B11A3E-1371-48D8-9B8E-AE6ED2380A50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78B11A3E-1371-48D8-9B8E-AE6ED2380A50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78B11A3E-1371-48D8-9B8E-AE6ED2380A50}.Release|Any CPU.Build.0 = Release|Any CPU + {7A349FC6-BCE7-465D-ADBC-7A21242E2C78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A349FC6-BCE7-465D-ADBC-7A21242E2C78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A349FC6-BCE7-465D-ADBC-7A21242E2C78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A349FC6-BCE7-465D-ADBC-7A21242E2C78}.Release|Any CPU.Build.0 = Release|Any CPU + {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} + {451AD529-16B4-4049-9D0C-0C79B3DDFA52} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} + {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} = {75210F45-D690-4A61-9CD8-96B09E5DAAC5} + {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} + {8FFB417A-2CDC-429F-ABE0-19B3015530D3} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} + {C4316562-555A-4A79-9D71-15737976DF8B} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} + {4ED8C38B-47EF-4368-9965-CF627465B45A} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} + {A936C411-0184-43F8-A343-0DE8C3B7B42E} = {4ED8C38B-47EF-4368-9965-CF627465B45A} + {B9747AFE-160D-4807-B989-B3F0ACCA3634} = {CC86007D-8193-4EAA-932D-A96B5F09847E} + {BDC147D8-4D97-4663-9408-BC822E1E0B3C} = {B9747AFE-160D-4807-B989-B3F0ACCA3634} + {80A728D5-7C00-4B59-A37E-321C54CC554F} = {BDC147D8-4D97-4663-9408-BC822E1E0B3C} + {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143} = {80A728D5-7C00-4B59-A37E-321C54CC554F} + {7565CFB4-9761-4064-B18F-5E2644730BC0} = {80A728D5-7C00-4B59-A37E-321C54CC554F} + {5978938E-19F6-42AE-B588-7719A65ABCA7} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF} = {4ED8C38B-47EF-4368-9965-CF627465B45A} + {78B11A3E-1371-48D8-9B8E-AE6ED2380A50} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {7A349FC6-BCE7-465D-ADBC-7A21242E2C78} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {2747FFC9-55AA-4A76-B0E9-D8A839E94E47} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4D0B64EB-14E9-4013-AA33-33716704909B} + EndGlobalSection +EndGlobal diff --git a/editor-dotnet/src/app/MBS.Editor.TestProject/MBS.Editor.TestProject.csproj b/editor-dotnet/src/app/MBS.Editor.TestProject/MBS.Editor.TestProject.csproj new file mode 100644 index 0000000..5778db8 --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.TestProject/MBS.Editor.TestProject.csproj @@ -0,0 +1,15 @@ + + + + + + + + + Exe + net8.0 + enable + enable + + + diff --git a/editor-dotnet/src/app/MBS.Editor.TestProject/Program.cs b/editor-dotnet/src/app/MBS.Editor.TestProject/Program.cs new file mode 100644 index 0000000..51998a4 --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.TestProject/Program.cs @@ -0,0 +1,51 @@ +using MBS.Core; + +using MBS.Editor.Core; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +class Program : Application +{ + protected override int StartInternal() + { + FileStream fs = System.IO.File.Open("/tmp/test.afs", FileMode.Create, FileAccess.Write); + /* + Writer writer = new Writer(fs); + + writer.Endianness = Endianness.BigEndian; + writer.WriteBoolean(true); + writer.WriteInt32(1024); + writer.WriteInt32(768); + writer.WriteFixedLengthString("Hello world"); + writer.WriteInt32(0x7B); + writer.Close(); + + */ + + FileSystemObjectModel fsom = new FileSystemObjectModel(); + fsom.Items.AddFile("test.ini", new ByteArrayFileSource(new byte[] { 0x20, 0x04, 0xFE, 0xDE })); + + Console.WriteLine(Environment.ProcessPath); + + Type t = MBS.Core.Reflection.TypeLoader.FindType("MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS.AFSDataFormat"); + Console.WriteLine("found type {0}", t); + + DataFormat afs = DataFormat.FromType(t); + if (afs == null) + { + Console.WriteLine("could not load type"); + return 2; + } + + Document.Save(fsom, afs, fs); + fs.Close(); + + return base.StartInternal(); + } + + static void Main(string[] args) + { + (new Program()).Start(); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/app/MBS.Editor/Program.cs b/editor-dotnet/src/app/MBS.Editor/Program.cs index 5bde512..87c163e 100644 --- a/editor-dotnet/src/app/MBS.Editor/Program.cs +++ b/editor-dotnet/src/app/MBS.Editor/Program.cs @@ -1,3 +1,19 @@ -using MBS.Editor.UserInterface; +// +// Copyright (C) + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using MBS.Editor.UserInterface; return (new EditorApplication()).Start(); \ No newline at end of file diff --git a/editor-dotnet/src/install-plugins.sh b/editor-dotnet/src/install-plugins.sh new file mode 100755 index 0000000..6b16c80 --- /dev/null +++ b/editor-dotnet/src/install-plugins.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +CONFIDENCE=Debug +NET_VERSION=net8.0 + +if [ ! -d app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/plugins ]; then + + mkdir app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/plugins + +fi + +if [ ! -d app/MBS.Editor.TestProject/bin/$CONFIDENCE/$NET_VERSION/plugins ]; then + + mkdir app/MBS.Editor.TestProject/bin/$CONFIDENCE/$NET_VERSION/plugins + +fi + +for dir in plugins/* ; do + + echo "Building $dir" + + pushd $dir + dotnet build + popd + + echo "Copying $dir" + cp $dir/bin/$CONFIDENCE/$NET_VERSION/*.dll app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/plugins + cp $dir/bin/$CONFIDENCE/$NET_VERSION/*.dll app/MBS.Editor.TestProject/bin/$CONFIDENCE/$NET_VERSION/plugins + +done diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/CRCUtilities.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/CRCUtilities.cs new file mode 100644 index 0000000..8fa8273 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/CRCUtilities.cs @@ -0,0 +1,157 @@ +using System.Runtime.CompilerServices; + +namespace MBS.Editor.Core.Checksum; + +internal static class CRCUtilities +{ + /// + /// The number of slicing lookup tables to generate. + /// + internal const int SlicingDegree = 16; + + /// + /// Generates multiple CRC lookup tables for a given polynomial, stored + /// in a linear array of uints. The first block (i.e. the first 256 + /// elements) is the same as the byte-by-byte CRC lookup table. + /// + /// The generating CRC polynomial + /// Whether the polynomial is in reversed bit order + /// A linear array of 256 * elements + /// + /// This table could also be generated as a rectangular array, but the + /// JIT compiler generates slower code than if we use a linear array. + /// Known issue, see: https://github.com/dotnet/runtime/issues/30275 + /// + internal static uint[] GenerateSlicingLookupTable(uint polynomial, bool isReversed) + { + var table = new uint[256 * SlicingDegree]; + uint one = isReversed ? 1 : (1U << 31); + + for (int i = 0; i < 256; i++) + { + uint res = (uint)(isReversed ? i : i << 24); + for (int j = 0; j < SlicingDegree; j++) + { + for (int k = 0; k < 8; k++) + { + if (isReversed) + { + res = (res & one) == 1 ? polynomial ^ (res >> 1) : res >> 1; + } + else + { + res = (res & one) != 0 ? polynomial ^ (res << 1) : res << 1; + } + } + + table[(256 * j) + i] = res; + } + } + + return table; + } + + /// + /// Mixes the first four bytes of input with + /// using normal ordering before calling . + /// + /// Array of data to checksum + /// Offset to start reading from + /// The table to use for slicing-by-16 lookup + /// Checksum state before this update call + /// A new unfinalized checksum value + /// + /// + /// Assumes input[offset]..input[offset + 15] are valid array indexes. + /// For performance reasons, this must be checked by the caller. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint UpdateDataForNormalPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) + { + byte x1 = (byte)((byte)(checkValue >> 24) ^ input[offset]); + byte x2 = (byte)((byte)(checkValue >> 16) ^ input[offset + 1]); + byte x3 = (byte)((byte)(checkValue >> 8) ^ input[offset + 2]); + byte x4 = (byte)((byte)checkValue ^ input[offset + 3]); + + return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); + } + + /// + /// Mixes the first four bytes of input with + /// using reflected ordering before calling . + /// + /// Array of data to checksum + /// Offset to start reading from + /// The table to use for slicing-by-16 lookup + /// Checksum state before this update call + /// A new unfinalized checksum value + /// + /// + /// Assumes input[offset]..input[offset + 15] are valid array indexes. + /// For performance reasons, this must be checked by the caller. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint UpdateDataForReversedPoly(byte[] input, int offset, uint[] crcTable, uint checkValue) + { + byte x1 = (byte)((byte)checkValue ^ input[offset]); + byte x2 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 1]); + byte x3 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 2]); + byte x4 = (byte)((byte)(checkValue >>= 8) ^ input[offset + 3]); + + return UpdateDataCommon(input, offset, crcTable, x1, x2, x3, x4); + } + + /// + /// A shared method for updating an unfinalized CRC checksum using slicing-by-16. + /// + /// Array of data to checksum + /// Offset to start reading from + /// The table to use for slicing-by-16 lookup + /// First byte of input after mixing with the old CRC + /// Second byte of input after mixing with the old CRC + /// Third byte of input after mixing with the old CRC + /// Fourth byte of input after mixing with the old CRC + /// A new unfinalized checksum value + /// + /// + /// Even though the first four bytes of input are fed in as arguments, + /// should be the same value passed to this + /// function's caller (either or + /// ). This method will get inlined + /// into both functions, so using the same offset produces faster code. + /// + /// + /// Because most processors running C# have some kind of instruction-level + /// parallelism, the order of XOR operations can affect performance. This + /// ordering assumes that the assembly code generated by the just-in-time + /// compiler will emit a bunch of arithmetic operations for checking array + /// bounds. Then it opportunistically XORs a1 and a2 to keep the processor + /// busy while those other parts of the pipeline handle the range check + /// calculations. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint UpdateDataCommon(byte[] input, int offset, uint[] crcTable, byte x1, byte x2, byte x3, byte x4) + { + uint result; + uint a1 = crcTable[x1 + 3840] ^ crcTable[x2 + 3584]; + uint a2 = crcTable[x3 + 3328] ^ crcTable[x4 + 3072]; + + result = crcTable[input[offset + 4] + 2816]; + result ^= crcTable[input[offset + 5] + 2560]; + a1 ^= crcTable[input[offset + 9] + 1536]; + result ^= crcTable[input[offset + 6] + 2304]; + result ^= crcTable[input[offset + 7] + 2048]; + result ^= crcTable[input[offset + 8] + 1792]; + a2 ^= crcTable[input[offset + 13] + 512]; + result ^= crcTable[input[offset + 10] + 1280]; + result ^= crcTable[input[offset + 11] + 1024]; + result ^= crcTable[input[offset + 12] + 768]; + result ^= a1; + result ^= crcTable[input[offset + 14] + 256]; + result ^= crcTable[input[offset + 15]]; + result ^= a2; + + return result; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/ChecksumModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/ChecksumModule.cs new file mode 100644 index 0000000..e2baadb --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/ChecksumModule.cs @@ -0,0 +1,49 @@ +using System.Formats.Tar; +using System.Runtime.CompilerServices; + +namespace MBS.Editor.Core.Checksum; + +/// +/// Interface to compute a data checksum used by checked input/output streams. +/// A data checksum can be updated by one byte or with a byte array. After each +/// update the value of the current checksum can be returned by calling +/// getValue. The complete checksum object can also be reset +/// so it can be used again with new data. +/// +public abstract class ChecksumModule +{ + /// + /// Resets the data checksum as if no update was ever called. + /// + protected abstract void ResetInternal(); + + /// + /// Resets the data checksum as if no update was ever called. + /// + public void Reset() + { + ResetInternal(); + } + + protected abstract void UpdateInternal(int bval); + protected abstract long GetValueInternal(); + + private long checkValue; + + + /// + /// Returns the data checksum computed so far. + /// + public long Value { get { return GetValueInternal(); } } + + /// + /// Adds one byte to the data checksum. + /// + /// + /// the data value to add. The high byte of the int is ignored. + /// + public void Update(int bval) + { + UpdateInternal(bval); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/Modules/BZip2CRC/BZip2CRCChecksumModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/Modules/BZip2CRC/BZip2CRCChecksumModule.cs new file mode 100644 index 0000000..d4ad9c7 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Checksum/Modules/BZip2CRC/BZip2CRCChecksumModule.cs @@ -0,0 +1,172 @@ +using System; +using System.Runtime.CompilerServices; + +namespace MBS.Editor.Core.Checksum.Modules.BZip2CRC; + +/// +/// CRC-32 with unreversed data and reversed output +/// +/// +/// Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: +/// x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0. +/// +/// Polynomials over GF(2) are represented in binary, one bit per coefficient, +/// with the lowest powers in the most significant bit. Then adding polynomials +/// is just exclusive-or, and multiplying a polynomial by x is a right shift by +/// one. If we call the above polynomial p, and represent a byte as the +/// polynomial q, also with the lowest power in the most significant bit (so the +/// byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, +/// where a mod b means the remainder after dividing a by b. +/// +/// This calculation is done using the shift-register method of multiplying and +/// taking the remainder. The register is initialized to zero, and for each +/// incoming bit, x^32 is added mod p to the register if the bit is a one (where +/// x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by +/// x (which is shifting right by one and adding x^32 mod p if the bit shifted +/// out is a one). We start with the highest power (least significant bit) of +/// q and repeat for all eight bits of q. +/// +/// This implementation uses sixteen lookup tables stored in one linear array +/// to implement the slicing-by-16 algorithm, a variant of the slicing-by-8 +/// algorithm described in this Intel white paper: +/// +/// https://web.archive.org/web/20120722193753/http://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf +/// +/// The first lookup table is simply the CRC of all possible eight bit values. +/// Each successive lookup table is derived from the original table generated +/// by Sarwate's algorithm. Slicing a 16-bit input and XORing the outputs +/// together will produce the same output as a byte-by-byte CRC loop with +/// fewer arithmetic and bit manipulation operations, at the cost of increased +/// memory consumed by the lookup tables. (Slicing-by-16 requires a 16KB table, +/// which is still small enough to fit in most processors' L1 cache.) +/// +public sealed class BZip2CRCChecksumModule : ChecksumModule +{ + #region Instance Fields + + private const uint crcInit = 0xFFFFFFFF; + //const uint crcXor = 0x00000000; + + private static readonly uint[] crcTable = CRCUtilities.GenerateSlicingLookupTable(0x04C11DB7, isReversed: false); + + /// + /// The CRC data checksum so far. + /// + private uint checkValue; + + #endregion Instance Fields + + /// + /// Initialise a default instance of + /// + public BZip2CRCChecksumModule() + { + Reset(); + } + + /// + /// Resets the CRC data checksum as if no update was ever called. + /// + protected override void ResetInternal() + { + checkValue = crcInit; + } + + /// + /// Returns the CRC data checksum computed so far. + /// + /// Reversed Out = true + protected override long GetValueInternal() + { + // Technically, the output should be: + //return (long)(~checkValue ^ crcXor); + // but x ^ 0 = x, so there is no point in adding + // the XOR operation + return (long)(~checkValue); + } + + /// + /// Updates the checksum with the int bval. + /// + /// + /// the byte is taken as the lower 8 bits of bval + /// + /// Reversed Data = false + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void UpdateInternal(int bval) + { + checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8)); + } + + + + + + + /// + /// Updates the CRC data checksum with the bytes taken from + /// a block of data. + /// + /// Contains the data to update the CRC with. + public void Update(byte[] buffer) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + Update(buffer, 0, buffer.Length); + } + + /// + /// Update CRC data checksum based on a portion of a block of data + /// + /// + /// The chunk of data to add + /// + public void Update(ArraySegment segment) + { + Update(segment.Array, segment.Offset, segment.Count); + } + + /// + /// Internal helper function for updating a block of data using slicing. + /// + /// The array containing the data to add + /// Range start for (inclusive) + /// The number of bytes to checksum starting from + private void Update(byte[] data, int offset, int count) + { + int remainder = count % CRCUtilities.SlicingDegree; + int end = offset + count - remainder; + + while (offset != end) + { + checkValue = CRCUtilities.UpdateDataForNormalPoly(data, offset, crcTable, checkValue); + offset += CRCUtilities.SlicingDegree; + } + + if (remainder != 0) + { + SlowUpdateLoop(data, offset, end + remainder); + } + } + + /// + /// A non-inlined function for updating data that doesn't fit in a 16-byte + /// block. We don't expect to enter this function most of the time, and when + /// we do we're not here for long, so disabling inlining here improves + /// performance overall. + /// + /// The array containing the data to add + /// Range start for (inclusive) + /// Range end for (exclusive) + [MethodImpl(MethodImplOptions.NoInlining)] + private void SlowUpdateLoop(byte[] data, int offset, int end) + { + while (offset != end) + { + Update(data[offset++]); + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionException.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionException.cs new file mode 100644 index 0000000..54dcc60 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionException.cs @@ -0,0 +1,9 @@ +namespace MBS.Editor.Core.Compression; + +public class CompressionException : Exception +{ + + public CompressionException() : base() { } + public CompressionException(string message) : base(message) { } + public CompressionException(string message, Exception innerException) : base(message, innerException) { } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionModule.cs new file mode 100644 index 0000000..179f9c3 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/CompressionModule.cs @@ -0,0 +1,43 @@ +namespace MBS.Editor.Core.Compression; + +public abstract class CompressionModule +{ + + public byte[] Compress(byte[] input) + { + MemoryStream inputStream = new MemoryStream(input); + MemoryStream outputStream = new MemoryStream(); + Compress(inputStream, outputStream); + + outputStream.Flush(); + outputStream.Close(); + return outputStream.ToArray(); + } + + public void Compress(Stream inputStream, Stream outputStream) + { + CompressInternal(inputStream, outputStream); + } + + public byte[] Decompress(byte[] input) + { + MemoryStream inputStream = new MemoryStream(input); + MemoryStream outputStream = new MemoryStream(); + Decompress(inputStream, outputStream); + + outputStream.Flush(); + outputStream.Close(); + return outputStream.ToArray(); + } + public void Decompress(Stream inputStream, Stream outputStream) + { + DecompressInternal(inputStream, outputStream); + } + + //protected abstract void CompressInternal(Stream inputStream, byte[] buffer, int offset, int length); + //protected abstract void DecompressInternal(Stream inputStream, byte[] buffer, int offset, int length); + + protected abstract void CompressInternal(Stream inputStream, Stream outputStream); + protected abstract void DecompressInternal(Stream inputStream, Stream outputStream); + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/DualStreamCompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/DualStreamCompressionModule.cs new file mode 100644 index 0000000..d7fd264 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/DualStreamCompressionModule.cs @@ -0,0 +1,29 @@ +using System.IO.Compression; + +namespace MBS.Editor.Core.Compression; + +public abstract class DualStreamCompressionModule : CompressionModule where TInputStream : Stream where TOutputStream : Stream +{ + protected abstract TOutputStream CreateCompressor(Stream stream); + protected abstract TInputStream CreateDecompressor(Stream stream); + + protected override void CompressInternal(Stream inputStream, Stream outputStream) + { + TOutputStream _compressor = CreateCompressor(outputStream); + inputStream.CopyTo(_compressor); + + // !!! IMPORTANT !!! DO NOT FORGET TO FLUSH !!! + _compressor.Flush(); + _compressor.Close(); + } + protected override void DecompressInternal(Stream inputStream, Stream outputStream) + { + TInputStream _decompressor = CreateDecompressor(inputStream); + _decompressor.CopyTo(outputStream); + + // !!! IMPORTANT !!! DO NOT FORGET TO FLUSH !!! + _decompressor.Flush(); + _decompressor.Close(); + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2CompresssionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2CompresssionModule.cs new file mode 100644 index 0000000..7ff72b5 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2CompresssionModule.cs @@ -0,0 +1,38 @@ +using System.IO.Compression; +using MBS.Editor.Core.Compression; +using MBS.Editor.Core.Compression.Modules.BZip2; + +namespace MBS.Editor.Core.Compression.Modules.GZip; + +public class BZip2CompressionModule : DualStreamCompressionModule +{ + protected override BZip2OutputStream CreateCompressor(Stream stream) + { + return new BZip2OutputStream(stream); + } + protected override BZip2InputStream CreateDecompressor(Stream stream) + { + return new BZip2InputStream(stream); + } + + /* + protected override void CompressInternal(byte[] buffer, int offset, int length) + { + if (_compressor == null) + { + MemoryStream ms = new MemoryStream(); + _compressor = new GZipStream(ms, GetSystemCompressionLevel()); + } + _compressor.Write(buffer, offset, length); + } + protected override int DecompressInternal(byte[] buffer, int offset, int length) + { + if (_decompressor == null) + { + MemoryStream ms = new MemoryStream(); + _decompressor = new GZipStream(ms, GetSystemCompressionLevel()); + } + return _decompressor.Read(buffer, offset, length); + } + */ +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Constants.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Constants.cs new file mode 100644 index 0000000..1387075 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Constants.cs @@ -0,0 +1,116 @@ +namespace MBS.Editor.Core.Compression.Modules.BZip2; + +/// +/// Defines internal values for both compression and decompression +/// +internal static class BZip2Constants +{ + /// + /// Random numbers used to randomise repetitive blocks + /// + public readonly static int[] RandomNumbers = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; + + /// + /// When multiplied by compression parameter (1-9) gives the block size for compression + /// 9 gives the best compression but uses the most memory. + /// + public const int BaseBlockSize = 100000; + + /// + /// Backend constant + /// + public const int MaximumAlphaSize = 258; + + /// + /// Backend constant + /// + public const int MaximumCodeLength = 23; + + /// + /// Backend constant + /// + public const int RunA = 0; + + /// + /// Backend constant + /// + public const int RunB = 1; + + /// + /// Backend constant + /// + public const int GroupCount = 6; + + /// + /// Backend constant + /// + public const int GroupSize = 50; + + /// + /// Backend constant + /// + public const int NumberOfIterations = 4; + + /// + /// Backend constant + /// + public const int MaximumSelectors = (2 + (900000 / GroupSize)); + + /// + /// Backend constant + /// + public const int OvershootBytes = 20; +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Exception.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Exception.cs new file mode 100644 index 0000000..9fe6f76 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2Exception.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.Serialization; + +namespace MBS.Editor.Core.Compression.Modules.BZip2; + +/// +/// BZip2Exception represents exceptions specific to BZip2 classes and code. +/// +public class BZip2Exception : CompressionException +{ + /// + /// Initialise a new instance of . + /// + public BZip2Exception() + { + } + + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public BZip2Exception(string message) + : base(message) + { + } + + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public BZip2Exception(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2InputStream.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2InputStream.cs new file mode 100644 index 0000000..b798fa8 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2InputStream.cs @@ -0,0 +1,1052 @@ +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER + #define VECTORIZE_MEMORY_MOVE +#endif + +using MBS.Editor.Core.Checksum; +using System; +using System.IO; + +namespace MBS.Editor.Core.Compression.Modules.BZip2; + +/// +/// An input stream that decompresses files in the BZip2 format +/// +public class BZip2InputStream : Stream +{ + #region Constants + + private const int START_BLOCK_STATE = 1; + private const int RAND_PART_A_STATE = 2; + private const int RAND_PART_B_STATE = 3; + private const int RAND_PART_C_STATE = 4; + private const int NO_RAND_PART_A_STATE = 5; + private const int NO_RAND_PART_B_STATE = 6; + private const int NO_RAND_PART_C_STATE = 7; + +#if VECTORIZE_MEMORY_MOVE + private static readonly int VectorSize = System.Numerics.Vector.Count; +#endif // VECTORIZE_MEMORY_MOVE + +#endregion Constants + + #region Instance Fields + + /*-- + index of the last char in the block, so + the block size == last + 1. + --*/ + private int last; + + /*-- + index in zptr[] of original string after sorting. + --*/ + private int origPtr; + + /*-- + always: in the range 0 .. 9. + The current block size is 100000 * this number. + --*/ + private int blockSize100k; + + private bool blockRandomised; + + private int bsBuff; + private int bsLive; + private ChecksumModule mCrc = new Checksum.Modules.BZip2CRC.BZip2CRCChecksumModule(); + + private bool[] inUse = new bool[256]; + private int nInUse; + + private byte[] seqToUnseq = new byte[256]; + private byte[] unseqToSeq = new byte[256]; + + private byte[] selector = new byte[BZip2Constants.MaximumSelectors]; + private byte[] selectorMtf = new byte[BZip2Constants.MaximumSelectors]; + + private int[] tt; + private byte[] ll8; + + /*-- + freq table collected to save a pass over the data + during decompression. + --*/ + private int[] unzftab = new int[256]; + + private int[][] limit = new int[BZip2Constants.GroupCount][]; + private int[][] baseArray = new int[BZip2Constants.GroupCount][]; + private int[][] perm = new int[BZip2Constants.GroupCount][]; + private int[] minLens = new int[BZip2Constants.GroupCount]; + + private readonly Stream baseStream; + private bool streamEnd; + + private int currentChar = -1; + + private int currentState = START_BLOCK_STATE; + + private int storedBlockCRC, storedCombinedCRC; + private int computedBlockCRC; + private uint computedCombinedCRC; + + private int count, chPrev, ch2; + private int tPos; + private int rNToGo; + private int rTPos; + private int i2, j2; + private byte z; + + #endregion Instance Fields + + /// + /// Construct instance for reading from stream + /// + /// Data source + public BZip2InputStream(Stream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + // init arrays + for (int i = 0; i < BZip2Constants.GroupCount; ++i) + { + limit[i] = new int[BZip2Constants.MaximumAlphaSize]; + baseArray[i] = new int[BZip2Constants.MaximumAlphaSize]; + perm[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + baseStream = stream; + bsLive = 0; + bsBuff = 0; + Initialize(); + InitBlock(); + SetupBlock(); + } + + /// + /// Get/set flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + public bool IsStreamOwner { get; set; } = true; + + #region Stream Overrides + + /// + /// Gets a value indicating if the stream supports reading + /// + public override bool CanRead + { + get + { + return baseStream.CanRead; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing. + /// This property always returns false + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get + { + return baseStream.Length; + } + } + + /// + /// Gets the current position of the stream. + /// Setting the position is not supported and will throw a NotSupportException. + /// + /// Any attempt to set the position. + public override long Position + { + get + { + return baseStream.Position; + } + set + { + throw new NotSupportedException("BZip2InputStream position cannot be set"); + } + } + + /// + /// Flushes the stream. + /// + public override void Flush() + { + baseStream.Flush(); + } + + /// + /// Set the streams position. This operation is not supported and will throw a NotSupportedException + /// + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// The new position of the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("BZip2InputStream Seek not supported"); + } + + /// + /// Sets the length of this stream to the given value. + /// This operation is not supported and will throw a NotSupportedExceptionortedException + /// + /// The new length for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("BZip2InputStream SetLength not supported"); + } + + /// + /// Writes a block of bytes to this stream using data from a buffer. + /// This operation is not supported and will throw a NotSupportedException + /// + /// The buffer to source data from. + /// The offset to start obtaining data from. + /// The number of bytes of data to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("BZip2InputStream Write not supported"); + } + + /// + /// Writes a byte to the current position in the file stream. + /// This operation is not supported and will throw a NotSupportedException + /// + /// The value to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("BZip2InputStream WriteByte not supported"); + } + + /// + /// Read a sequence of bytes and advances the read position by one byte. + /// + /// Array of bytes to store values in + /// Offset in array to begin storing data + /// The maximum number of bytes to read + /// The total number of bytes read into the buffer. This might be less + /// than the number of bytes requested if that number of bytes are not + /// currently available or zero if the end of the stream is reached. + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + for (int i = 0; i < count; ++i) + { + int rb = ReadByte(); + if (rb == -1) + { + return i; + } + buffer[offset + i] = (byte)rb; + } + return count; + } + + /// + /// Closes the stream, releasing any associated resources. + /// + protected override void Dispose(bool disposing) + { + if (disposing && IsStreamOwner) + { + baseStream.Dispose(); + } + } + + /// + /// Read a byte from stream advancing position + /// + /// byte read or -1 on end of stream + public override int ReadByte() + { + if (streamEnd) + { + return -1; // ok + } + + int retChar = currentChar; + switch (currentState) + { + case RAND_PART_B_STATE: + SetupRandPartB(); + break; + + case RAND_PART_C_STATE: + SetupRandPartC(); + break; + + case NO_RAND_PART_B_STATE: + SetupNoRandPartB(); + break; + + case NO_RAND_PART_C_STATE: + SetupNoRandPartC(); + break; + + case START_BLOCK_STATE: + case NO_RAND_PART_A_STATE: + case RAND_PART_A_STATE: + break; + } + return retChar; + } + + #endregion Stream Overrides + + private void MakeMaps() + { + nInUse = 0; + for (int i = 0; i < 256; ++i) + { + if (inUse[i]) + { + seqToUnseq[nInUse] = (byte)i; + unseqToSeq[i] = (byte)nInUse; + nInUse++; + } + } + } + + private void Initialize() + { + char magic1 = BsGetUChar(); + char magic2 = BsGetUChar(); + + char magic3 = BsGetUChar(); + char magic4 = BsGetUChar(); + + if (magic1 != 'B' || magic2 != 'Z' || magic3 != 'h' || magic4 < '1' || magic4 > '9') + { + streamEnd = true; + return; + } + + SetDecompressStructureSizes(magic4 - '0'); + computedCombinedCRC = 0; + } + + private void InitBlock() + { + char magic1 = BsGetUChar(); + char magic2 = BsGetUChar(); + char magic3 = BsGetUChar(); + char magic4 = BsGetUChar(); + char magic5 = BsGetUChar(); + char magic6 = BsGetUChar(); + + if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) + { + Complete(); + return; + } + + if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) + { + BadBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = BsGetInt32(); + + blockRandomised = (BsR(1) == 1); + + GetAndMoveToFrontDecode(); + + mCrc.Reset(); + currentState = START_BLOCK_STATE; + } + + private void EndBlock() + { + computedBlockCRC = (int)mCrc.Value; + + // -- A bad CRC is considered a fatal error. -- + if (storedBlockCRC != computedBlockCRC) + { + CrcError(); + } + + // 1528150659 + computedCombinedCRC = ((computedCombinedCRC << 1) & 0xFFFFFFFF) | (computedCombinedCRC >> 31); + computedCombinedCRC = computedCombinedCRC ^ (uint)computedBlockCRC; + } + + private void Complete() + { + storedCombinedCRC = BsGetInt32(); + if (storedCombinedCRC != (int)computedCombinedCRC) + { + CrcError(); + } + + streamEnd = true; + } + + private void FillBuffer() + { + int thech = 0; + + try + { + thech = baseStream.ReadByte(); + } + catch (Exception) + { + CompressedStreamEOF(); + } + + if (thech == -1) + { + CompressedStreamEOF(); + } + + bsBuff = (bsBuff << 8) | (thech & 0xFF); + bsLive += 8; + } + + private int BsR(int n) + { + while (bsLive < n) + { + FillBuffer(); + } + + int v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); + bsLive -= n; + return v; + } + + private char BsGetUChar() + { + return (char)BsR(8); + } + + private int BsGetIntVS(int numBits) + { + return BsR(numBits); + } + + private int BsGetInt32() + { + int result = BsR(8); + result = (result << 8) | BsR(8); + result = (result << 8) | BsR(8); + result = (result << 8) | BsR(8); + return result; + } + + private void RecvDecodingTables() + { + char[][] len = new char[BZip2Constants.GroupCount][]; + for (int i = 0; i < BZip2Constants.GroupCount; ++i) + { + len[i] = new char[BZip2Constants.MaximumAlphaSize]; + } + + bool[] inUse16 = new bool[16]; + + //--- Receive the mapping table --- + for (int i = 0; i < 16; i++) + { + inUse16[i] = (BsR(1) == 1); + } + + for (int i = 0; i < 16; i++) + { + if (inUse16[i]) + { + for (int j = 0; j < 16; j++) + { + inUse[i * 16 + j] = (BsR(1) == 1); + } + } + else + { + for (int j = 0; j < 16; j++) + { + inUse[i * 16 + j] = false; + } + } + } + + MakeMaps(); + int alphaSize = nInUse + 2; + + //--- Now the selectors --- + int nGroups = BsR(3); + int nSelectors = BsR(15); + + for (int i = 0; i < nSelectors; i++) + { + int j = 0; + while (BsR(1) == 1) + { + j++; + } + selectorMtf[i] = (byte)j; + } + + //--- Undo the MTF values for the selectors. --- + byte[] pos = new byte[BZip2Constants.GroupCount]; + for (int v = 0; v < nGroups; v++) + { + pos[v] = (byte)v; + } + + for (int i = 0; i < nSelectors; i++) + { + int v = selectorMtf[i]; + byte tmp = pos[v]; + while (v > 0) + { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + + //--- Now the coding tables --- + for (int t = 0; t < nGroups; t++) + { + int curr = BsR(5); + for (int i = 0; i < alphaSize; i++) + { + while (BsR(1) == 1) + { + if (BsR(1) == 0) + { + curr++; + } + else + { + curr--; + } + } + len[t][i] = (char)curr; + } + } + + //--- Create the Huffman decoding tables --- + for (int t = 0; t < nGroups; t++) + { + int minLen = 32; + int maxLen = 0; + for (int i = 0; i < alphaSize; i++) + { + maxLen = Math.Max(maxLen, len[t][i]); + minLen = Math.Min(minLen, len[t][i]); + } + HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize); + minLens[t] = minLen; + } + } + + private void GetAndMoveToFrontDecode() + { + byte[] yy = new byte[256]; + int nextSym; + + int limitLast = BZip2Constants.BaseBlockSize * blockSize100k; + origPtr = BsGetIntVS(24); + + RecvDecodingTables(); + int EOB = nInUse + 1; + int groupNo = -1; + int groupPos = 0; + + /*-- + Setting up the unzftab entries here is not strictly + necessary, but it does save having to do it later + in a separate pass, and so saves a block's worth of + cache misses. + --*/ + for (int i = 0; i <= 255; i++) + { + unzftab[i] = 0; + } + + for (int i = 0; i <= 255; i++) + { + yy[i] = (byte)i; + } + + last = -1; + + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + int zt = selector[groupNo]; + int zn = minLens[zt]; + int zvec = BsR(zn); + int zj; + + while (zvec > limit[zt][zn]) + { + if (zn > 20) + { // the longest code + throw new BZip2Exception("Bzip data error"); + } + zn++; + while (bsLive < 1) + { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + if (zvec - baseArray[zt][zn] < 0 || zvec - baseArray[zt][zn] >= BZip2Constants.MaximumAlphaSize) + { + throw new BZip2Exception("Bzip data error"); + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + + while (true) + { + if (nextSym == EOB) + { + break; + } + + if (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB) + { + int s = -1; + int n = 1; + do + { + if (nextSym == BZip2Constants.RunA) + { + s += (0 + 1) * n; + } + else if (nextSym == BZip2Constants.RunB) + { + s += (1 + 1) * n; + } + + n <<= 1; + + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + + while (zvec > limit[zt][zn]) + { + zn++; + while (bsLive < 1) + { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + } while (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB); + + s++; + byte ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while (s > 0) + { + last++; + ll8[last] = ch; + s--; + } + + if (last >= limitLast) + { + BlockOverrun(); + } + continue; + } + else + { + last++; + if (last >= limitLast) + { + BlockOverrun(); + } + + byte tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + var j = nextSym - 1; + +#if VECTORIZE_MEMORY_MOVE + // This is vectorized memory move. Going from the back, we're taking chunks of array + // and write them at the new location shifted by one. Since chunks are VectorSize long, + // at the end we have to move "tail" (or head actually) of the array using a plain loop. + // If System.Numerics.Vector API is not available, the plain loop is used to do the whole copying. + + while(j >= VectorSize) + { + var arrayPart = new System.Numerics.Vector(yy, j - VectorSize); + arrayPart.CopyTo(yy, j - VectorSize + 1); + j -= VectorSize; + } +#endif // VECTORIZE_MEMORY_MOVE + + while(j > 0) + { + yy[j] = yy[--j]; + } + + yy[0] = tmp; + + if (groupPos == 0) + { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) + { + zn++; + while (bsLive < 1) + { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + continue; + } + } + } + + private void SetupBlock() + { + int[] cftab = new int[257]; + + cftab[0] = 0; + Array.Copy(unzftab, 0, cftab, 1, 256); + + for (int i = 1; i <= 256; i++) + { + cftab[i] += cftab[i - 1]; + } + + for (int i = 0; i <= last; i++) + { + byte ch = ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; /*-- not a char and not EOF --*/ + + if (blockRandomised) + { + rNToGo = 0; + rTPos = 0; + SetupRandPartA(); + } + else + { + SetupNoRandPartA(); + } + } + + private void SetupRandPartA() + { + if (i2 <= last) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) + { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + ch2 ^= (int)((rNToGo == 1) ? 1 : 0); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.Update(ch2); + } + else + { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupNoRandPartA() + { + if (i2 <= last) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.Update(ch2); + } + else + { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + private void SetupRandPartB() + { + if (ch2 != chPrev) + { + currentState = RAND_PART_A_STATE; + count = 1; + SetupRandPartA(); + } + else + { + count++; + if (count >= 4) + { + z = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) + { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + z ^= (byte)((rNToGo == 1) ? 1 : 0); + j2 = 0; + currentState = RAND_PART_C_STATE; + SetupRandPartC(); + } + else + { + currentState = RAND_PART_A_STATE; + SetupRandPartA(); + } + } + } + + private void SetupRandPartC() + { + if (j2 < (int)z) + { + currentChar = ch2; + mCrc.Update(ch2); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + SetupRandPartA(); + } + } + + private void SetupNoRandPartB() + { + if (ch2 != chPrev) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + SetupNoRandPartA(); + } + else + { + count++; + if (count >= 4) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + SetupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + SetupNoRandPartA(); + } + } + } + + private void SetupNoRandPartC() + { + if (j2 < (int)z) + { + currentChar = ch2; + mCrc.Update(ch2); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + SetupNoRandPartA(); + } + } + + private void SetDecompressStructureSizes(int newSize100k) + { + if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) + { + throw new BZip2Exception("Invalid block size"); + } + + blockSize100k = newSize100k; + + if (newSize100k == 0) + { + return; + } + + int n = BZip2Constants.BaseBlockSize * newSize100k; + ll8 = new byte[n]; + tt = new int[n]; + } + + private static void CompressedStreamEOF() + { + throw new EndOfStreamException("BZip2 input stream end of compressed stream"); + } + + private static void BlockOverrun() + { + throw new BZip2Exception("BZip2 input stream block overrun"); + } + + private static void BadBlockHeader() + { + throw new BZip2Exception("BZip2 input stream bad block header"); + } + + private static void CrcError() + { + throw new BZip2Exception("BZip2 input stream crc error"); + } + + private static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, int alphaSize) + { + int pp = 0; + + for (int i = minLen; i <= maxLen; ++i) + { + for (int j = 0; j < alphaSize; ++j) + { + if (length[j] == i) + { + perm[pp] = j; + ++pp; + } + } + } + + for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) + { + baseArray[i] = 0; + } + + for (int i = 0; i < alphaSize; i++) + { + ++baseArray[length[i] + 1]; + } + + for (int i = 1; i < BZip2Constants.MaximumCodeLength; i++) + { + baseArray[i] += baseArray[i - 1]; + } + + for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) + { + limit[i] = 0; + } + + int vec = 0; + + for (int i = minLen; i <= maxLen; i++) + { + vec += (baseArray[i + 1] - baseArray[i]); + limit[i] = vec - 1; + vec <<= 1; + } + + for (int i = minLen + 1; i <= maxLen; i++) + { + baseArray[i] = ((limit[i - 1] + 1) << 1) - baseArray[i]; + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2OutputStream.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2OutputStream.cs new file mode 100644 index 0000000..d7956db --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/BZip2/BZip2OutputStream.cs @@ -0,0 +1,2033 @@ +using MBS.Editor.Core.Checksum; +using MBS.Editor.Core.Checksum.Modules.BZip2CRC; +using System; +using System.IO; + +namespace MBS.Editor.Core.Compression.Modules.BZip2; + +/// +/// An output stream that compresses into the BZip2 format +/// including file header chars into another stream. +/// +public class BZip2OutputStream : Stream +{ + #region Constants + + private const int SETMASK = (1 << 21); + private const int CLEARMASK = (~SETMASK); + private const int GREATER_ICOST = 15; + private const int LESSER_ICOST = 0; + private const int SMALL_THRESH = 20; + private const int DEPTH_THRESH = 10; + + /*-- + If you are ever unlucky/improbable enough + to get a stack overflow whilst sorting, + increase the following constant and try + again. In practice I have never seen the + stack go above 27 elems, so the following + limit seems very generous. + --*/ + private const int QSORT_STACK_SIZE = 1000; + + /*-- + Knuth's increments seem to work better + than Incerpi-Sedgewick here. Possibly + because the number of elems to sort is + usually small, typically <= 20. + --*/ + + private readonly int[] increments = { + 1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484 + }; + + #endregion Constants + + #region Instance Fields + + /*-- + index of the last char in the block, so + the block size == last + 1. + --*/ + private int last; + + /*-- + index in zptr[] of original string after sorting. + --*/ + private int origPtr; + + /*-- + always: in the range 0 .. 9. + The current block size is 100000 * this number. + --*/ + private int blockSize100k; + + private bool blockRandomised; + + private int bytesOut; + private int bsBuff; + private int bsLive; + private ChecksumModule mCrc = new BZip2CRCChecksumModule(); + + private bool[] inUse = new bool[256]; + private int nInUse; + + private char[] seqToUnseq = new char[256]; + private char[] unseqToSeq = new char[256]; + + private char[] selector = new char[BZip2Constants.MaximumSelectors]; + private char[] selectorMtf = new char[BZip2Constants.MaximumSelectors]; + + private byte[] block; + private int[] quadrant; + private int[] zptr; + private short[] szptr; + private int[] ftab; + + private int nMTF; + + private int[] mtfFreq = new int[BZip2Constants.MaximumAlphaSize]; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workDone; + private int workLimit; + private bool firstAttempt; + private int nBlocksRandomised; + + private int currentChar = -1; + private int runLength; + private uint blockCRC, combinedCRC; + private int allowableBlockSize; + private readonly Stream baseStream; + private bool disposed_; + + #endregion Instance Fields + + /// + /// Construct a default output stream with maximum block size + /// + /// The stream to write BZip data onto. + public BZip2OutputStream(Stream stream) : this(stream, 9) + { + } + + /// + /// Initialise a new instance of the + /// for the specified stream, using the given blocksize. + /// + /// The stream to write compressed data to. + /// The block size to use. + /// + /// Valid block sizes are in the range 1..9, with 1 giving + /// the lowest compression and 9 the highest. + /// + public BZip2OutputStream(Stream stream, int blockSize) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + baseStream = stream; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + + workFactor = 50; + if (blockSize > 9) + { + blockSize = 9; + } + + if (blockSize < 1) + { + blockSize = 1; + } + blockSize100k = blockSize; + AllocateCompressStructures(); + Initialize(); + InitBlock(); + } + + /// + /// Ensures that resources are freed and other cleanup operations + /// are performed when the garbage collector reclaims the BZip2OutputStream. + /// + ~BZip2OutputStream() + { + Dispose(false); + } + + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return false; + } + } + + /// + /// Gets a value indicating whether the current stream supports seeking + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value indicating whether the current stream supports writing + /// + public override bool CanWrite + { + get + { + return baseStream.CanWrite; + } + } + + /// + /// Gets the length in bytes of the stream + /// + public override long Length + { + get + { + return baseStream.Length; + } + } + + /// + /// Gets or sets the current position of this stream. + /// + public override long Position + { + get + { + return baseStream.Position; + } + set + { + throw new NotSupportedException("BZip2OutputStream position cannot be set"); + } + } + + /// + /// Sets the current position of this stream to the given value. + /// + /// The point relative to the offset from which to being seeking. + /// The reference point from which to begin seeking. + /// The new position in the stream. + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("BZip2OutputStream Seek not supported"); + } + + /// + /// Sets the length of this stream to the given value. + /// + /// The new stream length. + public override void SetLength(long value) + { + throw new NotSupportedException("BZip2OutputStream SetLength not supported"); + } + + /// + /// Read a byte from the stream advancing the position. + /// + /// The byte read cast to an int; -1 if end of stream. + public override int ReadByte() + { + throw new NotSupportedException("BZip2OutputStream ReadByte not supported"); + } + + /// + /// Read a block of bytes + /// + /// The buffer to read into. + /// The offset in the buffer to start storing data at. + /// The maximum number of bytes to read. + /// The total number of bytes read. This might be less than the number of bytes + /// requested if that number of bytes are not currently available, or zero + /// if the end of the stream is reached. + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("BZip2OutputStream Read not supported"); + } + + /// + /// Write a block of bytes to the stream + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + public override void Write(byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (buffer.Length - offset < count) + { + throw new ArgumentException("Offset/count out of range"); + } + + for (int i = 0; i < count; ++i) + { + WriteByte(buffer[offset + i]); + } + } + + /// + /// Write a byte to the stream. + /// + /// The byte to write to the stream. + public override void WriteByte(byte value) + { + int b = (256 + value) % 256; + if (currentChar != -1) + { + if (currentChar == b) + { + runLength++; + if (runLength > 254) + { + WriteRun(); + currentChar = -1; + runLength = 0; + } + } + else + { + WriteRun(); + runLength = 1; + currentChar = b; + } + } + else + { + currentChar = b; + runLength++; + } + } + + private void MakeMaps() + { + nInUse = 0; + for (int i = 0; i < 256; i++) + { + if (inUse[i]) + { + seqToUnseq[nInUse] = (char)i; + unseqToSeq[i] = (char)nInUse; + nInUse++; + } + } + } + + /// + /// Get the number of bytes written to output. + /// + private void WriteRun() + { + if (last < allowableBlockSize) + { + inUse[currentChar] = true; + for (int i = 0; i < runLength; i++) + { + mCrc.Update(currentChar); + } + + switch (runLength) + { + case 1: + last++; + block[last + 1] = (byte)currentChar; + break; + + case 2: + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + break; + + case 3: + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + break; + + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)currentChar; + last++; + block[last + 1] = (byte)(runLength - 4); + break; + } + } + else + { + EndBlock(); + InitBlock(); + WriteRun(); + } + } + + /// + /// Get the number of bytes written to the output. + /// + public int BytesWritten + { + get { return bytesOut; } + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + override protected void Dispose(bool disposing) + { + try + { + try + { + base.Dispose(disposing); + if (!disposed_) + { + disposed_ = true; + + if (runLength > 0) + { + WriteRun(); + } + + currentChar = -1; + EndBlock(); + EndCompression(); + Flush(); + } + } + finally + { + if (disposing) + { + if (IsStreamOwner) + { + baseStream.Dispose(); + } + } + } + } + catch + { + } + } + + /// + /// Flush output buffers + /// + public override void Flush() + { + baseStream.Flush(); + } + + private void Initialize() + { + bytesOut = 0; + nBlocksRandomised = 0; + + /*--- Write header `magic' bytes indicating file-format == huffmanised, + followed by a digit indicating blockSize100k. + ---*/ + + BsPutUChar('B'); + BsPutUChar('Z'); + + BsPutUChar('h'); + BsPutUChar('0' + blockSize100k); + + combinedCRC = 0; + } + + private void InitBlock() + { + mCrc.Reset(); + last = -1; + + for (int i = 0; i < 256; i++) + { + inUse[i] = false; + } + + /*--- 20 is just a paranoia constant ---*/ + allowableBlockSize = BZip2Constants.BaseBlockSize * blockSize100k - 20; + } + + private void EndBlock() + { + if (last < 0) + { // dont do anything for empty files, (makes empty files compatible with original Bzip) + return; + } + + blockCRC = unchecked((uint)mCrc.Value); + combinedCRC = (combinedCRC << 1) | (combinedCRC >> 31); + combinedCRC ^= blockCRC; + + /*-- sort the block and establish position of original string --*/ + DoReversibleTransformation(); + + /*-- + A 6-byte block header, the value chosen arbitrarily + as 0x314159265359 :-). A 32 bit value does not really + give a strong enough guarantee that the value will not + appear by chance in the compressed datastream. Worst-case + probability of this event, for a 900k block, is about + 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + For a compressed file of size 100Gb -- about 100000 blocks -- + only a 48-bit marker will do. NB: normal compression/ + decompression do *not* rely on these statistical properties. + They are only important when trying to recover blocks from + damaged files. + --*/ + BsPutUChar(0x31); + BsPutUChar(0x41); + BsPutUChar(0x59); + BsPutUChar(0x26); + BsPutUChar(0x53); + BsPutUChar(0x59); + + /*-- Now the block's CRC, so it is in a known place. --*/ + unchecked + { + BsPutint((int)blockCRC); + } + + /*-- Now a single bit indicating randomisation. --*/ + if (blockRandomised) + { + BsW(1, 1); + nBlocksRandomised++; + } + else + { + BsW(1, 0); + } + + /*-- Finally, block's contents proper. --*/ + MoveToFrontCodeAndSend(); + } + + private void EndCompression() + { + /*-- + Now another magic 48-bit number, 0x177245385090, to + indicate the end of the last block. (sqrt(pi), if + you want to know. I did want to use e, but it contains + too much repetition -- 27 18 28 18 28 46 -- for me + to feel statistically comfortable. Call me paranoid.) + --*/ + BsPutUChar(0x17); + BsPutUChar(0x72); + BsPutUChar(0x45); + BsPutUChar(0x38); + BsPutUChar(0x50); + BsPutUChar(0x90); + + unchecked + { + BsPutint((int)combinedCRC); + } + + BsFinishedWithStream(); + } + + private void BsFinishedWithStream() + { + while (bsLive > 0) + { + int ch = (bsBuff >> 24); + baseStream.WriteByte((byte)ch); // write 8-bit + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void BsW(int n, int v) + { + while (bsLive >= 8) + { + int ch = (bsBuff >> 24); + unchecked { baseStream.WriteByte((byte)ch); } // write 8-bit + bsBuff <<= 8; + bsLive -= 8; + ++bytesOut; + } + bsBuff |= (v << (32 - bsLive - n)); + bsLive += n; + } + + private void BsPutUChar(int c) + { + BsW(8, c); + } + + private void BsPutint(int u) + { + BsW(8, (u >> 24) & 0xFF); + BsW(8, (u >> 16) & 0xFF); + BsW(8, (u >> 8) & 0xFF); + BsW(8, u & 0xFF); + } + + private void BsPutIntVS(int numBits, int c) + { + BsW(numBits, c); + } + + private void SendMTFValues() + { + char[][] len = new char[BZip2Constants.GroupCount][]; + for (int i = 0; i < BZip2Constants.GroupCount; ++i) + { + len[i] = new char[BZip2Constants.MaximumAlphaSize]; + } + + int gs, ge, totc, bt, bc, iter; + int nSelectors = 0, alphaSize, minLen, maxLen, selCtr; + int nGroups; + + alphaSize = nInUse + 2; + for (int t = 0; t < BZip2Constants.GroupCount; t++) + { + for (int v = 0; v < alphaSize; v++) + { + len[t][v] = (char)GREATER_ICOST; + } + } + + /*--- Decide how many coding tables to use ---*/ + if (nMTF <= 0) + { + Panic(); + } + + if (nMTF < 200) + { + nGroups = 2; + } + else if (nMTF < 600) + { + nGroups = 3; + } + else if (nMTF < 1200) + { + nGroups = 4; + } + else if (nMTF < 2400) + { + nGroups = 5; + } + else + { + nGroups = 6; + } + + /*--- Generate an initial set of coding tables ---*/ + int nPart = nGroups; + int remF = nMTF; + gs = 0; + while (nPart > 0) + { + int tFreq = remF / nPart; + int aFreq = 0; + ge = gs - 1; + while (aFreq < tFreq && ge < alphaSize - 1) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if (ge > gs && nPart != nGroups && nPart != 1 && ((nGroups - nPart) % 2 == 1)) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for (int v = 0; v < alphaSize; v++) + { + if (v >= gs && v <= ge) + { + len[nPart - 1][v] = (char)LESSER_ICOST; + } + else + { + len[nPart - 1][v] = (char)GREATER_ICOST; + } + } + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + + int[][] rfreq = new int[BZip2Constants.GroupCount][]; + for (int i = 0; i < BZip2Constants.GroupCount; ++i) + { + rfreq[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + int[] fave = new int[BZip2Constants.GroupCount]; + short[] cost = new short[BZip2Constants.GroupCount]; + /*--- + Iterate up to N_ITERS times to improve the tables. + ---*/ + for (iter = 0; iter < BZip2Constants.NumberOfIterations; ++iter) + { + for (int t = 0; t < nGroups; ++t) + { + fave[t] = 0; + } + + for (int t = 0; t < nGroups; ++t) + { + for (int v = 0; v < alphaSize; ++v) + { + rfreq[t][v] = 0; + } + } + + nSelectors = 0; + totc = 0; + gs = 0; + while (true) + { + /*--- Set group start & end marks. --*/ + if (gs >= nMTF) + { + break; + } + ge = gs + BZip2Constants.GroupSize - 1; + if (ge >= nMTF) + { + ge = nMTF - 1; + } + + /*-- + Calculate the cost of this group as coded + by each of the coding tables. + --*/ + for (int t = 0; t < nGroups; t++) + { + cost[t] = 0; + } + + if (nGroups == 6) + { + short cost0, cost1, cost2, cost3, cost4, cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for (int i = gs; i <= ge; ++i) + { + short icv = szptr[i]; + cost0 += (short)len[0][icv]; + cost1 += (short)len[1][icv]; + cost2 += (short)len[2][icv]; + cost3 += (short)len[3][icv]; + cost4 += (short)len[4][icv]; + cost5 += (short)len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for (int i = gs; i <= ge; ++i) + { + short icv = szptr[i]; + for (int t = 0; t < nGroups; t++) + { + cost[t] += (short)len[t][icv]; + } + } + } + + /*-- + Find the coding table which is best for this group, + and record its identity in the selector table. + --*/ + bc = 999999999; + bt = -1; + for (int t = 0; t < nGroups; ++t) + { + if (cost[t] < bc) + { + bc = cost[t]; + bt = t; + } + } + totc += bc; + fave[bt]++; + selector[nSelectors] = (char)bt; + nSelectors++; + + /*-- + Increment the symbol frequencies for the selected table. + --*/ + for (int i = gs; i <= ge; ++i) + { + ++rfreq[bt][szptr[i]]; + } + + gs = ge + 1; + } + + /*-- + Recompute the tables based on the accumulated frequencies. + --*/ + for (int t = 0; t < nGroups; ++t) + { + HbMakeCodeLengths(len[t], rfreq[t], alphaSize, 20); + } + } + + rfreq = null; + fave = null; + cost = null; + + if (!(nGroups < 8)) + { + Panic(); + } + + if (!(nSelectors < 32768 && nSelectors <= (2 + (900000 / BZip2Constants.GroupSize)))) + { + Panic(); + } + + /*--- Compute MTF values for the selectors. ---*/ + char[] pos = new char[BZip2Constants.GroupCount]; + char ll_i, tmp2, tmp; + + for (int i = 0; i < nGroups; i++) + { + pos[i] = (char)i; + } + + for (int i = 0; i < nSelectors; i++) + { + ll_i = selector[i]; + int j = 0; + tmp = pos[j]; + while (ll_i != tmp) + { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = (char)j; + } + + int[][] code = new int[BZip2Constants.GroupCount][]; + + for (int i = 0; i < BZip2Constants.GroupCount; ++i) + { + code[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + /*--- Assign actual codes for the tables. --*/ + for (int t = 0; t < nGroups; t++) + { + minLen = 32; + maxLen = 0; + for (int i = 0; i < alphaSize; i++) + { + if (len[t][i] > maxLen) + { + maxLen = len[t][i]; + } + if (len[t][i] < minLen) + { + minLen = len[t][i]; + } + } + if (maxLen > 20) + { + Panic(); + } + if (minLen < 1) + { + Panic(); + } + HbAssignCodes(code[t], len[t], minLen, maxLen, alphaSize); + } + + /*--- Transmit the mapping table. ---*/ + bool[] inUse16 = new bool[16]; + for (int i = 0; i < 16; ++i) + { + inUse16[i] = false; + for (int j = 0; j < 16; ++j) + { + if (inUse[i * 16 + j]) + { + inUse16[i] = true; + } + } + } + + for (int i = 0; i < 16; ++i) + { + if (inUse16[i]) + { + BsW(1, 1); + } + else + { + BsW(1, 0); + } + } + + for (int i = 0; i < 16; ++i) + { + if (inUse16[i]) + { + for (int j = 0; j < 16; ++j) + { + if (inUse[i * 16 + j]) + { + BsW(1, 1); + } + else + { + BsW(1, 0); + } + } + } + } + + /*--- Now the selectors. ---*/ + BsW(3, nGroups); + BsW(15, nSelectors); + for (int i = 0; i < nSelectors; ++i) + { + for (int j = 0; j < selectorMtf[i]; ++j) + { + BsW(1, 1); + } + BsW(1, 0); + } + + /*--- Now the coding tables. ---*/ + for (int t = 0; t < nGroups; ++t) + { + int curr = len[t][0]; + BsW(5, curr); + for (int i = 0; i < alphaSize; ++i) + { + while (curr < len[t][i]) + { + BsW(2, 2); + curr++; /* 10 */ + } + while (curr > len[t][i]) + { + BsW(2, 3); + curr--; /* 11 */ + } + BsW(1, 0); + } + } + + /*--- And finally, the block data proper ---*/ + selCtr = 0; + gs = 0; + while (true) + { + if (gs >= nMTF) + { + break; + } + ge = gs + BZip2Constants.GroupSize - 1; + if (ge >= nMTF) + { + ge = nMTF - 1; + } + + for (int i = gs; i <= ge; i++) + { + BsW(len[selector[selCtr]][szptr[i]], code[selector[selCtr]][szptr[i]]); + } + + gs = ge + 1; + ++selCtr; + } + if (!(selCtr == nSelectors)) + { + Panic(); + } + } + + private void MoveToFrontCodeAndSend() + { + BsPutIntVS(24, origPtr); + GenerateMTFValues(); + SendMTFValues(); + } + + private void SimpleSort(int lo, int hi, int d) + { + int i, j, h, bigN, hp; + int v; + + bigN = hi - lo + 1; + if (bigN < 2) + { + return; + } + + hp = 0; + while (increments[hp] < bigN) + { + hp++; + } + hp--; + + for (; hp >= 0; hp--) + { + h = increments[hp]; + + i = lo + h; + while (true) + { + /*-- copy 1 --*/ + if (i > hi) + break; + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) + break; + } + zptr[j] = v; + i++; + + /*-- copy 2 --*/ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) + { + break; + } + } + zptr[j] = v; + i++; + + /*-- copy 3 --*/ + if (i > hi) + { + break; + } + v = zptr[i]; + j = i; + while (FullGtU(zptr[j - h] + d, v + d)) + { + zptr[j] = zptr[j - h]; + j = j - h; + if (j <= (lo + h - 1)) + { + break; + } + } + zptr[j] = v; + i++; + + if (workDone > workLimit && firstAttempt) + { + return; + } + } + } + } + + private void Vswap(int p1, int p2, int n) + { + int temp = 0; + while (n > 0) + { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private void QSort3(int loSt, int hiSt, int dSt) + { + int unLo, unHi, ltLo, gtHi, med, n, m; + int lo, hi, d; + + StackElement[] stack = new StackElement[QSORT_STACK_SIZE]; + + int sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while (sp > 0) + { + if (sp >= QSORT_STACK_SIZE) + { + Panic(); + } + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if (hi - lo < SMALL_THRESH || d > DEPTH_THRESH) + { + SimpleSort(lo, hi, d); + if (workDone > workLimit && firstAttempt) + { + return; + } + continue; + } + + med = Med3(block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[(lo + hi) >> 1] + d + 1]); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while (true) + { + while (true) + { + if (unLo > unHi) + { + break; + } + n = ((int)block[zptr[unLo] + d + 1]) - med; + if (n == 0) + { + int temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + if (n > 0) + { + break; + } + unLo++; + } + + while (true) + { + if (unLo > unHi) + { + break; + } + n = ((int)block[zptr[unHi] + d + 1]) - med; + if (n == 0) + { + int temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + if (n < 0) + { + break; + } + unHi--; + } + + if (unLo > unHi) + { + break; + } + + { + int temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + } + + if (gtHi < ltLo) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ((ltLo - lo) < (unLo - ltLo)) ? (ltLo - lo) : (unLo - ltLo); + Vswap(lo, unLo - n, n); + m = ((hi - gtHi) < (gtHi - unHi)) ? (hi - gtHi) : (gtHi - unHi); + Vswap(unLo, hi - m + 1, m); + + n = lo + unLo - ltLo - 1; + m = hi - (gtHi - unHi) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void MainSort() + { + int i, j, ss, sb; + int[] runningOrder = new int[256]; + int[] copy = new int[256]; + bool[] bigDone = new bool[256]; + int c1, c2; + int numQSorted; + + /*-- + In the various block-sized structures, live data runs + from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + set up the overshoot area for block. + --*/ + + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for (i = 0; i < BZip2Constants.OvershootBytes; i++) + { + block[last + i + 2] = block[(i % (last + 1)) + 1]; + } + for (i = 0; i <= last + BZip2Constants.OvershootBytes; i++) + { + quadrant[i] = 0; + } + + block[0] = (byte)(block[last + 1]); + + if (last < 4000) + { + /*-- + Use simpleSort(), since the full sorting mechanism + has quite a large constant overhead. + --*/ + for (i = 0; i <= last; i++) + { + zptr[i] = i; + } + firstAttempt = false; + workDone = workLimit = 0; + SimpleSort(0, last, 0); + } + else + { + numQSorted = 0; + for (i = 0; i <= 255; i++) + { + bigDone[i] = false; + } + for (i = 0; i <= 65536; i++) + { + ftab[i] = 0; + } + + c1 = block[0]; + for (i = 0; i <= last; i++) + { + c2 = block[i + 1]; + ftab[(c1 << 8) + c2]++; + c1 = c2; + } + + for (i = 1; i <= 65536; i++) + { + ftab[i] += ftab[i - 1]; + } + + c1 = block[1]; + for (i = 0; i < last; i++) + { + c2 = block[i + 2]; + j = (c1 << 8) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ((block[last + 1]) << 8) + (block[1]); + ftab[j]--; + zptr[ftab[j]] = last; + + /*-- + Now ftab contains the first loc of every small bucket. + Calculate the running order, from smallest to largest + big bucket. + --*/ + + for (i = 0; i <= 255; i++) + { + runningOrder[i] = i; + } + + int vv; + int h = 1; + do + { + h = 3 * h + 1; + } while (h <= 256); + do + { + h = h / 3; + for (i = h; i <= 255; i++) + { + vv = runningOrder[i]; + j = i; + while ((ftab[((runningOrder[j - h]) + 1) << 8] - ftab[(runningOrder[j - h]) << 8]) > (ftab[((vv) + 1) << 8] - ftab[(vv) << 8])) + { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if (j <= (h - 1)) + { + break; + } + } + runningOrder[j] = vv; + } + } while (h != 1); + + /*-- + The main sorting loop. + --*/ + for (i = 0; i <= 255; i++) + { + /*-- + Process big buckets, starting with the least full. + --*/ + ss = runningOrder[i]; + + /*-- + Complete the big bucket [ss] by quicksorting + any unsorted small buckets [ss, j]. Hopefully + previous pointer-scanning phases have already + completed many of the small buckets [ss, j], so + we don't have to sort them at all. + --*/ + for (j = 0; j <= 255; j++) + { + sb = (ss << 8) + j; + if (!((ftab[sb] & SETMASK) == SETMASK)) + { + int lo = ftab[sb] & CLEARMASK; + int hi = (ftab[sb + 1] & CLEARMASK) - 1; + if (hi > lo) + { + QSort3(lo, hi, 2); + numQSorted += (hi - lo + 1); + if (workDone > workLimit && firstAttempt) + { + return; + } + } + ftab[sb] |= SETMASK; + } + } + + /*-- + The ss big bucket is now done. Record this fact, + and update the quadrant descriptors. Remember to + update quadrants in the overshoot area too, if + necessary. The "if (i < 255)" test merely skips + this updating for the last bucket processed, since + updating for the last bucket is pointless. + --*/ + bigDone[ss] = true; + + if (i < 255) + { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = (ftab[(ss + 1) << 8] & CLEARMASK) - bbStart; + int shifts = 0; + + while ((bbSize >> shifts) > 65534) + { + shifts++; + } + + for (j = 0; j < bbSize; j++) + { + int a2update = zptr[bbStart + j]; + int qVal = (j >> shifts); + quadrant[a2update] = qVal; + if (a2update < BZip2Constants.OvershootBytes) + { + quadrant[a2update + last + 1] = qVal; + } + } + + if (!(((bbSize - 1) >> shifts) <= 65535)) + { + Panic(); + } + } + + /*-- + Now scan this big bucket so as to synthesise the + sorted order for small buckets [t, ss] for all t != ss. + --*/ + for (j = 0; j <= 255; j++) + { + copy[j] = ftab[(j << 8) + ss] & CLEARMASK; + } + + for (j = ftab[ss << 8] & CLEARMASK; j < (ftab[(ss + 1) << 8] & CLEARMASK); j++) + { + c1 = block[zptr[j]]; + if (!bigDone[c1]) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for (j = 0; j <= 255; j++) + { + ftab[(j << 8) + ss] |= SETMASK; + } + } + } + } + + private void RandomiseBlock() + { + int i; + int rNToGo = 0; + int rTPos = 0; + for (i = 0; i < 256; i++) + { + inUse[i] = false; + } + + for (i = 0; i <= last; i++) + { + if (rNToGo == 0) + { + rNToGo = (int)BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) + { + rTPos = 0; + } + } + rNToGo--; + block[i + 1] ^= (byte)((rNToGo == 1) ? 1 : 0); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void DoReversibleTransformation() + { + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + MainSort(); + + if (workDone > workLimit && firstAttempt) + { + RandomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + MainSort(); + } + + origPtr = -1; + for (int i = 0; i <= last; i++) + { + if (zptr[i] == 0) + { + origPtr = i; + break; + } + } + + if (origPtr == -1) + { + Panic(); + } + } + + private bool FullGtU(int i1, int i2) + { + int k; + byte c1, c2; + int s1, s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if (c1 != c2) + { + return c1 > c2; + } + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if (s1 != s2) + { + return s1 > s2; + } + i1++; + i2++; + + if (i1 > last) + { + i1 -= last; + i1--; + } + if (i2 > last) + { + i2 -= last; + i2--; + } + + k -= 4; + ++workDone; + } while (k >= 0); + + return false; + } + + private void AllocateCompressStructures() + { + int n = BZip2Constants.BaseBlockSize * blockSize100k; + block = new byte[(n + 1 + BZip2Constants.OvershootBytes)]; + quadrant = new int[(n + BZip2Constants.OvershootBytes)]; + zptr = new int[n]; + ftab = new int[65537]; + + if (block == null || quadrant == null || zptr == null || ftab == null) + { + // int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + // compressOutOfMemory ( totalDraw, n ); + } + + /* + The back end needs a place to store the MTF values + whilst it calculates the coding tables. We could + put them in the zptr array. However, these values + will fit in a short, so we overlay szptr at the + start of zptr, in the hope of reducing the number + of cache misses induced by the multiple traversals + of the MTF values when calculating coding tables. + Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void GenerateMTFValues() + { + char[] yy = new char[256]; + int i, j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + MakeMaps(); + EOB = nInUse + 1; + + for (i = 0; i <= EOB; i++) + { + mtfFreq[i] = 0; + } + + wr = 0; + zPend = 0; + for (i = 0; i < nInUse; i++) + { + yy[i] = (char)i; + } + + for (i = 0; i <= last; i++) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while (ll_i != tmp) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + yy[0] = tmp; + + if (j == 0) + { + zPend++; + } + else + { + if (zPend > 0) + { + zPend--; + while (true) + { + switch (zPend % 2) + { + case 0: + szptr[wr] = (short)BZip2Constants.RunA; + wr++; + mtfFreq[BZip2Constants.RunA]++; + break; + + case 1: + szptr[wr] = (short)BZip2Constants.RunB; + wr++; + mtfFreq[BZip2Constants.RunB]++; + break; + } + if (zPend < 2) + { + break; + } + zPend = (zPend - 2) / 2; + } + zPend = 0; + } + szptr[wr] = (short)(j + 1); + wr++; + mtfFreq[j + 1]++; + } + } + + if (zPend > 0) + { + zPend--; + while (true) + { + switch (zPend % 2) + { + case 0: + szptr[wr] = (short)BZip2Constants.RunA; + wr++; + mtfFreq[BZip2Constants.RunA]++; + break; + + case 1: + szptr[wr] = (short)BZip2Constants.RunB; + wr++; + mtfFreq[BZip2Constants.RunB]++; + break; + } + if (zPend < 2) + { + break; + } + zPend = (zPend - 2) / 2; + } + } + + szptr[wr] = (short)EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + private static void Panic() + { + throw new BZip2Exception("BZip2 output stream panic"); + } + + private static void HbMakeCodeLengths(char[] len, int[] freq, int alphaSize, int maxLen) + { + /*-- + Nodes and heap entries run from 1. Entry 0 + for both the heap and nodes is a sentinel. + --*/ + int nNodes, nHeap, n1, n2, j, k; + bool tooLong; + + int[] heap = new int[BZip2Constants.MaximumAlphaSize + 2]; + int[] weight = new int[BZip2Constants.MaximumAlphaSize * 2]; + int[] parent = new int[BZip2Constants.MaximumAlphaSize * 2]; + + for (int i = 0; i < alphaSize; ++i) + { + weight[i + 1] = (freq[i] == 0 ? 1 : freq[i]) << 8; + } + + while (true) + { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for (int i = 1; i <= alphaSize; ++i) + { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + int zz = nHeap; + int tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + if (!(nHeap < (BZip2Constants.MaximumAlphaSize + 2))) + { + Panic(); + } + + while (nHeap > 1) + { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + int zz = 1; + int yy = 0; + int tmp = heap[zz]; + while (true) + { + yy = zz << 1; + if (yy > nHeap) + { + break; + } + if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) + { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) + { + break; + } + + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + + zz = 1; + yy = 0; + tmp = heap[zz]; + while (true) + { + yy = zz << 1; + if (yy > nHeap) + { + break; + } + if (yy < nHeap && weight[heap[yy + 1]] < weight[heap[yy]]) + { + yy++; + } + if (weight[tmp] < weight[heap[yy]]) + { + break; + } + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = (int)((weight[n1] & 0xffffff00) + (weight[n2] & 0xffffff00)) | + (int)(1 + (((weight[n1] & 0x000000ff) > (weight[n2] & 0x000000ff)) ? (weight[n1] & 0x000000ff) : (weight[n2] & 0x000000ff))); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + + zz = nHeap; + tmp = heap[zz]; + while (weight[tmp] < weight[heap[zz >> 1]]) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + if (!(nNodes < (BZip2Constants.MaximumAlphaSize * 2))) + { + Panic(); + } + + tooLong = false; + for (int i = 1; i <= alphaSize; ++i) + { + j = 0; + k = i; + while (parent[k] >= 0) + { + k = parent[k]; + j++; + } + len[i - 1] = (char)j; + tooLong |= j > maxLen; + } + + if (!tooLong) + { + break; + } + + for (int i = 1; i < alphaSize; ++i) + { + j = weight[i] >> 8; + j = 1 + (j / 2); + weight[i] = j << 8; + } + } + } + + private static void HbAssignCodes(int[] code, char[] length, int minLen, int maxLen, int alphaSize) + { + int vec = 0; + for (int n = minLen; n <= maxLen; ++n) + { + for (int i = 0; i < alphaSize; ++i) + { + if (length[i] == n) + { + code[i] = vec; + ++vec; + } + } + vec <<= 1; + } + } + + private static byte Med3(byte a, byte b, byte c) + { + byte t; + if (a > b) + { + t = a; + a = b; + b = t; + } + if (b > c) + { + t = b; + b = c; + c = t; + } + if (a > b) + { + b = a; + } + return b; + } + + private struct StackElement + { + public int ll; + public int hh; + public int dd; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/Deflate/DeflateCompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/Deflate/DeflateCompressionModule.cs new file mode 100644 index 0000000..a08b22f --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/Deflate/DeflateCompressionModule.cs @@ -0,0 +1,16 @@ +using System.IO.Compression; +using MBS.Editor.Core.Compression; + +namespace MBS.Editor.Core.Compression.Modules.Deflate; + +public class DeflateCompressionModule : SystemCompressionModule +{ + protected override DeflateStream CreateCompressor(Stream stream) + { + return new DeflateStream(stream, GetSystemCompressionLevel()); + } + protected override DeflateStream CreateDecompressor(Stream stream) + { + return new DeflateStream(stream, CompressionMode.Decompress); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/GZip/GZipCompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/GZip/GZipCompressionModule.cs new file mode 100644 index 0000000..2a4b3b3 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/GZip/GZipCompressionModule.cs @@ -0,0 +1,37 @@ +using System.IO.Compression; +using MBS.Editor.Core.Compression; + +namespace MBS.Editor.Core.Compression.Modules.GZip; + +public class GZipCompressionModule : SystemCompressionModule +{ + protected override GZipStream CreateCompressor(Stream stream) + { + return new GZipStream(stream, GetSystemCompressionLevel()); + } + protected override GZipStream CreateDecompressor(Stream stream) + { + return new GZipStream(stream, System.IO.Compression.CompressionMode.Decompress); + } + + /* + protected override void CompressInternal(byte[] buffer, int offset, int length) + { + if (_compressor == null) + { + MemoryStream ms = new MemoryStream(); + _compressor = new GZipStream(ms, GetSystemCompressionLevel()); + } + _compressor.Write(buffer, offset, length); + } + protected override int DecompressInternal(byte[] buffer, int offset, int length) + { + if (_decompressor == null) + { + MemoryStream ms = new MemoryStream(); + _decompressor = new GZipStream(ms, GetSystemCompressionLevel()); + } + return _decompressor.Read(buffer, offset, length); + } + */ +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwConstants.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwConstants.cs new file mode 100644 index 0000000..cccf728 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwConstants.cs @@ -0,0 +1,62 @@ +namespace MBS.Editor.Core.Compression.Modules.LZW.Internal; + +/// +/// This class contains constants used for LZW +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")] +sealed public class LzwConstants +{ + /// + /// Magic number found at start of LZW header: 0x1f 0x9d + /// + public const int MAGIC = 0x1f9d; + + /// + /// Maximum number of bits per code + /// + public const int MAX_BITS = 16; + + /* 3rd header byte: + * bit 0..4 Number of compression bits + * bit 5 Extended header + * bit 6 Free + * bit 7 Block mode + */ + + /// + /// Mask for 'number of compression bits' + /// + public const int BIT_MASK = 0x1f; + + /// + /// Indicates the presence of a fourth header byte + /// + public const int EXTENDED_MASK = 0x20; + + //public const int FREE_MASK = 0x40; + + /// + /// Reserved bits + /// + public const int RESERVED_MASK = 0x60; + + /// + /// Block compression: if table is full and compression rate is dropping, + /// clear the dictionary. + /// + public const int BLOCK_MODE_MASK = 0x80; + + /// + /// LZW file header size (in bytes) + /// + public const int HDR_SIZE = 3; + + /// + /// Initial number of bits per code + /// + public const int INIT_BITS = 9; + + private LzwConstants() + { + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwInputStream.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwInputStream.cs new file mode 100644 index 0000000..6f89c8e --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/Internal/LzwInputStream.cs @@ -0,0 +1,571 @@ +using System; +using System.IO; + +namespace MBS.Editor.Core.Compression.Modules.LZW.Internal; + +/// +/// This filter stream is used to decompress a LZW format stream. +/// Specifically, a stream that uses the LZC compression method. +/// This file format is usually associated with the .Z file extension. +/// +/// See http://en.wikipedia.org/wiki/Compress +/// See http://wiki.wxwidgets.org/Development:_Z_File_Format +/// +/// The file header consists of 3 (or optionally 4) bytes. The first two bytes +/// contain the magic marker "0x1f 0x9d", followed by a byte of flags. +/// +/// Based on Java code by Ronald Tschalar, which in turn was based on the unlzw.c +/// code in the gzip package. +/// +/// This sample shows how to unzip a compressed file +/// +/// using System; +/// using System.IO; +/// +/// using ICSharpCode.SharpZipLib.Core; +/// using ICSharpCode.SharpZipLib.LZW; +/// +/// class MainClass +/// { +/// public static void Main(string[] args) +/// { +/// using (Stream inStream = new LzwInputStream(File.OpenRead(args[0]))) +/// using (FileStream outStream = File.Create(Path.GetFileNameWithoutExtension(args[0]))) { +/// byte[] buffer = new byte[4096]; +/// StreamUtils.Copy(inStream, outStream, buffer); +/// // OR +/// inStream.Read(buffer, 0, buffer.Length); +/// // now do something with the buffer +/// } +/// } +/// } +/// +/// +public class LzwInputStream : Stream +{ + /// + /// Gets or sets a flag indicating ownership of underlying stream. + /// When the flag is true will close the underlying stream also. + /// + /// The default value is true. + public bool IsStreamOwner { get; set; } = true; + + /// + /// Creates a LzwInputStream + /// + /// + /// The stream to read compressed data from (baseInputStream LZW format) + /// + public LzwInputStream(Stream baseInputStream) + { + this.baseInputStream = baseInputStream; + } + + /// + /// See + /// + /// + public override int ReadByte() + { + int b = Read(one, 0, 1); + if (b == 1) + return (one[0] & 0xff); + return -1; + } + + /// + /// Reads decompressed data into the provided buffer byte array + /// + /// + /// The array to read and decompress data into + /// + /// + /// The offset indicating where the data should be placed + /// + /// + /// The number of bytes to decompress + /// + /// The number of bytes read. Zero signals the end of stream + public override int Read(byte[] buffer, int offset, int count) + { + if (!headerParsed) + ParseHeader(); + + if (eof) + return 0; + + int start = offset; + + /* Using local copies of various variables speeds things up by as + * much as 30% in Java! Performance not tested in C#. + */ + int[] lTabPrefix = tabPrefix; + byte[] lTabSuffix = tabSuffix; + byte[] lStack = stack; + int lNBits = nBits; + int lMaxCode = maxCode; + int lMaxMaxCode = maxMaxCode; + int lBitMask = bitMask; + int lOldCode = oldCode; + byte lFinChar = finChar; + int lStackP = stackP; + int lFreeEnt = freeEnt; + byte[] lData = data; + int lBitPos = bitPos; + + // empty stack if stuff still left + int sSize = lStack.Length - lStackP; + if (sSize > 0) + { + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + } + + if (count == 0) + { + stackP = lStackP; + return offset - start; + } + + // loop, filling local buffer until enough data has been decompressed + MainLoop: + do + { + if (end < EXTRA) + { + Fill(); + } + + int bitIn = (got > 0) ? (end - end % lNBits) << 3 : + (end << 3) - (lNBits - 1); + + while (lBitPos < bitIn) + { + #region A + + // handle 1-byte reads correctly + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + maxMaxCode = lMaxMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } + + // check for code-width expansion + if (lFreeEnt > lMaxCode) + { + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + + nBytes - (lBitPos - 1 + nBytes) % nBytes; + + lNBits++; + lMaxCode = (lNBits == maxBits) ? lMaxMaxCode : + (1 << lNBits) - 1; + + lBitMask = (1 << lNBits) - 1; + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + #endregion A + + #region B + + // read next code + int pos = lBitPos >> 3; + int code = (((lData[pos] & 0xFF) | + ((lData[pos + 1] & 0xFF) << 8) | + ((lData[pos + 2] & 0xFF) << 16)) >> + (lBitPos & 0x7)) & lBitMask; + + lBitPos += lNBits; + + // handle first iteration + if (lOldCode == -1) + { + if (code >= 256) + throw new LZWException("corrupt input: " + code + " > 255"); + + lFinChar = (byte)(lOldCode = code); + buffer[offset++] = lFinChar; + count--; + continue; + } + + // handle CLEAR code + if (code == TBL_CLEAR && blockMode) + { + Array.Copy(zeros, 0, lTabPrefix, 0, zeros.Length); + lFreeEnt = TBL_FIRST - 1; + + int nBytes = lNBits << 3; + lBitPos = (lBitPos - 1) + nBytes - (lBitPos - 1 + nBytes) % nBytes; + lNBits = LzwConstants.INIT_BITS; + lMaxCode = (1 << lNBits) - 1; + lBitMask = lMaxCode; + + // Code tables reset + + lBitPos = ResetBuf(lBitPos); + goto MainLoop; + } + + #endregion B + + #region C + + // setup + int inCode = code; + lStackP = lStack.Length; + + // Handle KwK case + if (code >= lFreeEnt) + { + if (code > lFreeEnt) + { + throw new LZWException("corrupt input: code=" + code + + ", freeEnt=" + lFreeEnt); + } + + lStack[--lStackP] = lFinChar; + code = lOldCode; + } + + // Generate output characters in reverse order + while (code >= 256) + { + lStack[--lStackP] = lTabSuffix[code]; + code = lTabPrefix[code]; + } + + lFinChar = lTabSuffix[code]; + buffer[offset++] = lFinChar; + count--; + + // And put them out in forward order + sSize = lStack.Length - lStackP; + int num = (sSize >= count) ? count : sSize; + Array.Copy(lStack, lStackP, buffer, offset, num); + offset += num; + count -= num; + lStackP += num; + + #endregion C + + #region D + + // generate new entry in table + if (lFreeEnt < lMaxMaxCode) + { + lTabPrefix[lFreeEnt] = lOldCode; + lTabSuffix[lFreeEnt] = lFinChar; + lFreeEnt++; + } + + // Remember previous code + lOldCode = inCode; + + // if output buffer full, then return + if (count == 0) + { + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + return offset - start; + } + + #endregion D + } // while + + lBitPos = ResetBuf(lBitPos); + } while (got > 0); // do..while + + nBits = lNBits; + maxCode = lMaxCode; + bitMask = lBitMask; + oldCode = lOldCode; + finChar = lFinChar; + stackP = lStackP; + freeEnt = lFreeEnt; + bitPos = lBitPos; + + eof = true; + return offset - start; + } + + /// + /// Moves the unread data in the buffer to the beginning and resets + /// the pointers. + /// + /// + /// + private int ResetBuf(int bitPosition) + { + int pos = bitPosition >> 3; + Array.Copy(data, pos, data, 0, end - pos); + end -= pos; + return 0; + } + + private void Fill() + { + got = baseInputStream.Read(data, end, data.Length - 1 - end); + if (got > 0) + { + end += got; + } + } + + private void ParseHeader() + { + headerParsed = true; + + byte[] hdr = new byte[LzwConstants.HDR_SIZE]; + + int result = baseInputStream.Read(hdr, 0, hdr.Length); + + // Check the magic marker + if (result < 0) + throw new LZWException("Failed to read LZW header"); + + if (hdr[0] != (LzwConstants.MAGIC >> 8) || hdr[1] != (LzwConstants.MAGIC & 0xff)) + { + throw new LZWException(String.Format( + "Wrong LZW header. Magic bytes don't match. 0x{0:x2} 0x{1:x2}", + hdr[0], hdr[1])); + } + + // Check the 3rd header byte + blockMode = (hdr[2] & LzwConstants.BLOCK_MODE_MASK) > 0; + maxBits = hdr[2] & LzwConstants.BIT_MASK; + + if (maxBits > LzwConstants.MAX_BITS) + { + throw new LZWException("Stream compressed with " + maxBits + + " bits, but decompression can only handle " + + LzwConstants.MAX_BITS + " bits."); + } + + if ((hdr[2] & LzwConstants.RESERVED_MASK) > 0) + { + throw new LZWException("Unsupported bits set in the header."); + } + + // Initialize variables + maxMaxCode = 1 << maxBits; + nBits = LzwConstants.INIT_BITS; + maxCode = (1 << nBits) - 1; + bitMask = maxCode; + oldCode = -1; + finChar = 0; + freeEnt = blockMode ? TBL_FIRST : 256; + + tabPrefix = new int[1 << maxBits]; + tabSuffix = new byte[1 << maxBits]; + stack = new byte[1 << maxBits]; + stackP = stack.Length; + + for (int idx = 255; idx >= 0; idx--) + tabSuffix[idx] = (byte)idx; + } + + #region Stream Overrides + + /// + /// Gets a value indicating whether the current stream supports reading + /// + public override bool CanRead + { + get + { + return baseInputStream.CanRead; + } + } + + /// + /// Gets a value of false indicating seeking is not supported for this stream. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Gets a value of false indicating that this stream is not writeable. + /// + public override bool CanWrite + { + get + { + return false; + } + } + + /// + /// A value representing the length of the stream in bytes. + /// + public override long Length + { + get + { + return got; + } + } + + /// + /// The current position within the stream. + /// Throws a NotSupportedException when attempting to set the position + /// + /// Attempting to set the position + public override long Position + { + get + { + return baseInputStream.Position; + } + set + { + throw new NotSupportedException("InflaterInputStream Position not supported"); + } + } + + /// + /// Flushes the baseInputStream + /// + public override void Flush() + { + baseInputStream.Flush(); + } + + /// + /// Sets the position within the current stream + /// Always throws a NotSupportedException + /// + /// The relative offset to seek to. + /// The defining where to seek from. + /// The new position in the stream. + /// Any access + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seek not supported"); + } + + /// + /// Set the length of the current stream + /// Always throws a NotSupportedException + /// + /// The new length value for the stream. + /// Any access + public override void SetLength(long value) + { + throw new NotSupportedException("InflaterInputStream SetLength not supported"); + } + + /// + /// Writes a sequence of bytes to stream and advances the current position + /// This method always throws a NotSupportedException + /// + /// The buffer containing data to write. + /// The offset of the first byte to write. + /// The number of bytes to write. + /// Any access + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("InflaterInputStream Write not supported"); + } + + /// + /// Writes one byte to the current stream and advances the current position + /// Always throws a NotSupportedException + /// + /// The byte to write. + /// Any access + public override void WriteByte(byte value) + { + throw new NotSupportedException("InflaterInputStream WriteByte not supported"); + } + + /// + /// Closes the input stream. When + /// is true the underlying stream is also closed. + /// + protected override void Dispose(bool disposing) + { + if (!isClosed) + { + isClosed = true; + if (IsStreamOwner) + { + baseInputStream.Dispose(); + } + } + } + + #endregion Stream Overrides + + #region Instance Fields + + private Stream baseInputStream; + + /// + /// Flag indicating wether this instance has been closed or not. + /// + private bool isClosed; + + private readonly byte[] one = new byte[1]; + private bool headerParsed; + + // string table stuff + private const int TBL_CLEAR = 0x100; + + private const int TBL_FIRST = TBL_CLEAR + 1; + + private int[] tabPrefix; + private byte[] tabSuffix; + private readonly int[] zeros = new int[256]; + private byte[] stack; + + // various state + private bool blockMode; + + private int nBits; + private int maxBits; + private int maxMaxCode; + private int maxCode; + private int bitMask; + private int oldCode; + private byte finChar; + private int stackP; + private int freeEnt; + + // input buffer + private readonly byte[] data = new byte[1024 * 8]; + + private int bitPos; + private int end; + private int got; + private bool eof; + private const int EXTRA = 64; + + #endregion Instance Fields +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWCompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWCompressionModule.cs new file mode 100644 index 0000000..973f6a7 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWCompressionModule.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; +using MBS.Editor.Core.Compression.Modules.LZW.Internal; + +namespace MBS.Editor.Core.Compression.Modules.LZW; + +public class LZWCompressionModule : SystemCompressionModule +{ + protected override LzwInputStream CreateCompressor(Stream stream) + { + throw new NotImplementedException(); + } + + protected override LzwInputStream CreateDecompressor(Stream stream) + { + return new LzwInputStream(stream); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWException.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWException.cs new file mode 100644 index 0000000..81a5462 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/LZW/LZWException.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.Serialization; + +namespace MBS.Editor.Core.Compression.Modules.LZW; + +/// +/// LZWException represents exceptions specific to LZW classes and code. +/// +public class LZWException : CompressionException +{ + /// + /// Initialise a new instance of . + /// + public LZWException() + { + } + + /// + /// Initialise a new instance of with its message string. + /// + /// A that describes the error. + public LZWException(string message) + : base(message) + { + } + + /// + /// Initialise a new instance of . + /// + /// A that describes the error. + /// The that caused this exception. + public LZWException(string message, Exception innerException) + : base(message, innerException) + { + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/ZlibBuiltin/ZlibBuiltinCompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/ZlibBuiltin/ZlibBuiltinCompressionModule.cs new file mode 100644 index 0000000..37f294c --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/Modules/ZlibBuiltin/ZlibBuiltinCompressionModule.cs @@ -0,0 +1,14 @@ +using System.IO.Compression; +using MBS.Editor.Core.Compression; + +public class ZlibBuiltinCompressionModule : SystemCompressionModule +{ + protected override ZLibStream CreateCompressor(Stream stream) + { + return new ZLibStream(stream, GetSystemCompressionLevel()); + } + protected override ZLibStream CreateDecompressor(Stream stream) + { + return new ZLibStream(stream, CompressionMode.Decompress); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionLevel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionLevel.cs new file mode 100644 index 0000000..b5df694 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionLevel.cs @@ -0,0 +1,26 @@ +namespace MBS.Editor.Core.Compression; + +/// +/// Specifies values that indicate whether a compression operation emphasizes speed or compression size. +/// +public enum SystemCompressionLevel +{ + /// + /// The compression operation should optimally balance compression speed and output size. + /// + Optimal = 0, + /// + /// The compression operation should complete as quickly as possible, even if the resulting + /// file is not optimally compressed. + /// + Fastest = 1, + /// + /// No compression should be performed on the file. + /// + NoCompression = 2, + /// + /// The compression operation should create output as small as possible, even if + /// the operation takes a longer time to complete. + /// + SmallestSize = 3 +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionModule.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionModule.cs new file mode 100644 index 0000000..d01f1e1 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Compression/SystemCompressionModule.cs @@ -0,0 +1,44 @@ +using System.IO.Compression; + +namespace MBS.Editor.Core.Compression; + +public abstract class SystemCompressionModule : CompressionModule where TStream : Stream +{ + public SystemCompressionLevel CompressionLevel { get; set; } = SystemCompressionLevel.Optimal; + + + protected abstract TStream CreateCompressor(Stream stream); + protected abstract TStream CreateDecompressor(Stream stream); + + protected override void CompressInternal(Stream inputStream, Stream outputStream) + { + TStream _compressor = CreateCompressor(outputStream); + inputStream.CopyTo(_compressor); + + // !!! IMPORTANT !!! DO NOT FORGET TO FLUSH !!! + _compressor.Flush(); + _compressor.Close(); + } + protected override void DecompressInternal(Stream inputStream, Stream outputStream) + { + TStream _decompressor = CreateDecompressor(inputStream); + _decompressor.CopyTo(outputStream); + + // !!! IMPORTANT !!! DO NOT FORGET TO FLUSH !!! + _decompressor.Flush(); + _decompressor.Close(); + } + + protected CompressionLevel GetSystemCompressionLevel() + { + switch (CompressionLevel) + { + case SystemCompressionLevel.Fastest: return System.IO.Compression.CompressionLevel.Fastest; + case SystemCompressionLevel.NoCompression: return System.IO.Compression.CompressionLevel.NoCompression; + case SystemCompressionLevel.Optimal: return System.IO.Compression.CompressionLevel.Optimal; + case SystemCompressionLevel.SmallestSize: return System.IO.Compression.CompressionLevel.SmallestSize; + } + throw new ArgumentException("no System.IO.Compression.CompressionLevel matches the given SystemCompressionLevel"); + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs index a3d33aa..566e0ea 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs @@ -1,6 +1,38 @@ namespace MBS.Editor.Core; -public class DataFormat +public abstract class DataFormat { + public void Load(ObjectModel objectModel, Stream stream) + { + LoadInternal(objectModel, stream); + } + protected abstract void LoadInternal(ObjectModel objectModel, Stream stream); + + public void Save(ObjectModel objectModel, Stream stream) + { + SaveInternal(objectModel, stream); + } + protected abstract void SaveInternal(ObjectModel objectModel, Stream stream); + + + + public static T FromType() where T : DataFormat, new() + { + T objectModel = new T(); + return objectModel; + } + public static DataFormat FromType(Type type) + { + if (type.IsAbstract || !type.IsSubclassOf(typeof(DataFormat))) + { + throw new InvalidCastException("type must be a non-abstract subclass of DataFormat"); + } + DataFormat? objectModel = type.Assembly.CreateInstance(type.FullName) as DataFormat; + if (objectModel == null) + { + throw new TypeLoadException("could not create DataFormat from type name"); + } + return objectModel; + } } \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormatMetadata.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormatMetadata.cs new file mode 100644 index 0000000..b53fdf7 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormatMetadata.cs @@ -0,0 +1,10 @@ +namespace MBS.Editor.Core; + +using MBS.Core.Settings; + +public class DataFormatMetadata +{ + public SettingsProvider ExportSettings { get; } + public SettingsProvider ImportSettings { get; } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/ZIP/ZIPDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/ZIP/ZIPDataFormat.cs new file mode 100644 index 0000000..160810c --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/ZIP/ZIPDataFormat.cs @@ -0,0 +1,13 @@ + +namespace MBS.Editor.Core.DataFormats.FileSystem.ZIP; + +public class ZIPDataFormat : DataFormat +{ + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + + } + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Document.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Document.cs new file mode 100644 index 0000000..7f6ba86 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Document.cs @@ -0,0 +1,41 @@ +namespace MBS.Editor.Core; + +public class Document +{ + public ObjectModel ObjectModel { get; set; } + public DataFormat DataFormat { get; set; } + + public Stream InputStream { get; set; } + public Stream OutputStream { get; set; } + + public Document(ObjectModel objectModel, DataFormat dataFormat, Stream stream) : this(objectModel, dataFormat, stream, stream) { } + public Document(ObjectModel objectModel, DataFormat dataFormat, Stream inputStream, Stream outputStream) + { + ObjectModel = objectModel; + DataFormat = dataFormat; + InputStream = inputStream; + OutputStream = outputStream; + } + + public void Load() + { + DataFormat.Load(ObjectModel, InputStream); + } + public void Save() + { + DataFormat.Save(ObjectModel, OutputStream); + } + + public static Document Load(ObjectModel objectModel, DataFormat dataFormat, Stream stream) + { + Document doc = new Document(objectModel, dataFormat, stream); + doc.Load(); + return doc; + } + public static Document Save(ObjectModel objectModel, DataFormat dataFormat, Stream stream) + { + Document doc = new Document(objectModel, dataFormat, stream); + doc.Save(); + return doc; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessage.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessage.cs new file mode 100644 index 0000000..a93787e --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessage.cs @@ -0,0 +1,138 @@ +namespace MBS.Editor.Core.Hosting; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +public delegate void HostApplicationMessageModifyingEventHandler(object sender, HostApplicationMessageModifyingEventArgs e); +public class HostApplicationMessageModifyingEventArgs + : CancelEventArgs +{ + public HostApplicationMessageModifyingEventArgs(HostApplicationMessage message) + { + Message = message; + } + + public HostApplicationMessage Message { get; } +} +public delegate void HostApplicationMessageModifiedEventHandler(object sender, HostApplicationMessageModifiedEventArgs e); +public class HostApplicationMessageModifiedEventArgs + : EventArgs +{ + public HostApplicationMessageModifiedEventArgs(HostApplicationMessage message) + { + Message = message; + } + + public HostApplicationMessage Message { get; } +} + +public class HostApplicationMessage +{ + public class HostApplicationMessageCollection + : System.Collections.ObjectModel.Collection + { + public event HostApplicationMessageModifyingEventHandler? MessageAdding; + public event HostApplicationMessageModifyingEventHandler? MessageRemoving; + + public event HostApplicationMessageModifiedEventHandler? MessageAdded; + public event HostApplicationMessageModifiedEventHandler? MessageRemoved; + + public HostApplicationMessage Add(HostApplicationMessageSeverity severity, string description, string fileName = null, int? lineNumber = null, int? columnNumber = null, string projectName = null) + { + HostApplicationMessage message = new HostApplicationMessage(); + message.Severity = severity; + message.Description = description; + message.FileName = fileName; + message.LineNumber = lineNumber; + message.ColumnNumber = columnNumber; + message.ProjectName = projectName; + Add(message); + return message; + } + + protected virtual void OnMessageAdding(HostApplicationMessageModifyingEventArgs e) + { + if (MessageAdding != null) + { + MessageAdding(this, e); + } + } + protected virtual void OnMessageAdded(HostApplicationMessageModifiedEventArgs e) + { + if (MessageAdded != null) + { + MessageAdded(this, e); + } + } + + protected virtual void OnMessageRemoving(HostApplicationMessageModifyingEventArgs e) + { + if (MessageRemoving != null) + { + MessageRemoving(this, e); + } + } + protected virtual void OnMessageRemoved(HostApplicationMessageModifiedEventArgs e) + { + if (MessageRemoved != null) + { + MessageRemoved(this, e); + } + } + + public event EventHandler? MessagesCleared; + protected virtual void OnMessagesCleared(EventArgs e) + { + if (MessagesCleared != null) MessagesCleared(this, e); + } + + protected override void InsertItem(int index, HostApplicationMessage item) + { + HostApplicationMessage message = item; + HostApplicationMessageModifyingEventArgs e = new HostApplicationMessageModifyingEventArgs(message); + OnMessageAdding(e); + if (e.Cancel) return; + + base.InsertItem(index, item); + + OnMessageAdded(new HostApplicationMessageModifiedEventArgs(message)); + } + protected override void RemoveItem(int index) + { + HostApplicationMessage message = this[index]; + HostApplicationMessageModifyingEventArgs e = new HostApplicationMessageModifyingEventArgs(message); + OnMessageRemoving(e); + if (e.Cancel) return; + + base.RemoveItem(index); + + OnMessageRemoved(new HostApplicationMessageModifiedEventArgs(message)); + } + protected override void ClearItems() + { + base.ClearItems(); + OnMessagesCleared(EventArgs.Empty); + } + } + + private HostApplicationMessageSeverity mvarSeverity = HostApplicationMessageSeverity.None; + public HostApplicationMessageSeverity Severity { get { return mvarSeverity; } set { mvarSeverity = value; } } + + private string mvarDescription = String.Empty; + public string Description { get { return mvarDescription; } set { mvarDescription = value; } } + + private string mvarFileName = null; + public string FileName { get { return mvarFileName; } set { mvarFileName = value; } } + + private int? mvarLineNumber = null; + public int? LineNumber { get { return mvarLineNumber; } set { mvarLineNumber = value; } } + + private int? mvarColumnNumber = null; + public int? ColumnNumber { get { return mvarColumnNumber; } set { mvarColumnNumber = value; } } + + private string mvarProjectName = null; + public string ProjectName { get { return mvarProjectName; } set { mvarProjectName = value; } } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessageSeverity.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessageSeverity.cs new file mode 100644 index 0000000..e3a6d70 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationMessageSeverity.cs @@ -0,0 +1,9 @@ +namespace MBS.Editor.Core.Hosting; + +public enum HostApplicationMessageSeverity +{ + None = 0, + Notice = 1, + Warning = 2, + Error = 3 +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationOutputWindow.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationOutputWindow.cs new file mode 100644 index 0000000..aa9c156 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostApplicationOutputWindow.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MBS.Editor.Core.Hosting; + +/// +/// Handles the output window in Universal Editor. If the user is running a plugin which makes +/// use of these features in non-GUI mode, it will write to the console instead. +/// +public class HostApplicationOutputWindow +{ + public event TextWrittenEventHandler? TextWritten; + protected virtual void OnTextWritten(TextWrittenEventArgs e) + { + if (TextWritten != null) + { + TextWritten(this, e); + } + else + { + Console.Write(e.Text); + } + } + + public event EventHandler? TextCleared; + protected virtual void OnTextCleared(EventArgs e) + { + if (TextCleared != null) + { + TextCleared(this, e); + } + else + { + Console.Clear(); + } + } + + public void Clear() + { + OnTextCleared(EventArgs.Empty); + } + public void Write(string text) + { + OnTextWritten(new TextWrittenEventArgs(text)); + } + public void WriteLine(string text) + { + Write(text + System.Environment.NewLine); + } +} + +public delegate void TextWrittenEventHandler(object sender, TextWrittenEventArgs e); +public class TextWrittenEventArgs +{ + private string mvarText = String.Empty; + public string Text { get { return mvarText; } } + + public TextWrittenEventArgs(string text) + { + mvarText = text; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostServices.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostServices.cs new file mode 100644 index 0000000..daf8c6c --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/HostServices.cs @@ -0,0 +1,7 @@ +namespace MBS.Editor.Core.Hosting; + +public class HostServices +{ + public HostApplicationMessage.HostApplicationMessageCollection Messages { get; } = new HostApplicationMessage.HostApplicationMessageCollection(); + public HostApplicationOutputWindow OutputWindow { get; } = new HostApplicationOutputWindow(); +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/IHostApplication.cs b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/IHostApplication.cs new file mode 100644 index 0000000..f28c6a7 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/Hosting/IHostApplication.cs @@ -0,0 +1,6 @@ +namespace MBS.Editor.Core.Hosting; + +public interface IHostApplication +{ + HostServices HostServices { get; } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Endianness.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Endianness.cs new file mode 100644 index 0000000..402d672 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Endianness.cs @@ -0,0 +1,7 @@ +namespace MBS.Editor.Core.IO; + +public enum Endianness +{ + LittleEndian, + BigEndian +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/NewLineSequence.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/NewLineSequence.cs new file mode 100644 index 0000000..dd6b86b --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/NewLineSequence.cs @@ -0,0 +1,11 @@ +namespace MBS.Editor.Core.IO; + +public enum NewLineSequence +{ + Automatic, + SystemDefault, + CarriageReturn, + LineFeed, + CarriageReturnLineFeed, + LineFeedCarriageReturn +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs new file mode 100644 index 0000000..305afe3 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs @@ -0,0 +1,1848 @@ +// +// Reader.cs - input/output module for reading binary or text data +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2024 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +// FIXME: this does not have any idea how to handle network streams that don't +// support seeking + +namespace MBS.Editor.Core.IO; + +using MBS.Core; + +// [DebuggerNonUserCode()] +public class Reader : ReaderWriterBase +{ + public Reader(Stream st) : base(st) { } + + public void Read(byte[] buffer, int start, int length) + { + BaseStream.Read(buffer, start, length); + } + + public bool ReadBoolean() + { + return (ReadBytes(1)[0] != 0); + } + + public byte ReadByte() + { + return ReadBytes(1)[0]; + } + + string? charBuffer = null; + int charBufferIndex = 0; + + public char[] ReadChars(int count) + { + char[] value = new char[count]; + for (int i = 0; i < count; i++) + value[i] = ReadChar(); + return value; + } + + public T ReadObject() where T : new() + { + T obj = new T(); + // FIXME: implement this + return obj; + } + + public char ReadChar() + { + return ReadChar(DefaultEncoding); + } + public char ReadChar(Encoding encoding) + { + charBuffer = null; + if (charBuffer == null) + { + int maxByteCount = encoding.GetMaxByteCount(1); + byte[] bytes = PeekBytes(maxByteCount); + charBuffer = encoding.GetString(bytes); + charBufferIndex = 0; + } + + char c = charBuffer[charBufferIndex]; + charBufferIndex++; + + int ct = encoding.GetByteCount(new char[] { c }); + BaseStream.Seek(ct, SeekOrigin.Current); + + if (charBufferIndex + 1 > charBuffer.Length) + { + charBuffer = null; + } + return c; + } + public byte PeekByte() + { + byte b = ReadByte(); + BaseStream.Seek(-1, SeekOrigin.Current); + return b; + } + public byte[] PeekBytes(int length) + { + byte[] buffer = new byte[length]; + int len = BaseStream.Read(buffer, 0, length); + BaseStream.Seek(-len, SeekOrigin.Current); + return buffer; + } + public char PeekChar() + { + return (char)PeekByte(); + } + [CLSCompliant(false)] + public sbyte ReadSByte() + { + return (sbyte)(ReadBytes(1)[0]); + } + + private int xorkey_index = 0; + + [CLSCompliant(false)] + public byte[] ReadBytes(uint length) + { + byte[] buf = new byte[length]; + uint lastct = 0; + while (lastct < length) + { + int ct = (int)length; + byte[] buf2 = new byte[ct]; + Read(buf2, 0, ct); + + Array.Copy(buf2, 0, buf, lastct, buf2.Length); + lastct += (uint)ct; + } + /* + for (int i = 0; i < Transformations.Count; i++) + { + buf = Transformations[i].Transform(buf); + } + */ + return buf; + } + + [CLSCompliant(false)] + public byte[] ReadBytes(ulong length) + { + byte[] buf = new byte[length]; + for (ulong i = 0L; i < length; i += (ulong)1L) + { + buf[(int)i] = ReadByte(); + } + return buf; + } + + [DebuggerNonUserCode()] + public byte[] ReadBytes(long length) + { + byte[] buffer = new byte[length]; + BaseStream.Read(buffer, 0, (int)length); + return buffer; + } + + [CLSCompliant(false)] + public sbyte[] ReadSBytes(long length) + { + byte[] buffer = ReadBytes(length); + return (sbyte[])(Array)buffer; + } + + public int ReadCompactInt32() + { + int multiplier = 1; + byte b1 = this.ReadByte(); + if ((b1 & 0x80) == 1) + { + multiplier = -1; + } + if ((b1 & 0x40) == 1) + { + byte b2 = this.ReadByte(); + if ((b2 & 0x80) == 1) + { + byte b3 = this.ReadByte(); + if ((b2 & 0x80) == 1) + { + byte b4 = this.ReadByte(); + return (multiplier * (((b1 | (b2 << 8)) | (b3 << 0x10)) | (b4 << 0x18))); + } + return (multiplier * ((b1 | (b2 << 8)) | (b3 << 0x10))); + } + return (multiplier * (b1 | (b2 << 8))); + } + return (multiplier * b1); + } + + /// + /// Reads a in a format that encodes the property in a 2-bit field + /// and the property in a 62-bit field. + /// + /// An object that is equivalent to the System.DateTime object that was serialized by the method. + /// + /// The serialized value is less than or greater than . + /// + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public DateTime ReadDateTime() + { + return ReadDateTime64(); + } + /// + /// Reads a in a format that encodes the property in a 2-bit field + /// and the property in a 62-bit field. + /// + /// An object that is equivalent to the System.DateTime object that was serialized by the method. + /// + /// The serialized value is less than or greater than . + /// + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public DateTime ReadDateTime64() + { + long l = ReadInt64(); + return DateTime.FromBinary(l); + } + /// + /// Reads a in a format that encodes the property in a 2-bit field + /// and the property in a 30-bit field. + /// + /// An object that is equivalent to the System.DateTime object that was serialized by the method. + /// + /// The serialized value is less than or greater than . + /// + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public DateTime ReadDateTime32() + { + int l = ReadInt32(); + return DateTime.FromBinary(l); + } + + /// + /// Reads a in ISO-9660 format (yyyyMMddHHMMSSssT). + /// + /// The read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public DateTime ReadISO9660DateTime() + { + string year = ReadFixedLengthString(4); + int nYear = int.Parse(year); + + string month = ReadFixedLengthString(2); + int nMonth = int.Parse(month); + + string day = ReadFixedLengthString(2); + int nDay = int.Parse(day); + + string hour = ReadFixedLengthString(2); + int nHour = int.Parse(hour); + + string minute = ReadFixedLengthString(2); + int nMinute = int.Parse(minute); + + string second = ReadFixedLengthString(2); + int nSecond = int.Parse(second); + + string secondHundredths = ReadFixedLengthString(2); + int nSecondHundredths = int.Parse(secondHundredths); + + // offset from Greenwich Mean Time, in 15-minute intervals, + // as a twos complement signed number, positive for time + // zones east of Greenwich, and negative for time zones + // west of Greenwich + sbyte gmtOffset = ReadSByte(); + + return new DateTime(nYear, nMonth, nDay, nHour + gmtOffset, nMinute, nSecond, nSecondHundredths, DateTimeKind.Utc); + } + + public int Read7BitEncodedInt() + { + int num = 0; + int num2 = 0; + while (num2 != 35) + { + byte b = ReadByte(); + num |= (int)(b & 127) << num2; + num2 += 7; + if ((b & 128) == 0) + { + return num; + } + } + throw new ArgumentOutOfRangeException("Invalid 7-bit encoded Int32"); + } + + public string ReadFixedLengthUTF16EndianString(int byteCount) + { + return ReadFixedLengthUTF16EndianString(byteCount, DefaultEncoding); + } + public string ReadFixedLengthUTF16EndianString(int byteCount, Encoding encoding) + { + if (byteCount % 2 != 0) + { + throw new ArgumentException("byteCount must be an even number"); + } + + byte[] data = ReadBytes(byteCount); + + // swap endians + if (Endianness == Endianness.BigEndian) + { + for (int i = 0; i < data.Length; i += 2) + { + byte tmp = data[i + 1]; + data[i + 1] = data[i]; + data[i] = tmp; + } + } + + return encoding.GetString(data); + } + + /// + /// Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time. + /// + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadLengthPrefixedString() + { + int num = 0; + int num2 = Read7BitEncodedInt(); + if (num2 < 0) throw new ArgumentOutOfRangeException("invalid string length"); + if (num2 == 0) return String.Empty; + + int count = (num2 - num > 128) ? 128 : (num2 - num); + return ReadFixedLengthString(count); + } + + /// + /// Reads a string of the specified length from the current stream. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadFixedLengthString(byte length) + { + return this.ReadFixedLengthString(length, DefaultEncoding); + } + /// + /// Reads a string of the specified length from the current stream. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadFixedLengthString(int length) + { + return ReadFixedLengthString(length, DefaultEncoding); + } + /// + /// Reads a string of the specified length from the current stream. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public string ReadFixedLengthString(uint length) + { + return this.ReadFixedLengthString(length, DefaultEncoding); + } + /// + /// Reads a string of the specified length from the current stream. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadFixedLengthString(byte length, Encoding encoding) + { + return this.ReadFixedLengthString((int)length, encoding); + } + /// + /// Reads a string of the specified length from the current stream using the specified encoding. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The to use to convert the bytes read into a instance. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadFixedLengthString(int length, Encoding encoding) + { + byte[] id = ReadBytes(length); + return encoding.GetString(id); + } + /// + /// Reads a string of the specified length from the current stream using the specified encoding. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The to use to convert the bytes read into a instance. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public string ReadFixedLengthString(uint length, Encoding encoding) + { + int l1 = (int)length; + int l2 = ((int)(length - l1)); + byte[] id = ReadBytes(l1); + if (l2 > 0) + { + Array.Resize(ref id, id.Length + l2); + Array.Copy(ReadBytes(l2), 0, id, id.Length - l2, l2); + } + return encoding.GetString(id); + } + /// + /// Reads a string of the specified length from the current stream. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadFixedLengthString(long length) + { + return ReadFixedLengthString(length, DefaultEncoding); + } + /// + /// Reads a string of the specified length from the current stream using the specified encoding. This method does not trim null characters; use to do this. + /// + /// The length of the string to read. + /// The to use to convert the bytes read into a instance. + /// The string being read. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public string ReadFixedLengthString(long length, Encoding encoding) + { + return encoding.GetString(ReadBytes((ulong)length)); + } + + /// + /// Reads a 16-byte (128-bit) value from the current stream and advances the current position of the stream by sixteen bytes. + /// + /// A 16-byte (128-bit) value read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public Guid ReadGuid(bool reverse = false) + { + uint a = 0; + ushort b = 0; + ushort c = 0; + byte d = 0; + byte e = 0; + byte f = 0; + byte g = 0; + byte h = 0; + byte i = 0; + byte j = 0; + byte k = 0; + if (!reverse) + { + a = ReadUInt32(); + b = ReadUInt16(); + c = ReadUInt16(); + d = ReadByte(); + e = ReadByte(); + f = ReadByte(); + g = ReadByte(); + h = ReadByte(); + i = ReadByte(); + j = ReadByte(); + k = ReadByte(); + } + else + { + k = ReadByte(); + j = ReadByte(); + i = ReadByte(); + h = ReadByte(); + g = ReadByte(); + f = ReadByte(); + e = ReadByte(); + d = ReadByte(); + c = ReadUInt16(); + b = ReadUInt16(); + a = ReadUInt32(); + } + return new Guid(a, b, c, d, e, f, g, h, i, j, k); + } + /// + /// Reads an array of 16-byte (128-bit) values from the current stream and advances the current position of the stream by sixteen bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 16-byte (128-bit) values read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public Guid[] ReadGuidArray(int count) + { + Guid[] retval = new Guid[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadGuid(); + } + return retval; + } + + /// + /// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes. + /// + /// A 2-byte signed integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public short ReadInt16() + { + byte[] buffer = ReadBytes((uint)2); + byte[] _buffer = new byte[2]; + if (base.Endianness == Endianness.LittleEndian) + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + } + else if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[1]; + _buffer[1] = buffer[0]; + } + return BitConverter.ToInt16(_buffer, 0); + } + /// + /// Reads an array of 2-byte signed integers from the current stream and advances the current position of the stream by two bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 2-byte signed integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public short[] ReadInt16Array(int count) + { + short[] retval = new short[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadInt16(); + } + return retval; + } + /// + /// Reads a 2-byte unsigned integer from the current stream and advances the current position of the stream by two bytes. + /// + /// A 2-byte unsigned integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public ushort ReadUInt16() + { + byte[] buffer = ReadBytes(2); + if (base.Endianness == Endianness.LittleEndian) + { + return (ushort)(buffer[0] | (buffer[1] << 8)); + } + return (ushort)(buffer[1] | (buffer[0] << 8)); + } + /// + /// Reads an array of 2-byte unsigned integers from the current stream and advances the current position of the stream by two bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 2-byte unsigned integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public ushort[] ReadUInt16Array(int count) + { + ushort[] retval = new ushort[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadUInt16(); + } + return retval; + } + /// + /// Reads a 3-byte signed integer from the current stream and advances the current position of the stream by three bytes. + /// + /// A 3-byte signed integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public int ReadInt24() + { + byte[] buffer = ReadBytes((uint)3); + byte[] _buffer = new byte[3]; + if (base.Endianness == Endianness.LittleEndian) + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = 0; + } + else if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = 0; + _buffer[1] = buffer[2]; + _buffer[2] = buffer[1]; + _buffer[3] = buffer[0]; + } + return BitConverter.ToInt32(_buffer, 0); + } + /// + /// Reads an array of 3-byte signed integers from the current stream and advances the current position of the stream by three bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 3-byte signed integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public int[] ReadInt24Array(int count) + { + int[] retval = new int[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadInt24(); + } + return retval; + } + /// + /// Reads a 3-byte unsigned integer from the current stream and advances the current position of the stream by three bytes. + /// + /// A 3-byte unsigned integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public uint ReadUInt24() + { + // TODO: Test this out! + byte[] buffer = ReadBytes(3); + if (base.Endianness == Endianness.LittleEndian) + { + return (uint)((buffer[2] << 16) | (buffer[1] << 8) | (buffer[0])); + } + return (uint)((buffer[2]) | (buffer[1] << 8) | (buffer[0] << 16)); + } + /// + /// Reads an array of 3-byte unsigned integers from the current stream and advances the current position of the stream by three bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 3-byte unsigned integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public uint[] ReadUInt24Array(int count) + { + uint[] retval = new uint[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadUInt24(); + } + return retval; + } + /// + /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes. + /// + /// A 4-byte signed integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public int ReadInt32() + { + byte[] buffer = ReadBytes((uint)4); + byte[] _buffer = new byte[4]; + if (base.Endianness == Endianness.LittleEndian) + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + } + else if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[3]; + _buffer[1] = buffer[2]; + _buffer[2] = buffer[1]; + _buffer[3] = buffer[0]; + } + return BitConverter.ToInt32(_buffer, 0); + } + /// + /// Reads an array of 4-byte signed integers from the current stream and advances the current position of the stream by four bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 4-byte signed integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public int[] ReadInt32Array(int count) + { + int[] retval = new int[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadInt32(); + } + return retval; + } + /// + /// Reads a 4-byte unsigned integer from the current stream but does not advance the current position of the stream. + /// + /// A 4-byte unsigned integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public uint PeekUInt32() + { + uint value = ReadUInt32(); + BaseStream.Seek(-4, SeekOrigin.Current); + return value; + } + /// + /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four bytes. + /// + /// A 4-byte unsigned integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public uint ReadUInt32() + { + byte[] buffer = ReadBytes((uint)4); + if (base.Endianness == Endianness.LittleEndian) + { + return (uint)(((buffer[0] | (buffer[1] << 8)) | (buffer[2] << 0x10)) | (buffer[3] << 0x18)); + } + return (uint)(((buffer[3] | (buffer[2] << 8)) | (buffer[1] << 0x10)) | (buffer[0] << 0x18)); + } + /// + /// Reads an array of 4-byte unsigned integers from the current stream and advances the current position of the stream by four bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 4-byte unsigned integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public uint[] ReadUInt32Array(int count) + { + uint[] retval = new uint[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadUInt32(); + } + return retval; + } + /// + /// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes. + /// + /// An 8-byte signed integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public long ReadInt64() + { + byte[] buffer = ReadBytes((uint)8); + byte[] _buffer = new byte[8]; + if (base.Endianness == Endianness.LittleEndian) + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + _buffer[4] = buffer[4]; + _buffer[5] = buffer[5]; + _buffer[6] = buffer[6]; + _buffer[7] = buffer[7]; + } + else if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[7]; + _buffer[1] = buffer[6]; + _buffer[2] = buffer[5]; + _buffer[3] = buffer[4]; + _buffer[4] = buffer[3]; + _buffer[5] = buffer[2]; + _buffer[6] = buffer[1]; + _buffer[7] = buffer[0]; + } + return BitConverter.ToInt64(_buffer, 0); + } + /// + /// Reads an array of 8-byte signed integers from the current stream and advances the current position of the stream by eight bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 8-byte signed integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + public long[] ReadInt64Array(int count) + { + long[] retval = new long[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadInt64(); + } + return retval; + } + /// + /// Reads an 8-byte unsigned integer from the current stream and advances the current position of the stream by eight bytes. + /// + /// An 8-byte unsigned integer read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public ulong ReadUInt64() + { + byte[] buffer = ReadBytes((uint)8); + byte[] _buffer = new byte[8]; + if (base.Endianness == Endianness.LittleEndian) + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + _buffer[4] = buffer[4]; + _buffer[5] = buffer[5]; + _buffer[6] = buffer[6]; + _buffer[7] = buffer[7]; + } + else if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[7]; + _buffer[1] = buffer[6]; + _buffer[2] = buffer[5]; + _buffer[3] = buffer[4]; + _buffer[4] = buffer[3]; + _buffer[5] = buffer[2]; + _buffer[6] = buffer[1]; + _buffer[7] = buffer[0]; + } + return BitConverter.ToUInt64(_buffer, 0); + } + /// + /// Reads an array of 8-byte unsigned integers from the current stream and advances the current position of the stream by eight bytes times the number of values read. + /// + /// The number of values to read from the current stream. + /// An array of 8-byte unsigned integers read from the current stream. + /// The end of the stream is reached. + /// The stream is closed. + /// An I/O error occurs. + [CLSCompliant(false)] + public ulong[] ReadUInt64Array(int count) + { + ulong[] retval = new ulong[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadUInt64(); + } + return retval; + } + + public float ReadSingle() + { + byte[] buffer = ReadBytes((uint)4); + byte[] _buffer = new byte[4]; + if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[3]; + _buffer[1] = buffer[2]; + _buffer[2] = buffer[1]; + _buffer[3] = buffer[0]; + } + else + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + } + return BitConverter.ToSingle(_buffer, 0); + } + public float[] ReadSingleArray(int count) + { + float[] retval = new float[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadSingle(); + } + return retval; + } + + /// + /// Reads a 64-bit floating-point value. + /// + /// The double. + public double ReadDouble() + { + byte[] buffer = ReadBytes((uint)8); + byte[] _buffer = new byte[8]; + if (base.Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[7]; + _buffer[1] = buffer[6]; + _buffer[2] = buffer[5]; + _buffer[3] = buffer[4]; + _buffer[4] = buffer[3]; + _buffer[5] = buffer[2]; + _buffer[6] = buffer[1]; + _buffer[7] = buffer[0]; + } + else + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + _buffer[4] = buffer[4]; + _buffer[5] = buffer[5]; + _buffer[6] = buffer[6]; + _buffer[7] = buffer[7]; + } + return BitConverter.ToDouble(_buffer, 0); + } + public double[] ReadDoubleArray(int count) + { + double[] retval = new double[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadDouble(); + } + return retval; + } + + + public int ReadVariableLengthInt32() + { + int value = ReadByte(); + byte c = 0; + + if ((value & 0x80) == 0x80) + { + value &= 0x7F; + do + { + value = (value << 7) + ((c = ReadByte()) & 0x7F); + } + while ((c & 0x80) == 0x80); + } + + return value; + } + public int[] ReadVariableLengthInt32Array(int count) + { + int[] retval = new int[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadVariableLengthInt32(); + } + return retval; + } + + [CLSCompliant(false)] + public ulong ReadUInt48() + { + byte[] buffer = ReadBytes((uint)6); + if (base.Endianness == Endianness.LittleEndian) + { + uint num = (uint)(((buffer[0] << 0x10)) | (buffer[1] << 0x18)); + uint num2 = (uint)(((buffer[2] | (buffer[3] << 8)) | (buffer[4] << 0x10)) | (buffer[5] << 0x18)); + return (ulong)(num | num2 << 0x20); + } + else + { + uint num = (uint)(((buffer[5] << 0x10)) | (buffer[4] << 0x18)); + uint num2 = (uint)(((buffer[3] | (buffer[2] << 8)) | (buffer[1] << 0x10)) | (buffer[0] << 0x18)); + return (ulong)(num << 0x20 | num2); + } + } + [CLSCompliant(false)] + public ulong[] ReadUInt48Array(int count) + { + ulong[] retval = new ulong[count]; + for (int i = 0; i < count; i++) + { + retval[i] = ReadUInt48(); + } + return retval; + } + + + public string ReadNullTerminatedString() + { + return this.ReadNullTerminatedString(DefaultEncoding); + } + + public string ReadNullTerminatedString(int maxLength) + { + return this.ReadNullTerminatedString(maxLength, DefaultEncoding); + } + + public string ReadNullTerminatedString(Encoding encoding) + { + List r = new List(); + while (true) + { + byte nextChar = ReadByte(); + if ((nextChar == 0 && !(encoding == Encoding.Unicode)) || ((encoding == Encoding.Unicode) && (nextChar == 0 && (r.Count > 2 && r[r.Count - 1] == 0)))) + { + string result = encoding.GetString(r.ToArray()); + return result; + } + r.Add(nextChar); + } + } + + public string ReadNullTerminatedString(int maxLength, Encoding encoding) + { + string ret = this.ReadNullTerminatedString(encoding); + if (ret.Length > maxLength) + { + return ret.Substring(0, maxLength); + } + if (ret.Length < maxLength) + { + ReadBytes((maxLength - ret.Length) - 1); + } + return ret; + } + + /// + /// Reads a length-prefixed string that is prefixed with a signed short (2-byte) length, rather than an int (4-byte) length. + /// + /// + public string ReadInt16String() + { + short length = ReadInt16(); + return this.ReadFixedLengthString((int)length); + } + /// + /// Reads a length-prefixed string that is prefixed with an unsigned short (2-byte) length, rather than an int (4-byte) length. + /// + /// + public string ReadUInt16String() + { + ushort length = ReadUInt16(); + return this.ReadFixedLengthString((uint)length); + } + + public byte[] ReadToEnd() + { + return ReadBytes(BaseStream.GetRemaining()); + } + public string ReadStringToEnd(Encoding encoding = null) + { + if (encoding == null) encoding = Encoding.Default; + byte[] data = ReadToEnd(); + return encoding.GetString(data); + } + + public byte[] ReadUntil(byte[] sequence, bool includeSequence = false) + { + byte[] w = new byte[0]; + while (!BaseStream.EndOfStream()) + { + Array.Resize(ref w, w.Length + 1); + w[w.Length - 1] = ReadByte(); + + bool matches = true; + for (int i = 0; i < sequence.Length; i++) + { + if (w.Length < sequence.Length) + { + matches = false; + break; + } + if (w[w.Length - (sequence.Length - i)] != sequence[i]) + { + matches = false; + break; + } + } + + if (matches) + { + if (!includeSequence) + { + Array.Resize(ref w, w.Length - sequence.Length); + + // HACK: we aren't including the sequence in the response, BUT we should consume it anyway... right? + try + { + BaseStream.Seek(-sequence.Length, SeekOrigin.Current); + } + catch (NotSupportedException ex) + { + + } + + } + return w; + } + } + return w; + } + public string ReadUntil(string sequence) + { + return ReadUntil(sequence, DefaultEncoding); + } + public string ReadUntil(string sequence, bool includeSequence) + { + return ReadUntil(sequence, DefaultEncoding, includeSequence); + } + public string ReadUntil(string sequence, Encoding encoding) + { + return encoding.GetString(ReadUntil(sequence.ToCharArray(), encoding)); + } + public string ReadUntil(string sequence, Encoding encoding, bool includeSequence) + { + return new string(ReadUntil(sequence.ToCharArray(), encoding, includeSequence)); + } + public byte[] ReadUntil(char[] sequence) + { + return this.ReadUntil(sequence, DefaultEncoding); + } + public char[] ReadUntil(char[] sequence, bool includeSequence) + { + return this.ReadUntil(sequence, DefaultEncoding, includeSequence); + } + public byte[] ReadUntil(char[] sequence, Encoding encoding) + { + return this.ReadUntil(encoding.GetBytes(sequence)); + } + public char[] ReadUntil(char[] sequence, Encoding encoding, bool includeSequence) + { + return encoding.GetChars(this.ReadUntil(encoding.GetBytes(sequence), includeSequence)); + } + public string ReadStringUntil(string sequence) + { + return ReadStringUntil(sequence, DefaultEncoding, DefaultEncoding); + } + public string ReadStringUntil(string sequence, bool includeSequence) + { + return ReadStringUntil(sequence, DefaultEncoding, DefaultEncoding, includeSequence); + } + public string ReadStringUntil(string sequence, Encoding inputEncoding, Encoding outputEncoding) + { + return ReadStringUntil(sequence.ToCharArray(), inputEncoding, outputEncoding); + } + public string ReadStringUntil(string sequence, Encoding inputEncoding, Encoding outputEncoding, bool includeSequence) + { + return ReadStringUntil(sequence.ToCharArray(), inputEncoding, outputEncoding, includeSequence); + } + public string ReadStringUntil(char[] sequence) + { + return ReadStringUntil(sequence, DefaultEncoding, DefaultEncoding); + } + public string ReadStringUntil(char[] sequence, bool includeSequence) + { + return ReadStringUntil(sequence, DefaultEncoding, DefaultEncoding, includeSequence); + } + public string ReadStringUntil(char[] sequence, Encoding inputEncoding, Encoding outputEncoding) + { + byte[] bytes = ReadUntil(inputEncoding.GetBytes(sequence)); + return outputEncoding.GetString(bytes); + } + public string ReadStringUntil(char[] sequence, Encoding inputEncoding, Encoding outputEncoding, bool includeSequence) + { + return outputEncoding.GetString(ReadUntil(inputEncoding.GetBytes(sequence), includeSequence)); + } + + public void SeekUntilFirstNonNull() + { + while (PeekByte() == 0) + { + ReadChar(); + } + } + + public string[] ReadNullTerminatedStringArray(int stringTableSize) + { + System.Collections.Generic.List list = new System.Collections.Generic.List(); + long endpos = BaseStream.Position + stringTableSize; + while (BaseStream.Position < endpos) + { + list.Add(ReadNullTerminatedString()); + } + return list.ToArray(); + } + + // TODO: TEST THIS!! + public decimal ReadDecimal() + { + byte[] buffer = ReadBytes(16); + int num = (int)buffer[0] | (int)buffer[1] << 8 | (int)buffer[2] << 16 | (int)buffer[3] << 24; + int num2 = (int)buffer[4] | (int)buffer[5] << 8 | (int)buffer[6] << 16 | (int)buffer[7] << 24; + int num3 = (int)buffer[8] | (int)buffer[9] << 8 | (int)buffer[10] << 16 | (int)buffer[11] << 24; + int flags = (int)buffer[12] | (int)buffer[13] << 8 | (int)buffer[14] << 16 | (int)buffer[15] << 24; + + bool isNegative = ((flags & -2147483648) == -2147483648); + byte scale = (byte)(flags >> 16); + + if ((flags & 2130771967) == 0 && (flags & 16711680) <= 1835008) + { + return new Decimal(num, num2, num3, isNegative, scale); + } + throw new ArgumentOutOfRangeException("Invalid decimal"); + } + + public string ReadByteSizedString() + { + byte len = ReadByte(); + return ReadFixedLengthString(len); + } + + public short ReadDoubleEndianInt16() + { + short value1 = ReadInt16(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + short value2 = ReadInt16(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + + if (value2 != value1) + { + throw new InvalidOperationException("Big-endian value does not match little-endian value"); + } + return value1; + } + [CLSCompliant(false)] + public ushort ReadDoubleEndianUInt16() + { + ushort value1 = ReadUInt16(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + ushort value2 = ReadUInt16(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + + if (value2 != value1) + { + throw new InvalidOperationException("Big-endian value does not match little-endian value"); + } + return value1; + } + public int ReadDoubleEndianInt32() + { + int value1 = ReadInt32(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + int value2 = ReadInt32(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + + if (value2 != value1) + { + throw new InvalidOperationException("Big-endian value does not match little-endian value"); + } + return value1; + } + [CLSCompliant(false)] + public uint ReadDoubleEndianUInt32() + { + uint value1 = ReadUInt32(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + uint value2 = ReadUInt32(); + if (base.Endianness == Endianness.LittleEndian) + { + base.Endianness = Endianness.BigEndian; + } + else + { + base.Endianness = Endianness.LittleEndian; + } + + if (value2 != value1) + { + throw new InvalidOperationException("Big-endian value does not match little-endian value"); + } + return value1; + } + + private int ToInt32(byte[] buffer) + { + int ret = 0; + int mode = 0; + for (int i = 0; i < Math.Min(4, buffer.Length); i++) + { + ret |= (buffer[i] << mode); + mode += 8; + } + return ret; + } + + public int ReadCompactInt32New() + { + byte[] buffer = new byte[2]; + int start = 0; + int length = buffer.Length; + while (true) + { + Read(buffer, start, length); + if (buffer[buffer.Length - 1] == 0 || (buffer.Length > 4)) + { + return ToInt32(buffer); + } + else + { + start = buffer.Length; + length = 1; + Array.Resize(ref buffer, buffer.Length + 1); + } + } + } + + public object ReadBEncodedObject() + { + char w = (char)PeekChar(); + switch (w) + { + case 'd': + { + // Read the starting 'd' + w = ReadChar(); + + Dictionary dict = new Dictionary(); + while (w != 'e') + { + string key = (string)ReadBEncodedObject(); + object value = ReadBEncodedObject(); + w = (char)PeekChar(); + dict.Add(key, value); + } + + // Read the final 'e' + w = ReadChar(); + + return dict; + } + case 'l': + { + // Read the starting 'l' + w = ReadChar(); + + List list = new List(); + while (w != 'e') + { + object item = ReadBEncodedObject(); + w = (char)PeekChar(); + + list.Add(item); + } + + // Read the final 'e' + w = ReadChar(); + return list; + } + case 'i': + { + // Read the starting 'i' + w = ReadChar(); + string num = String.Empty; + while (w != 'e') + { + w = ReadChar(); + if (w != 'e') + { + num += w; + } + } + // Already read the final 'e' + + return Int32.Parse(num); + } + default: + { + // Assume a string + w = (char)PeekChar(); + string num = String.Empty; + string val = String.Empty; + while (w != ':') + { + w = ReadChar(); + if (w != ':') + { + num += w; + } + } + + uint nnum = UInt32.Parse(num); + val = ReadFixedLengthString(nnum); + + return val; + } + } + } + /// + /// Reads a 32-bit integer length-prefixed string using the system default encoding. + /// + /// + /// + public string ReadInt32String() + { + return ReadInt32String(DefaultEncoding); + } + /// + /// Reads a 32-bit integer length-prefixed string using the specified encoding. + /// + /// + /// + public string ReadInt32String(Encoding encoding) + { + int length = ReadInt32(); + return ReadFixedLengthString(length); + } + + /// + /// Reads a length-prefixed . + /// + /// + public Version ReadVersion() + { + byte parts = ReadByte(); + switch (parts) + { + case 1: + { + int vmaj = ReadInt32(); + return new Version(vmaj, 0); + } + case 2: + { + int vmaj = ReadInt32(); + int vmin = ReadInt32(); + return new Version(vmaj, vmin); + } + case 3: + { + int vmaj = ReadInt32(); + int vmin = ReadInt32(); + int vbld = ReadInt32(); + return new Version(vmaj, vmin, vbld); + } + case 4: + { + int vmaj = ReadInt32(); + int vmin = ReadInt32(); + int vbld = ReadInt32(); + int vrev = ReadInt32(); + + if (vbld > -1) + { + if (vrev > -1) + { + return new Version(vmaj, vmin, vbld, vrev); + } + else + { + return new Version(vmaj, vmin, vbld); + } + } + else + { + return new Version(vmaj, vmin); + } + } + } + return new Version(); + } + + private short[] ReadInt16ArrayWTF(int count) + { + byte[] buffer = new byte[count * 2]; + Read(buffer, 0, buffer.Length); + + short[] buffer2 = new short[count]; + for (int i = 0; i < buffer.Length; i += 2) + { + byte b1 = buffer[i]; + byte b2 = buffer[i + 1]; + int index = (int)(i / 2); + + if (base.Endianness == Endianness.LittleEndian) + { + buffer2[index] = (short)(b1 | (b2 << 8)); + } + else if (base.Endianness == Endianness.BigEndian) + { + buffer2[index] = (short)(b2 | (b1 << 8)); + } + } + return buffer2; + } + + public string PeekFixedLengthString(int count) + { + return PeekFixedLengthString(count, DefaultEncoding); + } + public string PeekFixedLengthString(int count, Encoding encoding) + { + byte[] data = PeekBytes(count); + return encoding.GetString(data); + } + + /// + /// Reads a half (2 bytes/half instead of 4 bytes/single) as a floating-point value. + /// + /// + public float ReadHalf() + { + byte[] buffer = ReadBytes(2); + byte[] buffer2 = new byte[4]; + if (base.Endianness == Endianness.LittleEndian) + { + buffer2[0] = 0; + buffer2[1] = 0; + buffer2[2] = buffer[0]; + buffer2[3] = buffer[1]; + } + else + { + buffer2[0] = buffer[0]; + buffer2[1] = buffer[1]; + buffer2[2] = 0; + buffer2[3] = 0; + } + return BitConverter.ToSingle(buffer2, 0); + } + + public int ReadAtMostBytes(byte[] buffer, int count) + { + if (BaseStream.GetRemaining() == 0) return 0; + + if (count < BaseStream.GetRemaining()) + { + Read(buffer, 0, count); + return count; + } + else + { + Read(buffer, 0, (int)BaseStream.GetRemaining()); + return (int)BaseStream.GetRemaining(); + } + } + + private byte[] read_buf = new byte[4096]; + private int getbit_buf = 0; + private int getbit_len = 0; + private int getbit_count = 0; + private int getbit_mask = 0; + + public int ReadBitsAsInt32(int count) + { + int i, x = 0; + + for (i = 0; i < count; i++) + { + if (getbit_mask == 0) + { + if (getbit_len == getbit_count) + { + getbit_len = ReadAtMostBytes(read_buf, 4096); + if (getbit_len == 0) throw new EndOfStreamException(); + getbit_count = 0; + } + + getbit_buf = read_buf[getbit_count++]; + getbit_mask = 128; + } + x <<= 1; + if ((getbit_buf & getbit_mask) != 0) x |= 1; + getbit_mask >>= 1; + } + return x; + } + + public string ReadInt64String() + { + long length = ReadInt64(); + string value = ReadFixedLengthString(length); + return value; + } + + public string ReadUntil(string[] until) + { + string rest = null; + return ReadUntil(until, out rest); + } + public string ReadUntil(string[] until, out string rest) + { + return ReadUntil(until, null, null, out rest); + } + public string ReadUntil(string until, string ignoreBegin, string ignoreEnd) + { + return ReadUntil(new string[] { until }, ignoreBegin, ignoreEnd); + } + public string ReadUntil(string[] until, string ignoreBegin, string ignoreEnd) + { + string rest = null; + return ReadUntil(until, ignoreBegin, ignoreEnd, out rest); + } + public string ReadUntil(string[] until, string ignoreBegin, string ignoreEnd, out string rest) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + + while (!BaseStream.EndOfStream()) + { + sb.Append(ReadChar()); + + foreach (string s in until) + { + if (sb.ToString().EndsWith(s)) + { + string w = sb.ToString(); + string retval = w.Substring(0, w.Length - 1); + rest = w.Substring(w.Length - 1); + return retval; + } + } + + /* + char[] buffer = new char[until.Length * 2]; + ReadBlock(buffer, 0, until.Length * 2); + + string w = new string(buffer); + if (w.Contains(until)) + { + string ww = w.Substring(0, w.IndexOf(until)); + sb.Append(ww); + + // back up the stream reader + int indexOfUntil = (w.IndexOf(until) + until.Length); + int lengthToBackUp = w.Length - indexOfUntil; + BaseStream.Seek(-1 * lengthToBackUp, SeekOrigin.Current); + break; + } + sb.Append(w); + */ + } + rest = null; + return sb.ToString(); + } + + public string ReadBetween(string start, string end, bool discard) + { + string nextstr = String.Empty; + bool inside = false; + // 0000000-3842-17774-}ehaomfd + while (!BaseStream.EndOfStream()) + { + nextstr += ReadChar(); + if (!inside) + { + if (nextstr.EndsWith(start)) + { + inside = true; + nextstr = String.Empty; + if (!discard) nextstr += start; + } + } + else + { + if (nextstr.EndsWith(end)) + { + if (discard) + { + nextstr = nextstr.Substring(0, nextstr.Length - end.Length); + } + return nextstr; + } + } + } + return String.Empty; + } + + public int CurrentLine { get; private set; } + + private NewLineSequence _ActualNewLineSequenceForAutomatic = NewLineSequence.SystemDefault; + public string ReadLine() + { + StringBuilder sb = new StringBuilder(); + if (NewLineSequence == NewLineSequence.Automatic && _ActualNewLineSequenceForAutomatic == NewLineSequence.SystemDefault) + { + // first time around, determine actual new line sequence + while (!BaseStream.EndOfStream()) + { + char c = ReadChar(); + if (c == '\n') + { + char c2 = PeekChar(); + if (c2 == '\r') + { + ReadChar(); + _ActualNewLineSequenceForAutomatic = NewLineSequence.LineFeedCarriageReturn; + break; + } + else + { + _ActualNewLineSequenceForAutomatic = NewLineSequence.LineFeed; + break; + } + } + else if (c == '\r') + { + char c2 = PeekChar(); + if (c2 == '\n') + { + ReadChar(); + _ActualNewLineSequenceForAutomatic = NewLineSequence.CarriageReturnLineFeed; + break; + } + else + { + _ActualNewLineSequenceForAutomatic = NewLineSequence.CarriageReturn; + break; + } + } + else + { + sb.Append(c); + } + } + + CurrentLine++; + return sb.ToString(); + } + else + { + string line = ReadUntil(GetNewLineSequence()); + ReadChars(GetNewLineSequence().Length); + if (line.EndsWith("\r")) + line = line.Substring(0, line.Length - 1); + + CurrentLine++; + return line; + } + } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public void Close() + { + BaseStream.Close(); + } + + /// + /// Aligns the to the specified number of bytes. If the current + /// position of the is not a multiple of the specified number of bytes, + /// the position will be increased by the amount of bytes necessary to bring it to the + /// aligned position. + /// + /// The number of bytes on which to align the . + /// Any additional padding bytes that should be included after aligning to the specified boundary. + public void Align(int alignTo, int extraPadding = 0) + { + long paddingCount = ((alignTo - (BaseStream.Position % alignTo)) % alignTo); + paddingCount += extraPadding; + + if (BaseStream.Position + paddingCount < BaseStream.Length) + BaseStream.Position += paddingCount; + } + + public string ReadStringUntilAny(char[] anyOf) + { + StringBuilder sb = new StringBuilder(); + while (!BaseStream.EndOfStream()) + { + char c = ReadChar(); + bool found = false; + for (int i = 0; i < anyOf.Length; i++) + { + if (c == anyOf[i]) + { + found = true; + break; + } + } + if (found) break; + sb.Append(c); + } + BaseStream.Seek(-1, SeekOrigin.Current); + return sb.ToString(); + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs new file mode 100644 index 0000000..74aa39d --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs @@ -0,0 +1,79 @@ +using System.Text; + +namespace MBS.Editor.Core.IO; + +public class ReaderWriterBase +{ + private readonly Stream _st; + /// + /// Exposes access to the underlying stream of the . + /// + /// + /// The underlying stream associated with the . + /// + public Stream BaseStream { get { return _st; } } + + public Encoding DefaultEncoding { get; set; } = Encoding.UTF8; + + public NewLineSequence NewLineSequence { get; set; } = NewLineSequence.SystemDefault; + public Endianness Endianness { get; set; } = Endianness.LittleEndian; + + public bool SwapEndianness() + { + if (Endianness == Endianness.LittleEndian) + { + Endianness = Endianness.BigEndian; + return true; + } + else if (Endianness == Endianness.BigEndian) + { + Endianness = Endianness.LittleEndian; + return true; + } + return false; + } + + protected string GetNewLineSequence() + { + switch (NewLineSequence) + { + case NewLineSequence.SystemDefault: return System.Environment.NewLine; + case NewLineSequence.CarriageReturnLineFeed: return "\r\n"; + case NewLineSequence.CarriageReturn: return "\r"; + case NewLineSequence.LineFeed: return "\n"; + } + return System.Environment.NewLine; + } + + public ReaderWriterBase(Stream st) + { + _st = st; + } + + /// + /// Aligns the to the specified number of bytes. If the current + /// position of the is not a multiple of the specified number of bytes, + /// the position will be increased by the amount of bytes necessary to bring it to the + /// aligned position. + /// + /// The number of bytes on which to align the . + /// Any additional padding bytes that should be included after aligning to the specified boundary. + public void Align(int alignTo, int extraPadding = 0) + { + if (alignTo == 0) + return; + + long paddingCount = ((alignTo - (_st.Position % alignTo)) % alignTo); + paddingCount += extraPadding; + + if (_st.Position == _st.Length) + { + byte[] buffer = new byte[paddingCount]; + _st.Write(buffer, 0, buffer.Length); + } + else + { + _st.Position += paddingCount; + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Writer.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Writer.cs new file mode 100644 index 0000000..618b122 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Writer.cs @@ -0,0 +1,1023 @@ +namespace MBS.Editor.Core.IO; + +using System.IO; +using System.Text; + +public class Writer : ReaderWriterBase +{ + + public Writer(Stream st) : base(st) { } + + public void Write(byte[] buffer, int offset, int length) + { + BaseStream.Write(buffer, offset, length); + } + + public void WriteByte(byte value) + { + WriteBytes(new byte[] { value }); + } + + public void WriteBoolean(bool value) + { + WriteByte(value ? (byte)1 : (byte)0); + } + + [CLSCompliant(false)] + public void WriteSByte(sbyte value) + { + WriteBytes(new byte[] { (byte)value }); + } + + public void WriteBytes(byte[] data) + { + if (data == null) return; + + /* + for (int i = 0; i < Transformations.Count; i++) + { + data = Transformations[i].Transform(data); + } + */ + Write(data, 0, data.Length); + + if (AutoFlush) + Flush(); + } + + public bool AutoFlush { get; set; } = false; + + [CLSCompliant(false)] + public void WriteSBytes(sbyte[] data) + { + if (data == null) return; + + // thanks https://stackoverflow.com/questions/829983/how-to-convert-a-sbyte-to-byte-in-c + byte[] realdata = (byte[])(Array)data; + /* + for (int i = 0; i < Transformations.Count; i++) + { + realdata = Transformations[i].Transform(realdata); + } + */ + Write(realdata, 0, realdata.Length); + } + + public void WriteFixedLengthBytes(byte[] data, int count) + { + if (data == null) data = new byte[0]; + byte[] realdata = new byte[count]; + Array.Copy(data, 0, realdata, 0, Math.Min(realdata.Length, count)); + WriteBytes(realdata); + } + + public void WriteChar(char value) + { + WriteChar(value, DefaultEncoding); + } + public void WriteChar(char value, Encoding encoding) + { + byte[] data = encoding.GetBytes(new char[] { value }); + WriteBytes(data); + } + public void WriteCharArray(char[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteChar(values[i]); + } + } + + public void Write(char value) + { + Write(value.ToString()); + } + public void Write(string value) + { + WriteFixedLengthString(value); + } + public void WriteLine() + { + WriteLine(String.Empty); + } + public void WriteLine(char value) + { + WriteLine(value.ToString()); + } + public void WriteLine(string value) + { + WriteFixedLengthString(value + GetNewLineSequence()); + } + + public void WriteGuid(Guid guid) + { + WriteBytes(guid.ToByteArray()); + } + public void WriteFixedLengthString(string value) + { + WriteFixedLengthString(value, DefaultEncoding); + } + public void WriteFixedLengthString(string value, int length) + { + WriteFixedLengthString(value, DefaultEncoding, length); + } + [CLSCompliant(false)] + public void WriteFixedLengthString(string value, uint length) + { + WriteFixedLengthString(value, DefaultEncoding, length); + } + public void WriteFixedLengthString(string value, Encoding encoding) + { + if (value == null) + return; + + byte[] data = encoding.GetBytes(value); + WriteBytes(data); + } + public void WriteFixedLengthString(string value, int length, char paddingChar) + { + WriteFixedLengthString(value, DefaultEncoding, length, paddingChar); + } + [CLSCompliant(false)] + public void WriteFixedLengthString(string value, uint length, char paddingChar) + { + this.WriteFixedLengthString(value, DefaultEncoding, length, paddingChar); + } + public void WriteFixedLengthString(string value, Encoding encoding, int length) + { + this.WriteFixedLengthString(value, encoding, length, '\0'); + } + [CLSCompliant(false)] + public void WriteFixedLengthString(string value, Encoding encoding, uint length) + { + WriteFixedLengthString(value, encoding, length, '\0'); + } + public void WriteFixedLengthString(string value, Encoding encoding, int length, char paddingChar) + { + WriteFixedLengthString(value, encoding, (uint)length, paddingChar); + } + [CLSCompliant(false)] + public void WriteFixedLengthString(string value, Encoding encoding, uint length, char paddingChar) + { + if (value == null) + return; + + string v = value; + if (v == null) v = String.Empty; + byte[] data = encoding.GetBytes(v); + byte[] realData = new byte[length]; + + uint realLength = length; + if (data.Length < realLength) + { + realLength = (uint)data.Length; + } + Array.Copy(data, 0, realData, 0, realLength); + + for (int i = data.Length; i < realData.Length; i++) + { + realData[i] = (byte)paddingChar; + } + WriteBytes(realData); + } + + public void WriteLengthPrefixedString(string value) + { + WriteLengthPrefixedString(value, DefaultEncoding); + } + public void WriteLengthPrefixedString(string value, Encoding encoding) + { + Write7BitEncodedInt32(value.Length); + WriteFixedLengthString(value); + } + + public void Write7BitEncodedInt32(int value) + { + // TODO: verify this actually works + uint v = (uint)value; + while (v >= 0x80) + { + WriteByte((byte)(v | 0x80)); + v >>= 7; + } + WriteByte((byte)v); + } + public int Calculate7BitEncodedInt32Size(int value) + { + // TODO: verify this actually works + int size = 1; + uint v = (uint)value; + while (v >= 0x80) + { + size++; + v >>= 7; + } + return size; + } + + public void WriteNullTerminatedString(string sz) + { + WriteNullTerminatedString(sz, Encoding.UTF8); + } + public void WriteNullTerminatedString(string sz, Encoding encoding) + { + byte[] values = encoding.GetBytes(sz + '\0'); + WriteBytes(values); + } + public void WriteNullTerminatedString(string sz, int length) + { + // TODO: not sure how to handle this, should "length" refer to just the string length (data length) or should it include the null-terminator (field length)? + string ssz = sz.Substring(0, Math.Min(sz.Length, length) - 1); + WriteNullTerminatedString(ssz); + } + public void WriteNullTerminatedString(string sz, Encoding encoding, int length) + { + // TODO: not sure how to handle this, should "length" refer to just the string length (data length) or should it include the null-terminator (field length)? + string ssz = sz.Substring(0, Math.Min(sz.Length, length) - 1); + WriteNullTerminatedString(ssz, encoding); + } + + /// + /// Writes a two-byte signed integer to the current stream and advances the stream position by two bytes. + /// + /// The two-byte signed integer to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt16(short value) + { + byte[] _buffer = BitConverter.GetBytes(value); + byte[] buffer = new byte[2]; + if (Endianness == Endianness.BigEndian) + { + buffer[1] = _buffer[0]; + buffer[0] = _buffer[1]; + } + else + { + buffer[0] = _buffer[0]; + buffer[1] = _buffer[1]; + } + WriteBytes(buffer); + } + /// + /// Writes an array of two-byte signed integers to the current stream and advances the stream position by two bytes times the number of values written. + /// + /// The array of two-byte signed integers to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt16Array(short[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteInt16(values[i]); + } + } + + /// + /// Writes a variable-length unsigned integer to the current stream and advances the stream position by the number of bytes written. + /// + /// The value to write. + /// a representing the number of bytes written to the stream for the given + /// This code is taken from the answer on StackOverflow https://stackoverflow.com/q/3564685 + public int WriteVariableLengthInt32(int value) + { + // thx stackoverflow :) https://stackoverflow.com/q/3564685 + int count = 0; + bool first = true; + while (first || value > 0) + { + first = false; + byte lower7bits = (byte)(value & 0x7f); + value >>= 7; + if (value > 0) + lower7bits |= 128; + WriteByte(lower7bits); + count++; + } + return count; + } + + /// + /// Writes a two-byte unsigned integer to the current stream and advances the stream position by two bytes. + /// + /// The two-byte unsigned integer to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt16(ushort value) + { + byte[] _buffer = BitConverter.GetBytes(value); + byte[] buffer = new byte[2]; + if (Endianness == Endianness.BigEndian) + { + buffer[1] = _buffer[0]; + buffer[0] = _buffer[1]; + } + else + { + buffer[0] = _buffer[0]; + buffer[1] = _buffer[1]; + } + WriteBytes(buffer); + } + /// + /// Writes an array of two-byte unsigned integers to the current stream and advances the stream position by two bytes times the number of values written. + /// + /// The array of two-byte unsigned integers to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt16Array(ushort[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteUInt16(values[i]); + } + } + /// + /// Writes a three-byte signed integer to the current stream and advances the stream position by three bytes. + /// + /// The three-byte signed integer to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt24(int value) + { + byte[] buffer = new byte[3]; + if (Endianness == Endianness.BigEndian) + { + buffer[2] = (byte)value; + buffer[1] = (byte)(value >> 8); + buffer[0] = (byte)(value >> 16); + } + else + { + buffer[0] = (byte)value; + buffer[1] = (byte)(value >> 8); + buffer[2] = (byte)(value >> 16); + } + WriteBytes(buffer); + } + /// + /// Writes an array of three-byte signed integers to the current stream and advances the stream position by three bytes times the number of values written. + /// + /// The array of three-byte signed integers to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt24Array(int[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteInt24(values[i]); + } + } + /// + /// Writes a three-byte unsigned integer to the current stream and advances the stream position by three bytes. + /// + /// The three-byte unsigned integer to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt24(uint value) + { + byte[] buffer = new byte[3]; + if (Endianness == Endianness.BigEndian) + { + buffer[2] = (byte)value; + buffer[1] = (byte)(value >> 8); + buffer[0] = (byte)(value >> 16); + } + else + { + buffer[0] = (byte)value; + buffer[1] = (byte)(value >> 8); + buffer[2] = (byte)(value >> 16); + } + WriteBytes(buffer); + } + /// + /// Writes an array of three-byte unsigned integers to the current stream and advances the stream position by three bytes times the number of values written. + /// + /// The array of three-byte unsigned integers to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt24Array(uint[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteUInt24(values[i]); + } + } + /// + /// Writes a four-byte signed integer to the current stream and advances the stream position by four bytes. + /// + /// The four-byte signed integer to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt32(int value) + { + byte[] _buffer = BitConverter.GetBytes(value); + byte[] buffer = new byte[4]; + if (Endianness == Endianness.BigEndian) + { + buffer[3] = _buffer[0]; + buffer[2] = _buffer[1]; + buffer[1] = _buffer[2]; + buffer[0] = _buffer[3]; + } + else + { + buffer[0] = _buffer[0]; + buffer[1] = _buffer[1]; + buffer[2] = _buffer[2]; + buffer[3] = _buffer[3]; + } + WriteBytes(buffer); + } + /// + /// Writes an array of four-byte signed integers to the current stream and advances the stream position by four bytes times the number of values written. + /// + /// The array of four-byte signed integers to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt32Array(int[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteInt32(values[i]); + } + } + /// + /// Writes a four-byte unsigned integer to the current stream and advances the stream position by four bytes. + /// + /// The four-byte unsigned integer to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt32(uint value) + { + byte[] _buffer = BitConverter.GetBytes(value); + byte[] buffer = new byte[4]; + if (Endianness == Endianness.BigEndian) + { + buffer[3] = _buffer[0]; + buffer[2] = _buffer[1]; + buffer[1] = _buffer[2]; + buffer[0] = _buffer[3]; + } + else + { + buffer[0] = _buffer[0]; + buffer[1] = _buffer[1]; + buffer[2] = _buffer[2]; + buffer[3] = _buffer[3]; + } + WriteBytes(buffer); + } + /// + /// Writes an array of four-byte unsigned integers to the current stream and advances the stream position by four bytes times the number of values written. + /// + /// The array of four-byte unsigned integers to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt32Array(uint[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteUInt32(values[i]); + } + } + /// + /// Writes an eight-byte signed integer to the current stream and advances the stream position by eight bytes. + /// + /// The eight-byte signed integer to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt64(long value) + { + byte[] _buffer = new byte[8]; + if (Endianness == Endianness.BigEndian) + { + _buffer[0] = (byte)(value >> 56); + _buffer[1] = (byte)(value >> 48); + _buffer[2] = (byte)(value >> 40); + _buffer[3] = (byte)(value >> 32); + _buffer[4] = (byte)(value >> 24); + _buffer[5] = (byte)(value >> 16); + _buffer[6] = (byte)(value >> 8); + _buffer[7] = (byte)value; + } + else + { + _buffer[0] = (byte)value; + _buffer[1] = (byte)(value >> 8); + _buffer[2] = (byte)(value >> 16); + _buffer[3] = (byte)(value >> 24); + _buffer[4] = (byte)(value >> 32); + _buffer[5] = (byte)(value >> 40); + _buffer[6] = (byte)(value >> 48); + _buffer[7] = (byte)(value >> 56); + } + WriteBytes(_buffer); + } + /// + /// Writes an array of eight-byte signed integers to the current stream and advances the stream position by eight bytes times the number of values written. + /// + /// The array of eight-byte signed integers to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteInt64Array(long[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteInt64(values[i]); + } + } + /// + /// Writes an eight-byte unsigned integer to the current stream and advances the stream position by eight bytes. + /// + /// The eight-byte unsigned integer to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt64(ulong value) + { + byte[] _buffer = new byte[8]; + if (Endianness == Endianness.BigEndian) + { + _buffer[0] = (byte)(value >> 56); + _buffer[1] = (byte)(value >> 48); + _buffer[2] = (byte)(value >> 40); + _buffer[3] = (byte)(value >> 32); + _buffer[4] = (byte)(value >> 24); + _buffer[5] = (byte)(value >> 16); + _buffer[6] = (byte)(value >> 8); + _buffer[7] = (byte)value; + } + else + { + _buffer[0] = (byte)value; + _buffer[1] = (byte)(value >> 8); + _buffer[2] = (byte)(value >> 16); + _buffer[3] = (byte)(value >> 24); + _buffer[4] = (byte)(value >> 32); + _buffer[5] = (byte)(value >> 40); + _buffer[6] = (byte)(value >> 48); + _buffer[7] = (byte)(value >> 56); + } + WriteBytes(_buffer); + } + /// + /// Writes an array of eight-byte unsigned integers to the current stream and advances the stream position by eight bytes times the number of values written. + /// + /// The array of eight-byte unsigned integers to write. + /// An I/O error occurs. + /// The stream is closed. + [CLSCompliant(false)] + public void WriteUInt64Array(ulong[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteUInt64(values[i]); + } + } + /// + /// Writes an arbitrary object to the current stream and advances the stream position by the number of bytes needed to store the object. + /// + /// The object to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteObject(object value) + { + if (value == null) + return; + + if (value is object[]) + { + object[] array = (object[])value; + for (int i = 0; i < array.Length; i++) + { + WriteObject(array[i]); + } + return; + } + + Type objectType = value.GetType(); + + if (objectType == typeof(Boolean)) + { + WriteBoolean((bool)value); + return; + } + else if (objectType == typeof(Byte)) + { + WriteByte((byte)value); + return; + } + else if (objectType == typeof(Byte[])) + { + WriteBytes((byte[])value); + return; + } + else if (objectType == typeof(SByte)) + { + WriteSByte((sbyte)value); + return; + } + else if (objectType == typeof(String)) + { + WriteLengthPrefixedString((string)value); + return; + } + else if (objectType == typeof(Char)) + { + WriteChar((char)value); + return; + } + else if (objectType == typeof(Char[])) + { + WriteCharArray((char[])value); + return; + } + else if (objectType == typeof(Single)) + { + WriteSingle((float)value); + return; + } + else if (objectType == typeof(Double)) + { + WriteDouble((double)value); + return; + } + else if (objectType == typeof(Int16)) + { + WriteInt16((short)value); + return; + } + else if (objectType == typeof(Int32)) + { + WriteInt32((int)value); + return; + } + else if (objectType == typeof(Int64)) + { + WriteInt64((long)value); + return; + } + else if (objectType == typeof(UInt16)) + { + WriteUInt16((ushort)value); + return; + } + else if (objectType == typeof(UInt32)) + { + WriteUInt32((uint)value); + return; + } + else if (objectType == typeof(UInt64)) + { + WriteUInt64((ulong)value); + return; + } + else if (objectType == typeof(DateTime)) + { + WriteDateTime((DateTime)value); + return; + } + + System.Reflection.FieldInfo[] fis = (objectType.GetFields(System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)); + foreach (System.Reflection.FieldInfo fi in fis) + { + // Type fieldType = fi.FieldType; + WriteObject(fi.GetValue(value)); + } + } + /// + /// Writes an array of arbitrary objects to the current stream and advances the stream position by the number of bytes needed to store the objects. + /// + /// The array of objects to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteObjectArray(object[] values) + { + for (int i = 0; i < values.Length; i++) + { + WriteObject(values[i]); + } + } + + /// + /// Writes a in a format that encodes the property in a 2-bit field + /// and the property in a 62-bit field. + /// + /// An I/O error occurs. + /// The stream is closed. + public void WriteDateTime(DateTime value) + { + WriteInt64(value.ToBinary()); + } + + /// + /// Encodes a into a 32-bit value as used in MS-DOS and Windows FAT directory entries. + /// + /// Date time. + /// + /// Each portion of the time stamp (year, month etc.) is encoded within specific bits of the 32-bit timestamp. The epoch for this time format is 1980. This format has a granularity of 2 seconds. + /// + public void WriteDOSFileTime(DateTime dateTime) + { + // The 32 bit date and time format used in the MSDOS and Windows FAT directory entry + + // Year Month Day Hour Min Seconds / 2 + // Bits 31-25 24-21 20-16 15-11 10-5 4-0 + + /* + cal.set(Calendar.YEAR, (int)((dosTime >> 25) & 0x7F) + 1980); + cal.set(Calendar.MONTH, (int)((dosTime >> 21) & 0x0f) - 1); + cal.set(Calendar.DATE, (int)(dosTime >> 16) & 0x1f); + cal.set(Calendar.HOUR_OF_DAY, (int)(dosTime >> 11) & 0x1f); + cal.set(Calendar.MINUTE, (int)(dosTime >> 5) & 0x3f); + cal.set(Calendar.SECOND, (int)(dosTime << 1) & 0x3e); + cal.set(Calendar.MILLISECOND, 0); + */ + + uint seconds = (uint)(dateTime.Second / 2); + uint min = (uint)dateTime.Minute; + uint hour = (uint)dateTime.Hour; + uint day = (uint)dateTime.Day; + uint month = (uint)dateTime.Month; + uint year = (uint)dateTime.Year - 1973; // huh? + + uint value = 0;//e (seconds | (min << 6) | (hour << 11) | (day << 16) | (month << 21) | (year << 25)); + WriteUInt32(value); // (int)(dateTime.Second / 2)); + } + + /// + /// Writes a four-byte floating-point value to the current stream and advances the stream position by four bytes. + /// + /// The four-byte floating-point value to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteSingle(float value) + { + byte[] buffer = BitConverter.GetBytes(value); + byte[] _buffer = new byte[4]; + if (Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[3]; + _buffer[1] = buffer[2]; + _buffer[2] = buffer[1]; + _buffer[3] = buffer[0]; + } + else + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + } + WriteBytes(_buffer); + } + public void WriteSingleArray(float[] value) + { + for (int i = 0; i < value.Length; i++) + { + WriteSingle(value[i]); + } + } + + /// + /// Writes an eight-byte floating-point value to the current stream and advances the stream position by eight bytes. + /// + /// The eight-byte floating-point value to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteDouble(double value) + { + byte[] buffer = BitConverter.GetBytes(value); + byte[] _buffer = new byte[8]; + if (Endianness == Endianness.BigEndian) + { + _buffer[0] = buffer[7]; + _buffer[1] = buffer[6]; + _buffer[2] = buffer[5]; + _buffer[3] = buffer[4]; + _buffer[4] = buffer[3]; + _buffer[5] = buffer[2]; + _buffer[6] = buffer[1]; + _buffer[7] = buffer[0]; + } + else + { + _buffer[0] = buffer[0]; + _buffer[1] = buffer[1]; + _buffer[2] = buffer[2]; + _buffer[3] = buffer[3]; + _buffer[4] = buffer[4]; + _buffer[5] = buffer[5]; + _buffer[6] = buffer[6]; + _buffer[7] = buffer[7]; + } + WriteBytes(_buffer); + } + /// + /// Writes a decimal value to the current stream and advances the stream position by sixteen bytes. + /// + /// The decimal value to write. + /// An I/O error occurs. + /// The stream is closed. + public void WriteDecimal(decimal value) + { + int[] bits = decimal.GetBits(value); + int lo = bits[0], mid = bits[1], hi = bits[2], flags = bits[3]; + + byte[] buffer = new byte[16]; + if (Endianness == Endianness.LittleEndian) + { + buffer[0] = (byte)lo; + buffer[1] = (byte)(lo >> 8); + buffer[2] = (byte)(lo >> 16); + buffer[3] = (byte)(lo >> 24); + buffer[4] = (byte)mid; + buffer[5] = (byte)(mid >> 8); + buffer[6] = (byte)(mid >> 16); + buffer[7] = (byte)(mid >> 24); + buffer[8] = (byte)hi; + buffer[9] = (byte)(hi >> 8); + buffer[10] = (byte)(hi >> 16); + buffer[11] = (byte)(hi >> 24); + buffer[12] = (byte)flags; + buffer[13] = (byte)(flags >> 8); + buffer[14] = (byte)(flags >> 16); + buffer[15] = (byte)(flags >> 24); + } + else + { + buffer[15] = (byte)lo; + buffer[14] = (byte)(lo >> 8); + buffer[13] = (byte)(lo >> 16); + buffer[12] = (byte)(lo >> 24); + buffer[11] = (byte)mid; + buffer[10] = (byte)(mid >> 8); + buffer[9] = (byte)(mid >> 16); + buffer[9] = (byte)(mid >> 24); + buffer[7] = (byte)hi; + buffer[6] = (byte)(hi >> 8); + buffer[5] = (byte)(hi >> 16); + buffer[4] = (byte)(hi >> 24); + buffer[3] = (byte)flags; + buffer[2] = (byte)(flags >> 8); + buffer[1] = (byte)(flags >> 16); + buffer[0] = (byte)(flags >> 24); + } + WriteBytes(buffer); + } + + public void WriteVersion(Version version) + { + WriteVersion(version, 4); + } + public void WriteVersion(Version version, int count) + { + switch (count) + { + case 1: + { + WriteByte(1); + WriteInt32(version.Major); + break; + } + case 2: + { + WriteByte(2); + WriteInt32(version.Major); + WriteInt32(version.Minor); + break; + } + case 3: + { + WriteByte(3); + WriteInt32(version.Major); + WriteInt32(version.Minor); + WriteInt32(version.Build); + break; + } + case 4: + { + WriteByte(4); + WriteInt32(version.Major); + WriteInt32(version.Minor); + WriteInt32(version.Build); + WriteInt32(version.Revision); + break; + } + } + } + + public long CountAlignment(int paddingCount) + { + return CountAlignment(paddingCount, 0); + } + public long CountAlignment(int paddingCount, int dataCount) + { + long position = (BaseStream.Position + dataCount); + int num = (int)(position % paddingCount); + return num; + } + + public void WriteDoubleEndianInt16(short value) + { + WriteInt16(value); + SwapEndianness(); + WriteInt16(value); + SwapEndianness(); + } + public void WriteDoubleEndianInt32(int value) + { + WriteInt32(value); + SwapEndianness(); + WriteInt32(value); + SwapEndianness(); + } + public void WriteDoubleEndianInt64(long value) + { + WriteInt64(value); + SwapEndianness(); + WriteInt64(value); + SwapEndianness(); + } + [CLSCompliant(false)] + public void WriteDoubleEndianUInt16(ushort value) + { + WriteUInt16(value); + SwapEndianness(); + WriteUInt16(value); + SwapEndianness(); + } + [CLSCompliant(false)] + public void WriteDoubleEndianUInt32(uint value) + { + WriteUInt32(value); + SwapEndianness(); + WriteUInt32(value); + SwapEndianness(); + } + [CLSCompliant(false)] + public void WriteDoubleEndianUInt64(ulong value) + { + WriteUInt64(value); + SwapEndianness(); + WriteUInt64(value); + SwapEndianness(); + } + + public void WriteUInt16String(string value) + { + WriteUInt16String(value, DefaultEncoding); + } + public void WriteUInt16String(string value, Encoding encoding) + { + ushort length = (ushort)value.Length; + byte[] input = encoding.GetBytes(value); + byte[] output = new byte[length]; + Array.Copy(input, 0, output, 0, output.Length); + WriteUInt16(length); + WriteBytes(output); + } + + public void WriteUInt16SizedString(string value) + { + WriteUInt16SizedString(value, DefaultEncoding); + } + public void WriteUInt16SizedString(string value, Encoding encoding) + { + ushort length = (ushort)value.Length; + byte[] input = encoding.GetBytes(value); + byte[] output = new byte[length]; + Array.Copy(input, 0, output, 0, output.Length); + WriteBytes(output); + } + + public void WriteInt64String(string value) + { + WriteInt64(value.Length); + WriteFixedLengthString(value); + } + + /// + /// Clears all buffers for the associated and causes + /// any buffered data to be written to the underlying device. + /// + public void Flush() + { + BaseStream.Flush(); + } + /// + /// Closes the underlying . + /// + public void Close() + { + BaseStream.Close(); + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/InvalidDataFormatException.cs b/editor-dotnet/src/lib/MBS.Editor.Core/InvalidDataFormatException.cs new file mode 100644 index 0000000..7cb78db --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/InvalidDataFormatException.cs @@ -0,0 +1,29 @@ +namespace MBS.Editor.Core; + +public class InvalidDataFormatException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public InvalidDataFormatException() : base() { } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public InvalidDataFormatException(string? message) : base(message) { } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public InvalidDataFormatException(string? message, Exception? innerException) : base(message, innerException) { } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/MBS.Editor.Core.csproj b/editor-dotnet/src/lib/MBS.Editor.Core/MBS.Editor.Core.csproj index bb23fb7..192a048 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/MBS.Editor.Core.csproj +++ b/editor-dotnet/src/lib/MBS.Editor.Core/MBS.Editor.Core.csproj @@ -1,9 +1,10 @@ - - - - net8.0 - enable - enable - - - + + + + + + net8.0 + enable + enable + + \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs index 9d59c2f..c342e97 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs @@ -3,5 +3,34 @@ public class ObjectModel { public PropertyCollection Properties { get; } = new PropertyCollection(); + + public virtual void Clear() + { + + } + + public virtual void CopyTo(ObjectModel dest) + { + + } + + public static T FromType() where T : ObjectModel, new() + { + T objectModel = new T(); + return objectModel; + } + public static ObjectModel FromType(Type type) + { + if (type.IsAbstract || !type.IsSubclassOf(typeof(ObjectModel))) + { + throw new InvalidCastException("type must be a non-abstract subclass of ObjectModel"); + } + ObjectModel? objectModel = type.Assembly.CreateInstance(type.FullName) as ObjectModel; + if (objectModel == null) + { + throw new TypeLoadException("could not create ObjectModel from type name"); + } + return objectModel; + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelMetadata.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelMetadata.cs new file mode 100644 index 0000000..b3a9425 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelMetadata.cs @@ -0,0 +1,6 @@ +namespace MBS.Editor.Core; + +public class ObjectModelMetadata +{ + public string[] Path { get; set; } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs new file mode 100644 index 0000000..80e9a03 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs @@ -0,0 +1,37 @@ +namespace MBS.Editor.Core; + +public class ObjectModelNotSupportedException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public ObjectModelNotSupportedException() : base() { } + + /// + /// Initializes a new instance of the class + /// with a specified error message. + /// + /// The message that describes the error. + public ObjectModelNotSupportedException(string? message) : base(message) { } + + /// + /// Initializes a new instance of the class + /// with a specified error message and a reference to the inner exception that is the + /// cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference + /// (Nothing in Visual Basic) if no inner exception is specified. + /// + public ObjectModelNotSupportedException(string? message, Exception? innerException) : base(message, innerException) { } + + public Type? ExpectedObjectModelType { get; } + public Type? ActualObjectModelType { get; } + + public ObjectModelNotSupportedException(string? message, Type expectedObjectModelType, Type actualObjectModelType) + { + + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseField.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseField.cs new file mode 100644 index 0000000..72cfecd --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseField.cs @@ -0,0 +1,89 @@ +// +// DatabaseField.cs - represents a field (column) in a DatabaseObjectModel +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace MBS.Editor.Core.ObjectModels.Database +{ + /// + /// Represents a field (column) in a . + /// + public class DatabaseField : ICloneable + { + + public class DatabaseFieldCollection + : System.Collections.ObjectModel.Collection + { + public DatabaseField Add(string Name, object Value = null, Type dataType = null) + { + DatabaseField df = new DatabaseField(); + df.Name = Name; + df.Value = Value; + df.DataType = dataType; + + base.Add(df); + return df; + } + + public DatabaseField this[string Name] + { + get + { + for (int i = 0; i < Count; i++) + { + if (this[i].Name.Equals(Name)) return this[i]; + } + return null; + } + } + } + + public DatabaseField(string name = "", object value = null) + { + Name = name; + Value = value; + } + + public string Name { get; set; } = String.Empty; + public object Value { get; set; } = null; + public Type DataType { get; set; } = null; + + public object Clone() + { + DatabaseField clone = new DatabaseField(); + clone.Name = (Name.Clone() as string); + if (Value is ICloneable) + { + clone.Value = (Value as ICloneable).Clone(); + } + else + { + clone.Value = Value; + } + return clone; + } + + public override string ToString() + { + return String.Format("{0} = {1}", Name, Value); + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseObjectModel.cs new file mode 100644 index 0000000..0298076 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseObjectModel.cs @@ -0,0 +1,92 @@ +// +// DatabaseObjectModel.cs - provides an ObjectModel for manipulating databases +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +namespace MBS.Editor.Core.ObjectModels.Database +{ + /// + /// Provides an for manipulating databases. + /// + public class DatabaseObjectModel : ObjectModel + { + private static ObjectModelMetadata _omr = null; + public static ObjectModelMetadata Metadata + { + get + { + if (_omr == null) + { + _omr = new ObjectModelMetadata(); + _omr.Path = new string[] { "General", "Database" }; + } + return _omr; + } + } + + public string Name { get; set; } = null; + public DatabaseTable.DatabaseTableCollection Tables { get; } = new DatabaseTable.DatabaseTableCollection(); + + public override void Clear() + { + Tables.Clear(); + } + public override void CopyTo(ObjectModel where) + { + DatabaseObjectModel clone = (where as DatabaseObjectModel); + if (clone == null) + throw new ObjectModelNotSupportedException(); + + for (int i = 0; i < Tables.Count; i++) + { + clone.Tables.Add(Tables[i].Clone() as DatabaseTable); + } + } + +/* + public static DatabaseObjectModel FromMarkup(MarkupTagElement tag) + { + DatabaseObjectModel db = new DatabaseObjectModel(); + for (int i = 0; i < tag.Elements.Count; i++) + { + MarkupTagElement tag2 = (tag.Elements[i] as MarkupTagElement); + if (tag2 == null) continue; + if (tag2.FullName == "Tables") + { + foreach (MarkupElement elTable in tag2.Elements ) + { + MarkupTagElement tagTable = (elTable as MarkupTagElement); + if (tagTable == null) continue; + if (tagTable.FullName != "Table") continue; + + MarkupAttribute attName = tag2.Attributes["Name"]; + if (attName == null) continue; + + DatabaseTable dt = new DatabaseTable(); + dt.Name = attName.Value; + db.Tables.Add(dt); + } + } + } + return db; + } +*/ + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseRecord.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseRecord.cs new file mode 100644 index 0000000..875566c --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseRecord.cs @@ -0,0 +1,70 @@ +// +// DatabaseRecord.cs - represents a record (row) in a DatabaseObjectModel +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace MBS.Editor.Core.ObjectModels.Database +{ + /// + /// Represents a record (row) in a . + /// + public class DatabaseRecord : ICloneable + { + + public class DatabaseRecordCollection + : System.Collections.ObjectModel.Collection + { + public DatabaseRecord Add(params DatabaseField[] parameters) + { + DatabaseRecord dr = new DatabaseRecord(); + foreach (DatabaseField df in parameters) + { + dr.Fields.Add(df.Name, df.Value); + } + return dr; + } + } + + public DatabaseRecord(params DatabaseField[] fields) + { + for (int i = 0; i < fields.Length; i++) + { + Fields.Add(fields[i]); + } + } + + private DatabaseField.DatabaseFieldCollection mvarFields = new DatabaseField.DatabaseFieldCollection (); + public DatabaseField.DatabaseFieldCollection Fields + { + get { return mvarFields; } + } + + public object Clone() + { + DatabaseRecord clone = new DatabaseRecord(); + for (int i = 0; i < Fields.Count; i++) + { + clone.Fields.Add(Fields[i].Clone() as DatabaseField); + } + return clone; + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseTable.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseTable.cs new file mode 100644 index 0000000..69c4356 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Database/DatabaseTable.cs @@ -0,0 +1,95 @@ +// +// DatabaseTable.cs - represents a table in a DatabaseObjectModel +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; + +namespace MBS.Editor.Core.ObjectModels.Database +{ + /// + /// Represents a table in a . + /// + public class DatabaseTable : ICloneable + { + public class DatabaseTableCollection + : System.Collections.ObjectModel.Collection + { + public DatabaseTable this[string name] + { + get + { + if (_itemsByName.ContainsKey(name)) + return _itemsByName[name]; + return null; + } + } + + private Dictionary _itemsByName = new Dictionary(); + protected override void ClearItems() + { + base.ClearItems(); + _itemsByName.Clear(); + } + protected override void InsertItem(int index, DatabaseTable item) + { + base.InsertItem(index, item); + _itemsByName[item.Name] = item; + } + protected override void RemoveItem(int index) + { + if (_itemsByName.ContainsKey(this[index].Name)) + _itemsByName.Remove(this[index].Name); + base.RemoveItem(index); + } + } + + /// + /// Gets or sets the name of the . + /// + /// The name of the . + public string Name { get; set; } = String.Empty; + /// + /// Gets a collection of instances representing the fields (columns) in the . + /// + /// The fields (columns) in the . + public DatabaseField.DatabaseFieldCollection Fields { get; } = new DatabaseField.DatabaseFieldCollection(); + /// + /// Gets a collection of instances representing the records (rows) in the . + /// + /// The records (rows) in the . + public DatabaseRecord.DatabaseRecordCollection Records { get; } = new DatabaseRecord.DatabaseRecordCollection(); + + public object Clone() + { + DatabaseTable clone = new DatabaseTable(); + clone.Name = (Name.Clone() as string); + for (int i = 0; i < Fields.Count; i++) + { + clone.Fields.Add(Fields[i].Clone() as DatabaseField); + } + for (int i = 0; i < Records.Count; i++) + { + clone.Records.Add(Records[i].Clone() as DatabaseRecord); + } + return clone; + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs new file mode 100644 index 0000000..34cd688 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs @@ -0,0 +1,25 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public abstract class FileSource +{ + public long Length { get { return GetLengthInternal(); } } + + protected abstract long GetLengthInternal(); + protected abstract byte[] GetDataInternal(long offset, long length); + + public byte[] GetData() { return GetData(0, GetLengthInternal()); } + public byte[] GetData(long offset, long length) + { + byte[] data = GetDataInternal(offset, length); + MemoryStream msInput = new MemoryStream(data); + /* + for (int i = 0; i < Transformations.Count; i++) + { + System.IO.MemoryStream msOutput = new System.IO.MemoryStream(); + Transformations[i].Function(this, msInput, msOutput); + msInput = msOutput; + } + */ + return msInput.ToArray(); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/ByteArrayFileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/ByteArrayFileSource.cs new file mode 100644 index 0000000..3455f6d --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/ByteArrayFileSource.cs @@ -0,0 +1,55 @@ +// +// MemoryFileSource.cs - provides a FileSource for retrieving file data from a byte array +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Data; + +namespace MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +/// +/// Provides a for retrieving file data from a . +/// +public class ByteArrayFileSource : FileSource +{ + public byte[] Data { get; set; } + + public ByteArrayFileSource(byte[] data) + { + Data = data; + } + + protected override byte[] GetDataInternal(long offset, long length) + { + long realLength = Math.Min(length, Data.Length); + byte[] realData = Data; + long remaining = realData.Length - offset; + realLength = Math.Min(realLength, remaining); + + byte[] data = new byte[realLength]; + Array.Copy(realData, offset, data, 0, realLength); + return data; + } + + protected override long GetLengthInternal() + { + return Data.Length; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/CompressedEmbeddedFileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/CompressedEmbeddedFileSource.cs new file mode 100644 index 0000000..ea1bf7f --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/CompressedEmbeddedFileSource.cs @@ -0,0 +1,54 @@ +using MBS.Core.Collections; +using MBS.Editor.Core.Compression; + +namespace MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +public class CompressedEmbeddedFileSource : EmbeddedFileSource +{ + public CompressionModule? CompressionModule { get; set; } = null; + + public long CompressedLength { get; set; } + private long DecompressedLength { get; set; } + protected override long ActualLength => DecompressedLength; + + private byte[]? _decompressedData = null; + + protected override byte[] GetDataInternal(long offset, long length) + { + if (_decompressedData == null) + { + Stream.Seek(Offset + offset, SeekOrigin.Begin); + + byte[] compressedData = new byte[CompressedLength]; + Stream.Read(compressedData, 0, compressedData.Length); + Console.WriteLine("compressed data: " + compressedData.ToString(" ", "x")); + + byte[] decompressedData = compressedData; + if (CompressionModule != null) + { + decompressedData = CompressionModule.Decompress(compressedData); + } + _decompressedData = decompressedData; + Console.WriteLine("decompressed data: " + decompressedData.ToString(" ", "x")); + } + + if (offset + length > _decompressedData.Length) + { + Console.WriteLine(String.Format("embedded file offset: {0}", Offset)); + Console.WriteLine(String.Format("requested offset: {0}", offset)); + Console.WriteLine(String.Format("requested length: {0}", length)); + Console.WriteLine(String.Format("actual stream length: {0}", _decompressedData.Length)); + throw new ArgumentOutOfRangeException("offset + length", "embedded file offset + requested offset + requested length extends past the actual length of the underlying stream"); + } + + byte[] data = new byte[length]; + Array.Copy(_decompressedData, offset, data, 0, data.Length); + return data; + } + + public CompressedEmbeddedFileSource(Stream stream, long offset, long compressedLength, long decompressedLength) : base(stream, offset, decompressedLength) + { + CompressedLength = compressedLength; + DecompressedLength = decompressedLength; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs new file mode 100644 index 0000000..9327a10 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs @@ -0,0 +1,34 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +public class EmbeddedFileSource : FileSource +{ + public Stream Stream { get; } + public long Offset { get; set; } + + protected virtual long ActualLength { get; } + + protected override long GetLengthInternal() + { + return ActualLength; + } + protected override byte[] GetDataInternal(long offset, long length) + { + if (Offset + offset + length >= Stream.Length) + { + throw new ArgumentOutOfRangeException("embedded file offset + requested offset + requested length extends past the actual length of the underlying stream"); + } + + Stream.Seek(Offset + offset, SeekOrigin.Begin); + + byte[] data = new byte[length]; + Stream.Read(data, 0, data.Length); + return data; + } + + public EmbeddedFileSource(Stream stream, long offset, long length) + { + Stream = stream; + Offset = offset; + ActualLength = length; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/StreamFileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/StreamFileSource.cs new file mode 100644 index 0000000..43f00b2 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/StreamFileSource.cs @@ -0,0 +1,59 @@ +// +// StreamFileSource.cs - provides a FileSource for retrieving file data from a System.IO.Stream +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2024 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +/// +/// Provides a for retrieving file data from a . +/// +public class StreamFileSource : FileSource +{ + public Stream BaseStream { get; set; } + public StreamFileSource(Stream stream) + { + BaseStream = stream; + } + + protected override byte[] GetDataInternal(long offset, long length) + { + byte[] buffer = new byte[length]; + try + { + if (offset + length > BaseStream.Length) + { + throw new ArgumentOutOfRangeException("offset + length is out of range"); + } + } + catch (NotSupportedException ex) + { + // continue anyway + } + + BaseStream.Seek(offset, SeekOrigin.Begin); + BaseStream.Read(buffer, 0, (int)length); + return buffer; + } + + protected override long GetLengthInternal() + { + return BaseStream.Length; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemCustomDetailCollection.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemCustomDetailCollection.cs new file mode 100644 index 0000000..344c520 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemCustomDetailCollection.cs @@ -0,0 +1,29 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public class FileSystemCustomDetailCollection +{ + private struct _item + { + public string id; + public string title; + } + + private Dictionary _dict = new Dictionary(); + + public void Add(string id, string title) + { + _item item = new _item(); + item.id = id; + item.title = title; + _dict[id] = item; + } + public bool Contains(string id) + { + return _dict.ContainsKey(id); + } + public string GetTitle(string id) + { + _item item = _dict[id]; + return item.title; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFile.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFile.cs new file mode 100644 index 0000000..d0d27af --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFile.cs @@ -0,0 +1,20 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public class FileSystemFile : FileSystemItem +{ + public FileSource? Source { get; set; } = null; + + public DateTime? ModificationTimestamp { get; set; } = null; + + public Dictionary CustomDetails { get; } = new Dictionary(); + + public long Size + { + get + { + if (Source == null) + return 0; + return Source.Length; + } + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFolder.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFolder.cs new file mode 100644 index 0000000..ae9d73c --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemFolder.cs @@ -0,0 +1,12 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public class FileSystemFolder : FileSystemItem, IFileSystemItemContainer +{ + + public FileSystemItemCollection Items { get; } + + public FileSystemFolder() + { + Items = new FileSystemItemCollection(this); + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItem.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItem.cs new file mode 100644 index 0000000..9cd9159 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItem.cs @@ -0,0 +1,8 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public class FileSystemItem +{ + public FileSystemObjectModel FileSystem { get { return Parent?.FileSystem; } } + public string? Name { get; set; } = null; + public IFileSystemItemContainer? Parent { get; internal set; } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs new file mode 100644 index 0000000..38cfef2 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs @@ -0,0 +1,181 @@ + +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public class FileSystemItemCollection + : System.Collections.ObjectModel.Collection +{ + public IFileSystemItemContainer Parent { get; } + public FileSystemItemCollection(IFileSystemItemContainer parent) + { + Parent = parent; + } + + public bool Contains(string filename) + { + return this[filename] != null; + } + public FileSystemItem? this[string filename] + { + get + { + foreach (FileSystemItem item in this) + { + if (item.Name == filename) + { + return item; + } + } + return null; + } + } + + protected override void ClearItems() + { + for (int i = 0; i < Count; i++) + { + this[i].Parent = null; + } + base.ClearItems(); + } + protected override void InsertItem(int index, FileSystemItem item) + { + base.InsertItem(index, item); + item.Parent = Parent; + } + protected override void RemoveItem(int index) + { + this[index].Parent = null; + base.RemoveItem(index); + } + + + public FileSystemFolder AddFolder(string name) + { + string[] path = name.Split(Parent.FileSystem.PathSeparators, StringSplitOptions.None); + FileSystemFolder parent = null; + for (int i = 0; i < path.Length; i++) + { + if (parent == null) + { + parent = this[path[i]] as FileSystemFolder; + if (parent == null) + { + FileSystemFolder f = new FileSystemFolder(); + f.Name = path[i]; + Add(f); + parent = f; + } + } + else + { + FileSystemFolder new_parent = parent.Items[path[i]] as FileSystemFolder; + if (new_parent == null) + { + new_parent = new FileSystemFolder(); + new_parent.Name = path[i]; + parent.Items.Add(new_parent); + } + parent = new_parent; + } + if (parent == null) throw new DirectoryNotFoundException(); + } + return parent; + } + public FileSystemFile AddFile(string name, FileSource? source = null) + { + if (name == null) name = String.Empty; + string[] path = name.Split(Parent.FileSystem.PathSeparators, StringSplitOptions.None); + FileSystemFolder parent = null; + for (int i = 0; i < path.Length - 1; i++) + { + if (parent == null) + { + if (Contains(path[i])) + { + parent = this[path[i]] as FileSystemFolder; + } + else + { + parent = AddFolder(path[i]); + } + } + else + { + if (parent.Items.Contains(path[i])) + { + parent = parent.Items[path[i]] as FileSystemFolder; + } + else + { + parent = parent.Items.AddFolder(path[i]); + } + } + + if (parent == null) + { + throw new System.IO.DirectoryNotFoundException(); + } + } + + FileSystemFile file = new FileSystemFile(); + file.Name = path[path.Length - 1]; + file.Source = source; + if (parent == null) + { + Add(file); + } + else + { + parent.Items.Add(file); + } + + return file; + } + + + public FileSystemFile[] GetAllFiles() + { + List list = new List(); + for (int i = 0; i < Count; i++) + { + if (this[i] is FileSystemFile Peter) + { + list.Add(Peter); + } + else if (this[i] is FileSystemFolder folder) + { + FileSystemFile[] files2 = folder.Items.GetAllFiles(); + list.AddRange(files2); + } + } + return list.ToArray(); + } + + public FileSystemFile[] GetFiles() + { + List list = new List(); + for (int i = 0; i < Count; i++) + { + if (this[i] is FileSystemFile Peter) + { + list.Add(Peter); + } + } + return list.ToArray(); + } + + public FileSystemFolder[] GetFolders() + { + List list = new List(); + for (int i = 0; i < Count; i++) + { + if (this[i] is FileSystemFolder folder) + { + list.Add(folder); + } + } + return list.ToArray(); + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemObjectModel.cs new file mode 100644 index 0000000..2cfcc06 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemObjectModel.cs @@ -0,0 +1,20 @@ + + +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public class FileSystemObjectModel : ObjectModel, IFileSystemItemContainer +{ + public FileSystemObjectModel FileSystem { get { return this; } } + public FileSystemItemCollection Items { get; } + public FileSystemObjectModel() + { + Items = new FileSystemItemCollection(this); + } + + public FileSystemCustomDetailCollection CustomDetails { get; } = new FileSystemCustomDetailCollection(); + + public string[] PathSeparators { get; set; } = { "/", "\\" }; // System.IO.Path.DirectorySeparatorChar.ToString(), System.IO.Path.AltDirectorySeparatorChar.ToString() }; + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/IFileSystemItemContainer.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/IFileSystemItemContainer.cs new file mode 100644 index 0000000..33c5ba9 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/IFileSystemItemContainer.cs @@ -0,0 +1,7 @@ +namespace MBS.Editor.Core.ObjectModels.FileSystem; + +public interface IFileSystemItemContainer +{ + FileSystemObjectModel FileSystem { get; } + FileSystemItemCollection Items { get; } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/Internal/UTFTableInfo.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/Internal/UTFTableInfo.cs new file mode 100644 index 0000000..c1506f6 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/Internal/UTFTableInfo.cs @@ -0,0 +1,17 @@ +namespace MBS.Editor.Plugins.CRI.DataFormats.Database.UTF.Internal; + +internal struct UTFTABLEINFO +{ + public long utfOffset; + + public int tableSize; + public int schemaOffset; + public int rowsOffset; + public int stringTableOffset; + public int dataOffset; + public uint tableNameStringOffset; + public short tableColumns; + public short rowWidth; + public int tableRows; + public int stringTableSize; +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnDataType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnDataType.cs new file mode 100644 index 0000000..066ac32 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnDataType.cs @@ -0,0 +1,77 @@ +// +// UTFColumnDataType.cs - CRI Middleware UTF table column data types +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.Database.UTF; + +/// +/// The data type for a column in a UTF table. +/// +public enum UTFColumnDataType : byte +{ + /// + /// Mask value for combining with . + /// + Mask = 0x0f, + /// + /// The column represents a variable-length array of data. + /// + Data = 0x0b, + /// + /// The column represents a variable-length . + /// + String = 0x0a, + /// + /// The column represents a value. + /// + Float = 0x08, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Long2 = 0x07, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Long = 0x06, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Int2 = 0x05, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Int = 0x04, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Short2 = 0x03, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Short = 0x02, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Byte2 = 0x01, + /// + /// The column represents a value. There may or may not be a distinction between signed and unsigned types. + /// + Byte = 0x00 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnStorageType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnStorageType.cs new file mode 100644 index 0000000..bdf30e5 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFColumnStorageType.cs @@ -0,0 +1,45 @@ +// +// UTFColumnStorageType.cs - CRI Middleware UTF table column storage types +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.Database.UTF; + +/// +/// The storage type for a column in a UTF table. +/// +public enum UTFColumnStorageType : byte +{ + /// + /// Mask value for combining with . + /// + Mask = 0xf0, + /// + /// Data in this column is stored per row, with a single value written for each ROW in the table. + /// + PerRow = 0x50, + /// + /// Data in this column is constant regardless of row, with a single value written for each COLUMN in the table. + /// + Constant = 0x30, + /// + /// Data in this column is declared NULL for all rows in the table. No data is written for this column. + /// + Zero = 0x10 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFDataFormat.cs new file mode 100644 index 0000000..90619de --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/Database/UTF/UTFDataFormat.cs @@ -0,0 +1,644 @@ +// +// UTFDataFormat.cs - COMPLETED - Implementation of CRI Middleware UTF table (used in CPK) +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.Database.UTF; + +using System; +using System.Collections.Generic; + +using MBS.Core; +using MBS.Core.Collections; + +using MBS.Editor.Core; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Database; +using MBS.Editor.Plugins.CRI.DataFormats.Database.UTF.Internal; + +public class UTFDataFormat : DataFormat +{ + private static DataFormatMetadata _dfr; + public static DataFormatMetadata Metadata + { + get + { + if (_dfr == null) + { + _dfr = new DataFormatMetadata(); + } + return _dfr; + } + } + + private UTFTABLEINFO ReadUTFTableInfo(Reader br) + { + UTFTABLEINFO info = new UTFTABLEINFO(); + info.utfOffset = br.BaseStream.Position; + info.tableSize = br.ReadInt32(); + info.schemaOffset = 0x20; + info.rowsOffset = br.ReadInt32(); + info.stringTableOffset = br.ReadInt32(); + info.dataOffset = br.ReadInt32(); + + // CPK Header & UTF Header are ignored, so add 8 to each offset + + info.tableNameStringOffset = br.ReadUInt32(); // 00000007 + info.tableColumns = br.ReadInt16(); // 0023 + info.rowWidth = br.ReadInt16(); // 007e + info.tableRows = br.ReadInt32(); // 00000001 + info.stringTableSize = info.dataOffset - info.stringTableOffset; + return info; + } + + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + DatabaseObjectModel? utf = objectModel as DatabaseObjectModel; + if (utf == null) + throw new ObjectModelNotSupportedException(); + + Reader br = new Reader(stream); + string utf_signature = br.ReadFixedLengthString(4); + + if (utf_signature != "@UTF") + throw new InvalidDataFormatException(); // we are assuming passed in decrypted UTF from the CPK + + DatabaseTable dt = new DatabaseTable(); + + br.Endianness = Endianness.BigEndian; + + UTFTABLEINFO info = ReadUTFTableInfo(br); + + int[] columnNameOffsets = new int[info.tableColumns]; + long[] constantOffsets = new long[info.tableColumns]; + UTFColumnStorageType[] storageTypes = new UTFColumnStorageType[info.tableColumns]; + UTFColumnDataType[] dataTypes = new UTFColumnDataType[info.tableColumns]; + + // Read string table - remember, this is relative to UTF data WITH the "@UTF" signature + br.BaseStream.SavePosition(); + br.BaseStream.Seek(info.utfOffset + info.stringTableOffset + 4, SeekOrigin.Begin); + /* + while (br.PeekByte() == 0) + { + br.ReadByte(); + } + */ + byte[] stringTableData = br.ReadBytes(info.stringTableSize); + + MemoryStream maStringTable = new MemoryStream(stringTableData); + Reader stringTableReader = new Reader(maStringTable); + + stringTableReader.BaseStream.Seek(info.tableNameStringOffset, SeekOrigin.Begin); + dt.Name = stringTableReader.ReadNullTerminatedString(); + br.BaseStream.LoadPosition(); + + for (int i = 0; i < info.tableColumns; i++) + { + byte schema = br.ReadByte(); + columnNameOffsets[i] = br.ReadInt32(); + storageTypes[i] = (UTFColumnStorageType)(schema & (byte)UTFColumnStorageType.Mask); + dataTypes[i] = (UTFColumnDataType)(schema & (byte)UTFColumnDataType.Mask); + + object constantValue = null; + if (storageTypes[i] == UTFColumnStorageType.Constant) + { + constantOffsets[i] = br.BaseStream.Position; + switch (dataTypes[i]) + { + case UTFColumnDataType.Long: + case UTFColumnDataType.Long2: + case UTFColumnDataType.Data: + { + constantValue = br.ReadInt64(); + break; + } + case UTFColumnDataType.Float: + { + constantValue = br.ReadSingle(); + break; + } + case UTFColumnDataType.String: + { + int valueOffset = br.ReadInt32(); + stringTableReader.BaseStream.Seek(valueOffset, SeekOrigin.Begin); + constantValue = stringTableReader.ReadNullTerminatedString(); + break; + } + case UTFColumnDataType.Int: + case UTFColumnDataType.Int2: + { + constantValue = br.ReadInt32(); + break; + } + case UTFColumnDataType.Short: + case UTFColumnDataType.Short2: + { + constantValue = br.ReadInt16(); + break; + } + case UTFColumnDataType.Byte: + case UTFColumnDataType.Byte2: + { + constantValue = br.ReadByte(); + break; + } + default: + { + Console.WriteLine("cpk: ReadUTFTable: unknown data type for column " + i.ToString()); + break; + } + } + } + + dt.Fields.Add("Field" + i.ToString(), constantValue, SystemDataTypeForUTFDataType(dataTypes[i])); + } + + for (int i = 0; i < info.tableColumns; i++) + { + stringTableReader.BaseStream.Seek(columnNameOffsets[i], SeekOrigin.Begin); + dt.Fields[i].Name = stringTableReader.ReadNullTerminatedString(); + } + + for (int i = 0; i < info.tableRows; i++) + { + uint rowOffset = (uint)(info.utfOffset + 4 + info.rowsOffset + (i * info.rowWidth)); + uint rowStartOffset = rowOffset; + br.BaseStream.Seek(rowOffset, SeekOrigin.Begin); + + DatabaseRecord record = new DatabaseRecord(); + + for (int j = 0; j < info.tableColumns; j++) + { + UTFColumnStorageType storageType = storageTypes[j]; + UTFColumnDataType dataType = dataTypes[j]; + long constantOffset = constantOffsets[j] - 11; + + switch (storageType) + { + case UTFColumnStorageType.PerRow: + { + switch (dataType) + { + case UTFColumnDataType.String: + { + string value = null; + if (storageType == UTFColumnStorageType.Constant) + { + value = (dt.Fields[j].Value as string); + } + else + { + uint stringOffset = br.ReadUInt32(); + if (stringOffset < stringTableData.Length) + { + stringTableReader.BaseStream.Seek(stringOffset, SeekOrigin.Begin); + value = stringTableReader.ReadNullTerminatedString(); + } + } + record.Fields.Add(dt.Fields[j].Name, value); + break; + } + case UTFColumnDataType.Data: + { + uint varDataOffset = br.ReadUInt32(); + uint varDataSize = br.ReadUInt32(); + + byte[] value = null; + if (varDataOffset == 0 && varDataSize == 0) + { + value = null; + } + else + { + long realOffset = info.dataOffset + 8 + varDataOffset; + br.BaseStream.SavePosition(); + br.BaseStream.Seek(realOffset, SeekOrigin.Begin); + byte[] tableData = br.ReadBytes(varDataSize); + br.BaseStream.LoadPosition(); + value = tableData; + } + record.Fields.Add(dt.Fields[j].Name, value); + break; + } + case UTFColumnDataType.Long: + case UTFColumnDataType.Long2: + { + ulong value = br.ReadUInt64(); + record.Fields.Add(dt.Fields[j].Name, value); + + break; + } + case UTFColumnDataType.Int: + case UTFColumnDataType.Int2: + { + uint value = br.ReadUInt32(); + record.Fields.Add(dt.Fields[j].Name, value); + + break; + } + case UTFColumnDataType.Short: + case UTFColumnDataType.Short2: + { + ushort value = br.ReadUInt16(); + record.Fields.Add(dt.Fields[j].Name, value); + break; + } + case UTFColumnDataType.Float: + { + float value = br.ReadSingle(); + record.Fields.Add(dt.Fields[j].Name, value); + break; + } + case UTFColumnDataType.Byte: + case UTFColumnDataType.Byte2: + { + byte value = br.ReadByte(); + record.Fields.Add(dt.Fields[j].Name, value); + break; + } + } + break; + } + case UTFColumnStorageType.Constant: + { + record.Fields.Add(dt.Fields[j].Name, dt.Fields[j].Value); + continue; + } + case UTFColumnStorageType.Zero: + { + record.Fields.Add(dt.Fields[j].Name, null); + continue; + } + } + } + + dt.Records.Add(record); + } + utf.Tables.Add(dt); + } + + public static Type SystemDataTypeForUTFDataType(UTFColumnDataType dataType) + { + switch (dataType) + { + case UTFColumnDataType.Byte: + case UTFColumnDataType.Byte2: + { + return typeof(byte); + } + case UTFColumnDataType.Data: + { + return typeof(byte[]); + } + case UTFColumnDataType.Float: + { + return typeof(float); + } + case UTFColumnDataType.Int: + { + return typeof(uint); + } + case UTFColumnDataType.Int2: + { + return typeof(int); + } + case UTFColumnDataType.Long: + case UTFColumnDataType.Long2: + { + return typeof(long); + } + case UTFColumnDataType.Short: + case UTFColumnDataType.Short2: + { + return typeof(short); + } + case UTFColumnDataType.String: + { + return typeof(string); + } + } + return null; + } + public static UTFColumnDataType UTFDataTypeForSystemDataType(Type dataType) + { + if (dataType == typeof(byte)) return UTFColumnDataType.Byte; + else if (dataType == typeof(sbyte)) return UTFColumnDataType.Byte; + else if (dataType == typeof(byte[])) return UTFColumnDataType.Data; + else if (dataType == typeof(float)) return UTFColumnDataType.Float; + else if (dataType == typeof(int)) return UTFColumnDataType.Int2; + else if (dataType == typeof(uint)) return UTFColumnDataType.Int; + else if (dataType == typeof(long)) return UTFColumnDataType.Long; + else if (dataType == typeof(ulong)) return UTFColumnDataType.Long; + else if (dataType == typeof(short)) return UTFColumnDataType.Short; + else if (dataType == typeof(ushort)) return UTFColumnDataType.Short; + else if (dataType == typeof(string)) return UTFColumnDataType.String; + return UTFColumnDataType.Mask; + } + + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + DatabaseObjectModel? utf = objectModel as DatabaseObjectModel; + if (utf == null) + throw new ObjectModelNotSupportedException(); + + Writer bw = new Writer(stream); + bw.WriteFixedLengthString("@UTF"); + + DatabaseTable dt = utf.Tables[0]; + + bw.Endianness = Endianness.BigEndian; + + // do the hard work here to determine if a field should be recorded as zero or not + UTFColumnStorageType[] columnStorageTypes = new UTFColumnStorageType[dt.Fields.Count]; + UTFColumnDataType[] columnDataTypes = new UTFColumnDataType[dt.Fields.Count]; + for (int i = 0; i < dt.Fields.Count; i++) + { + columnStorageTypes[i] = UTFColumnStorageType.Zero; + columnDataTypes[i] = UTFDataTypeForSystemDataType(dt.Fields[i].DataType); + + if (dt.Fields[i].Value != null) + { + columnStorageTypes[i] = UTFColumnStorageType.Constant; + continue; + } + for (int j = 0; j < dt.Records.Count; j++) + { + if (dt.Records[j].Fields[i].Value != null) + { + columnStorageTypes[i] = UTFColumnStorageType.PerRow; + break; + } + } + } + + int tableSize = 24; // size of entire file = 32 - "@UTF".Length(4) - (size of table size field:4) = 32 - 8 = 24 + tableSize += (5 * dt.Fields.Count); // 5 * 36 = 204 5 * 35 = 195 + tableSize += (dt.Name.Length + 1); // 204 + "CpkHeader".Length + 1 = 214 + tableSize += 7; // "\0".Length // 214 + 7 = 221 + + int rowsOffset = 24 + (5 * dt.Fields.Count); + int stringTableOffset = rowsOffset; + short rowWidth = 0; + for (int i = 0; i < dt.Fields.Count; i++) + { + tableSize += (dt.Fields[i].Name.Length + 1); + if (columnStorageTypes[i] == UTFColumnStorageType.Constant) + { + int l = GetLengthForDataType(columnDataTypes[i]); + tableSize += l; + stringTableOffset += l; + rowsOffset += l; + + if (columnDataTypes[i] == UTFColumnDataType.String) + { + tableSize += ((string)dt.Fields[i].Value).Length + 1; + } + } + else if (columnStorageTypes[i] == UTFColumnStorageType.PerRow) + { + rowWidth += GetLengthForDataType(columnDataTypes[i]); + } + } + + for (int i = 0; i < dt.Records.Count; i++) + { + for (int j = 0; j < dt.Records[i].Fields.Count; j++) + { + if (columnStorageTypes[j] == UTFColumnStorageType.PerRow) + { + tableSize += GetLengthForDataType(columnDataTypes[j]); + stringTableOffset += GetLengthForDataType(columnDataTypes[j]); + if (columnDataTypes[j] == UTFColumnDataType.String) + { + tableSize += ((string)dt.Records[i].Fields[j].Value).Length + 1; + } + } + } + } + + // this is off... always at the same offset too (CpkTocInfo - 0x818 in cpk files) + // tableSize += 8; // this is correct, but, CpkFileBuilder chokes, unless it is omitted + tableSize = tableSize.Align(8); + + bw.WriteInt32(tableSize); + bw.WriteInt32(rowsOffset); + bw.WriteInt32(stringTableOffset); + bw.WriteInt32(tableSize); // data offset - same as table size? + bw.WriteUInt32(7); // "\0".Length + bw.WriteInt16((short)dt.Fields.Count); // 0023 + bw.WriteInt16(rowWidth); // 007e + bw.WriteInt32(dt.Records.Count); // 00000001 + + int columnNameOffset = (int)8 + (int)dt.Name.Length; // add space for "\0" string and dt.Name + 1 + + List stringTable = new List(); + stringTable.Add(""); + stringTable.Add(dt.Name); + for (int i = 0; i < dt.Fields.Count; i++) + { + byte schema = 0; + schema |= (byte)((byte)columnStorageTypes[i] | (byte)columnDataTypes[i]); + + bw.WriteByte(schema); + bw.WriteInt32(columnNameOffset); + + columnNameOffset += dt.Fields[i].Name.Length + 1; + stringTable.Add(dt.Fields[i].Name); + + if (columnStorageTypes[i] == UTFColumnStorageType.Constant) + { + WriteValue(bw, dt.Fields[i].Value, columnDataTypes[i], stringTable); + if (columnDataTypes[i] == UTFColumnDataType.String) + { + columnNameOffset += ((string)dt.Fields[i].Value).Length + 1; + } + } + } + + for (int i = 0; i < dt.Records.Count; i++) + { + for (int j = 0; j < dt.Fields.Count; j++) + { + if (columnStorageTypes[j] == UTFColumnStorageType.PerRow) + { + WriteValue(bw, dt.Records[i].Fields[j].Value, stringTable); + } + } + } + + for (int i = 0; i < stringTable.Count; i++) + { + bw.WriteNullTerminatedString(stringTable[i]); + } + + bw.Align(8); + } + + public static short GetLengthForDataType(UTFColumnDataType columnDataType) + { + switch (columnDataType) + { + case UTFColumnDataType.String: + { + return 4; + } + case UTFColumnDataType.Data: + case UTFColumnDataType.Long: + case UTFColumnDataType.Long2: + { + return 8; + } + case UTFColumnDataType.Byte: + case UTFColumnDataType.Byte2: + { + return 1; + } + case UTFColumnDataType.Float: + case UTFColumnDataType.Int: + case UTFColumnDataType.Int2: + { + return 4; + } + case UTFColumnDataType.Short: + case UTFColumnDataType.Short2: + { + return 2; + } + } + throw new NotImplementedException(); + } + + private void WriteValue(Writer bw, object value, List stringTable) + { + if (value is string) + { + WriteValue(bw, value, UTFColumnDataType.String, stringTable); + } + else if (value is byte[]) + { + WriteValue(bw, value, UTFColumnDataType.Data, stringTable); + } + else if (value is long || value is ulong) + { + WriteValue(bw, value, UTFColumnDataType.Long, stringTable); + } + else if (value is int || value is uint) + { + WriteValue(bw, value, UTFColumnDataType.Int, stringTable); + } + else if (value is short || value is ushort) + { + WriteValue(bw, value, UTFColumnDataType.Short, stringTable); + } + else if (value is byte || value is byte) + { + WriteValue(bw, value, UTFColumnDataType.Byte, stringTable); + } + else if (value is float) + { + WriteValue(bw, value, UTFColumnDataType.Float, stringTable); + } + } + + private void WriteValue(Writer bw, object value, UTFColumnDataType columnDataType, List stringTable) + { + switch (columnDataType) + { + case UTFColumnDataType.String: + { + string str = (string)value; + if (stringTable.Contains(str)) + { + bw.WriteUInt32((uint)stringTable.GetItemOffset(stringTable.IndexOf(str), 1)); + } + else + { + stringTable.Add(str); + bw.WriteUInt32((uint)stringTable.GetItemOffset(stringTable.Count - 1, 1)); + } + break; + } + case UTFColumnDataType.Data: + { + uint varDataOffset = 0; + uint varDataSize = 0; + bw.WriteUInt32(varDataOffset); + bw.WriteUInt32(varDataSize); + break; + } + case UTFColumnDataType.Long: + case UTFColumnDataType.Long2: + { + if (value is ulong) + { + bw.WriteUInt64((ulong)value); + } + else + { + bw.WriteInt64((long)value); + } + break; + } + case UTFColumnDataType.Int: + case UTFColumnDataType.Int2: + { + if (value is uint) + { + bw.WriteUInt32((uint)value); + } + else + { + bw.WriteInt32((int)value); + } + break; + } + case UTFColumnDataType.Short: + case UTFColumnDataType.Short2: + { + if (value is ushort) + { + bw.WriteUInt16((ushort)value); + } + else + { + bw.WriteInt16((short)value); + } + break; + } + case UTFColumnDataType.Float: + { + bw.WriteSingle((float)value); + break; + } + case UTFColumnDataType.Byte: + case UTFColumnDataType.Byte2: + { + if (value is byte) + { + bw.WriteByte((byte)value); + } + else + { + bw.WriteSByte((sbyte)value); + } + break; + } + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSDataFormat.cs new file mode 100644 index 0000000..e95a3fa --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSDataFormat.cs @@ -0,0 +1,293 @@ +// +// AFSDataFormat.cs - COMPLETED - implementation of CRI Middleware AFS archive +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2024 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS; + +using MBS.Core; + +using MBS.Editor.Core; +using MBS.Editor.Core.Hosting; +using MBS.Editor.Core.ObjectModels.FileSystem; + +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +/// +/// A for loading and saving archives in CRI Middleware AFS/AWB/ACB format. +/// +public class AFSDataFormat : DataFormat +{ + /* + private static DataFormatReference _dfr; + /// + /// Creates a containing metadata about the . + /// + /// The which contains metadata about the . + protected override DataFormatReference MakeReferenceInternal() + { + if (_dfr == null) + { + _dfr = base.MakeReferenceInternal(); + _dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All); + } + return _dfr; + } + */ + + /// + /// Gets or sets the version of AFS archive to read or write. Defaults to ('AFS\0'). + /// + /// The version of AFS archive to read or write. + public AFSFormatVersion FormatVersion { get; set; } = AFSFormatVersion.AFS0; + + /// + /// When true, and the underlying is a , + /// if the name of the file ends in ".awb", will automatically set to + /// . + /// + /// + public bool AutoDetectFormatVersion { get; set; } = true; + + /// + /// Loads the data from the input . + /// + /// A into which to load archive content. + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel); + if (fsom == null) + throw new ObjectModelNotSupportedException(); + + string? filename = null; + if (stream is FileStream) + { + filename = ((FileStream)stream).Name; + } + + Reader reader = new Reader(stream); + string afs = reader.ReadFixedLengthString(4); + + switch (afs) + { + case "AFS\0": + { + FormatVersion = AFSFormatVersion.AFS0; + + uint fileCount = reader.ReadUInt32(); + AFSFileInfo[] fileinfos = new AFSFileInfo[fileCount]; + + for (int i = 0; i < fileCount; i++) + { + fileinfos[i].offset = reader.ReadUInt32(); + fileinfos[i].length = reader.ReadUInt32(); + } + + uint tocOffset = reader.ReadUInt32(); + uint tocLength = reader.ReadUInt32(); + + if (tocOffset == 0) + { + (Application.Instance as IHostApplication)?.HostServices.Messages.Add(HostApplicationMessageSeverity.Warning, "table of contents not found", filename); + for (int j = 0; j < fileCount; j++) + { + fileinfos[j].name = String.Format("file_{0}", j); + FileSystemFile f = fsom.Items.AddFile(fileinfos[j].name); + f.Source = new EmbeddedFileSource(stream, fileinfos[j].offset, fileinfos[j].length); + } + } + else + { + reader.BaseStream.Seek(tocOffset, SeekOrigin.Begin); + for (int j = 0; j < fileCount; j++) + { + fileinfos[j].name = reader.ReadFixedLengthString(32).TrimNull(); + + ushort year = reader.ReadUInt16(); + ushort month = reader.ReadUInt16(); + ushort day = reader.ReadUInt16(); + ushort hour = reader.ReadUInt16(); + ushort minute = reader.ReadUInt16(); + ushort second = reader.ReadUInt16(); + fileinfos[j].datetime = new DateTime(year, month, day, hour, minute, second); + fileinfos[j].length2 = reader.ReadUInt32(); + + if (fileinfos[j].length2 != fileinfos[j].length) + { + (Application.Instance as IHostApplication)?.HostServices.Messages.Add(HostApplicationMessageSeverity.Warning, String.Format("length != length2 for file '{0}'", fileinfos[j].name), filename); + } + + FileSystemFile f = fsom.Items.AddFile(fileinfos[j].name); + f.Source = new EmbeddedFileSource(stream, fileinfos[j].offset, fileinfos[j].length); + f.ModificationTimestamp = fileinfos[j].datetime; + } + } + break; + } + case "AFS2": + { + FormatVersion = AFSFormatVersion.AFS2; + uint unknown1 = reader.ReadUInt32(); + + uint fileCount = reader.ReadUInt32(); + AFSFileInfo[] fileinfos = new AFSFileInfo[fileCount]; + + uint unknown2 = reader.ReadUInt32(); + for (uint i = 0; i < fileCount; i++) + { + ushort index = reader.ReadUInt16(); + } + for (uint i = 0; i < fileCount; i++) + { + fileinfos[i].offset = reader.ReadUInt32(); + fileinfos[i].offset = fileinfos[i].offset.Align(0x10); // does not affect 6 and 1 in v_etc_streamfiles.awb; idk why + if (i > 0) + { + fileinfos[i - 1].length = fileinfos[i].offset - fileinfos[i - 1].offset; + } + } + + uint totalArchiveSize = reader.ReadUInt32(); + fileinfos[fileinfos.Length - 1].length = totalArchiveSize - fileinfos[fileinfos.Length - 1].offset; + + ushort unknown4 = reader.ReadUInt16(); + + for (uint i = 0; i < fileinfos.Length; i++) + { + FileSystemFile f = fsom.Items.AddFile(i.ToString().PadLeft(8, '0')); + f.Source = new EmbeddedFileSource(stream, fileinfos[i].offset, fileinfos[i].length); + } + break; + } + default: + { + throw new InvalidDataFormatException("file does not begin with \"AFS\\0\""); + } + } + } + + /// + /// Writes the data to the output . + /// + /// A containing the archive content to write. + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + FileSystemObjectModel? fsom = objectModel as FileSystemObjectModel; + if (fsom == null) + throw new ObjectModelNotSupportedException(); + + if (stream is FileStream fs && AutoDetectFormatVersion) + { + if (fs.Name.ToLower().EndsWith(".awb")) + { + FormatVersion = AFSFormatVersion.AFS2; + } + } + + Writer writer = new Writer(stream); + FileSystemFile[] files = fsom.Items.GetFiles(); + + if (FormatVersion == AFSFormatVersion.AFS0) + { + writer.WriteFixedLengthString("AFS\0"); + + uint filecount = (uint)files.LongLength; + writer.WriteUInt32(filecount); + + uint offset = 8; + offset += (8 * filecount); // offset + size + offset += 8; // tocoffset + unknown1 + + uint[] offsets = new uint[(filecount * 2) + 1]; + offsets[0] = filecount; + + for (int i = 0; i < filecount; i++) + { + offset = offset.Align(2048); // align to 2048 byte boundary + + offsets[(i * 2) + 1] = offset; + offsets[(i * 2) + 2] = (uint)files[i].Size; + + writer.WriteUInt32(offset); + writer.WriteUInt32((uint)files[i].Size); + + offset += (uint)files[i].Size; + } + + offset = offset.Align(2048); + uint tocOffset = offset; + uint tocLength = (uint)(48 * files.Length); + writer.WriteUInt32(tocOffset); + writer.WriteUInt32(tocLength); + + // now we should be at file data + for (int i = 0; i < filecount; i++) + { + writer.Align(2048); + writer.WriteBytes(files[i].Source?.GetData()); + } + + // now we should be at the TOC + writer.Align(2048); + for (int j = 0; j < filecount; j++) + { + writer.WriteFixedLengthString(files[j].Name, 32); + + DateTime ts = files[j].ModificationTimestamp.GetValueOrDefault(DateTime.Now); + writer.WriteUInt16((ushort)ts.Year); + writer.WriteUInt16((ushort)ts.Month); + writer.WriteUInt16((ushort)ts.Day); + writer.WriteUInt16((ushort)ts.Hour); + writer.WriteUInt16((ushort)ts.Minute); + writer.WriteUInt16((ushort)ts.Second); + writer.WriteUInt32((uint)offsets[j]); + } + + writer.Align(2048); + } + else if (FormatVersion == AFSFormatVersion.AFS2) + { + writer.WriteFixedLengthString("AFS2"); + + writer.WriteUInt32(0); //unknown1 + writer.WriteUInt32((uint)files.Length); + writer.WriteUInt32(32); // unknown2 + for (uint i = 0; i < files.Length; i++) + { + writer.WriteUInt16((ushort)i); + } + + uint offset = (uint)(20 + (files.Length * 6)); + for (uint i = 0; i < files.Length; i++) + { + writer.WriteUInt32(offset); + offset += (uint) files[i].Size; + } + + writer.WriteUInt32(offset); // total archive size + + for (uint i = 0; i < files.Length; i++) + { + writer.Align(16); + writer.WriteBytes(files[i].Source?.GetData()); + } + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFileInfo.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFileInfo.cs new file mode 100644 index 0000000..d61bdfe --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFileInfo.cs @@ -0,0 +1,41 @@ +// +// AFSFileInfo.cs - internal structure representing metadata for files in an AFS archive +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS; + +using System; + +/// +/// Internal structure representing metadata for files in an AFS archive. +/// +internal struct AFSFileInfo +{ + public string name; + public uint offset; + public DateTime datetime; + public uint length; + public uint length2; + + public override string ToString() + { + return String.Format("{0} : {1} [{2}]", name, offset, length); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFormatVersion.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFormatVersion.cs new file mode 100644 index 0000000..4b0a8c1 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/AFS/AFSFormatVersion.cs @@ -0,0 +1,37 @@ +// +// AFSFormatVersion.cs - the version of AFS archive being handled by an AFSDataFormat instance +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS; + +/// +/// The version of AFS archive being handled by an instance. +/// +public enum AFSFormatVersion +{ + /// + /// Older version of AFS, which stores file data and TOC information in the same AFS file. + /// + AFS0, + /// + /// Newer version of AFS, which stores file data in an AWB file and writes the TOC to a separate ACB file. + /// + AFS2 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKCompressionModule.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKCompressionModule.cs new file mode 100644 index 0000000..59b3c30 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKCompressionModule.cs @@ -0,0 +1,193 @@ +using MBS.Editor.Core.Compression; +using MBS.Editor.Core.IO; + +namespace MBS.Editor.Plugins.CRI; + +public class CPKCompressionModule : CompressionModule +{ + private ushort get_next_bits(byte[] input, ref int offset_p, ref byte bit_pool_p, ref int bits_left_p, int bit_count) + { + ushort out_bits = 0; + int num_bits_produced = 0; + int bits_this_round; + + while (num_bits_produced < bit_count) + { + if (bits_left_p == 0) + { + bit_pool_p = input[offset_p]; + bits_left_p = 8; + offset_p--; + } + + if (bits_left_p > (bit_count - num_bits_produced)) + bits_this_round = bit_count - num_bits_produced; + else + bits_this_round = bits_left_p; + + out_bits <<= bits_this_round; + + out_bits |= (ushort)((ushort)(bit_pool_p >> (bits_left_p - bits_this_round)) & ((1 << bits_this_round) - 1)); + + bits_left_p -= bits_this_round; + num_bits_produced += bits_this_round; + } + + return out_bits; + } + + protected override void CompressInternal(Stream inputStream, Stream outputStream) + { + } + protected override void DecompressInternal(Stream inputStream, Stream outputStream) + { + Reader r = new Reader(inputStream); + byte[] input = r.ReadToEnd(); + byte[] output = DecompressCRILAYLA(input); + outputStream.Write(output, 0, output.Length); + } + + public byte[] DecompressCRILAYLA(byte[] input) + { + byte[] result; + + MemoryStream ms = new MemoryStream(input); + Reader br = new Reader(ms); + br.Endianness = Endianness.LittleEndian; + + br.BaseStream.Seek(8, SeekOrigin.Begin); // Skip CRILAYLA + int uncompressed_size = br.ReadInt32(); + int uncompressed_header_offset = br.ReadInt32(); + + result = new byte[uncompressed_size + 0x100]; + + // do some error checks here......... + + // copy uncompressed 0x100 header to start of file + Console.WriteLine("copy from input: {0}, 0, 0x100", uncompressed_header_offset + 0x10); + Array.Copy(input, uncompressed_header_offset + 0x10, result, 0, 0x100); + + int input_end = input.Length - 0x100 - 1; + int input_offset = input_end; + int output_end = 0x100 + uncompressed_size - 1; + byte bit_pool = 0; + int bits_left = 0, bytes_output = 0; + int[] vle_lens = new int[4] { 2, 3, 5, 8 }; + + while (bytes_output < uncompressed_size) + { + if (get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 1) > 0) + { + int backreference_offset = output_end - bytes_output + get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 13) + 3; + int backreference_length = 3; + int vle_level; + + for (vle_level = 0; vle_level < vle_lens.Length; vle_level++) + { + int this_level = get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, vle_lens[vle_level]); + backreference_length += this_level; + if (this_level != ((1 << vle_lens[vle_level]) - 1)) break; + } + + if (vle_level == vle_lens.Length) + { + int this_level; + do + { + this_level = get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 8); + backreference_length += this_level; + } while (this_level == 255); + } + + for (int i = 0; i < backreference_length; i++) + { + result[output_end - bytes_output] = result[backreference_offset--]; + bytes_output++; + } + } + else + { + // verbatim byte + result[output_end - bytes_output] = (byte)get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 8); + bytes_output++; + } + } + + br.Close(); + ms.Close(); + + return result; + } + + public byte[] DecompressLegacyCRI(byte[] input, int USize) + { + byte[] result;// = new byte[USize]; + + MemoryStream ms = new MemoryStream(input); + Reader br = new Reader(ms); + br.Endianness = Endianness.BigEndian; + + br.BaseStream.Seek(8, SeekOrigin.Begin); // Skip CRILAYLA + int uncompressed_size = br.ReadInt32(); + int uncompressed_header_offset = br.ReadInt32(); + + result = new byte[uncompressed_size + 0x100]; + + // do some error checks here......... + + // copy uncompressed 0x100 header to start of file + Array.Copy(input, uncompressed_header_offset + 0x10, result, 0, 0x100); + + int input_end = input.Length - 0x100 - 1; + int input_offset = input_end; + int output_end = 0x100 + uncompressed_size - 1; + byte bit_pool = 0; + int bits_left = 0, bytes_output = 0; + int[] vle_lens = new int[4] { 2, 3, 5, 8 }; + + while (bytes_output < uncompressed_size) + { + if (get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 1) > 0) + { + int backreference_offset = output_end - bytes_output + get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 13) + 3; + int backreference_length = 3; + int vle_level; + + for (vle_level = 0; vle_level < vle_lens.Length; vle_level++) + { + int this_level = get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, vle_lens[vle_level]); + backreference_length += this_level; + if (this_level != ((1 << vle_lens[vle_level]) - 1)) break; + } + + if (vle_level == vle_lens.Length) + { + int this_level; + do + { + this_level = get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 8); + backreference_length += this_level; + } while (this_level == 255); + } + + for (int i = 0; i < backreference_length; i++) + { + result[output_end - bytes_output] = result[backreference_offset--]; + bytes_output++; + } + } + else + { + // verbatim byte + result[output_end - bytes_output] = (byte)get_next_bits(input, ref input_offset, ref bit_pool, ref bits_left, 8); + bytes_output++; + } + } + + br.Close(); + ms.Close(); + + return result; + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKDataFormat.cs new file mode 100644 index 0000000..98d68ca --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKDataFormat.cs @@ -0,0 +1,897 @@ +// +// CPKDataFormat.cs - implementation of CRI Middleware CPK archive +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK; + +using System; +using System.Collections.Generic; +using MBS.Core; +using MBS.Core.Settings; + +using MBS.Editor.Core; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Database; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.CRI.DataFormats.Database.UTF; + +/// +/// A for loading and saving archives in CRI Middleware CPK format. +/// +public class CPKDataFormat : DataFormat +{ + private static DataFormatMetadata _dfr; + public static DataFormatMetadata Metadata + { + get + { + if (_dfr == null) + { + _dfr = new DataFormatMetadata(); + _dfr.ExportSettings.SettingsGroups[0].Settings.Add(new ChoiceSetting(nameof(Mode), "File access _method", CPKFileMode.IDFilename, new ChoiceSetting.ChoiceSettingValue[] + { + new ChoiceSetting.ChoiceSettingValue("IDOnly", "ID only", CPKFileMode.IDOnly), + new ChoiceSetting.ChoiceSettingValue("FilenameOnly", "Filename only", CPKFileMode.FilenameOnly), + new ChoiceSetting.ChoiceSettingValue("IDFilename", "ID + Filename", CPKFileMode.IDFilename), + new ChoiceSetting.ChoiceSettingValue("FilenameGroup", "Filename + Group (Attribute)", CPKFileMode.FilenameGroup), + new ChoiceSetting.ChoiceSettingValue("IDGroup", "ID + Group (Attribute)", CPKFileMode.IDGroup), + new ChoiceSetting.ChoiceSettingValue("FilenameIDGroup", "Filename + ID + Group (Attribute)", CPKFileMode.FilenameIDGroup) + }) + { Description = "Choose the method by which files should be accessed in the resulting archive." }); + + _dfr.ExportSettings.SettingsGroups[0].Settings.Add(new TextSetting(nameof(VersionString), "_Version string", "CPKMC2.14.00, DLL2.74.00") + { Description = "Override the version string written by the creator program." }); + + _dfr.ExportSettings.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(SectorAlignment), "Sector _alignment", 2048, 0, 2048) + { Description = "Choose the alignment for each file in the resulting archive (between 1 and 2048 in powers of 2)." }); + _dfr.ExportSettings.SettingsGroups[0].Settings.Add(new BooleanSetting(nameof(ScrambleDirectoryInformation), "_Scramble directory information") + { Description = "Encrypt the directory information in the resulting archive (contents are NOT encrypted)." }); + _dfr.ExportSettings.SettingsGroups[0].Settings.Add(new BooleanSetting(nameof(ForceCompression), "_Force compression") + { Description = "Attempt to compress all files in the archive regardless of individual file compression setting." }); + _dfr.ExportSettings.SettingsGroups[0].Settings.Add(new BooleanSetting(nameof(ForceCompression), "Include _CRC information") + { Description = "Generate CRC checksum information (about 4 additional bytes per file) for each file." }); + + _dfr.ExportSettings.SettingsGroups.Add(new SettingsGroup("CPK File Setting", new Setting[] + { + new BooleanSetting("TopTocInformation", "Write TOC at beginning of file", false) + { Description = "Media with slow seek time may be able to load the information fast, but may take up more space." }, + new BooleanSetting("RandomDataPadding", "_Random data padding", false) + { Description = "Pad file contents with random data instead of zero byte." }, + new BooleanSetting("RemoveLocalInfo", "Do not write _local filename information", false) + { Description = "When enabled, new files cannot be added." }, + new BooleanSetting("RemoveTimestampInfo", "Do not write _timestamp information", false) + { Description = "When enabled, data cannot be added." } + })); + } + return _dfr; + } + } + + public CPKFileMode Mode { get; set; } = CPKFileMode.FilenameOnly; + + /// + /// Gets or sets the version string which contains information about the library which created the archive. The default value is "CPKMC2.14.00, DLL2.74.00" which is the version string that official CRI Middleware CPK tools use. + /// + /// The version string. + public string VersionString { get; set; } = "CPKMC2.14.00, DLL2.74.00"; // "CPKFBSTD1.49.34, DLL3.24.00" + + public bool ScrambleDirectoryInformation { get; set; } = false; + public bool ForceCompression { get; set; } = false; + + /// + /// Gets or sets the sector alignment, in bytes, of the CPK archive. The default value is 2048. + /// + /// The file alignment. + public int SectorAlignment { get; set; } = 2048; + + // these are mainly for the benefit of the CRI Extensions for FileSystemEditor + + private byte[] _HeaderData = null; + /// + /// Returns the raw data from the initial "CPK " chunk of this file, or if this chunk does not exist. + /// + /// The raw data from the "CPK " chunk. + public byte[] HeaderData { get { return _HeaderData; } } + /// + /// Returns the UTF table from the initial "CPK " chunk of this file, or if this chunk does not exist. + /// + /// The header table. + public DatabaseTable HeaderTable { get; private set; } = null; + + private byte[] _TocData = null; + /// + /// Returns the raw data from the "TOC " chunk of this file, or NULL if this chunk does not exist. + /// + /// The raw data from the "TOC " chunk. + public byte[] TocData { get { return _TocData; } } + + private byte[] _ITocData = null; + /// + /// Returns the raw data from the "ITOC" chunk of this file, or NULL if this chunk does not exist. + /// + /// The raw data from the "ITOC" chunk. + public byte[] ITocData { get { return _ITocData; } } + + private byte[] _GTocData = null; + /// + /// Returns the raw data from the "GTOC" chunk of this file, or NULL if this chunk does not exist. + /// + /// The raw data from the "GTOC" chunk. + public byte[] GTocData { get { return _GTocData; } } + + private byte[] _ETocData = null; + /// + /// Returns the raw data from the final "ETOC" chunk of this file, or NULL if this chunk does not exist. + /// + /// The raw data from the "ETOC" chunk. + public byte[] ETocData { get { return _ETocData; } } + + /// + /// Loads the data from the input . + /// + /// A into which to load archive content. + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + FileSystemObjectModel? fsom = objectModel as FileSystemObjectModel; + if (fsom == null) + throw new ObjectModelNotSupportedException(); + + fsom.CustomDetails.Add("CRI.CPK.FileID", "ID"); + fsom.CustomDetails.Add("CRI.CPK.CRC", "CRC"); + + Reader br = new Reader(stream); + + // Rebuilt based on cpk_unpack + // Rebuilt AGAIN based on github.com/esperknight/CriPakTools + DatabaseObjectModel utf_om = ReadUTF("CPK ", br, out _HeaderData); + int utf_checksum = br.ReadInt32(); // maybe checksum? + + DatabaseTable dtUTF = utf_om.Tables[0]; + HeaderTable = dtUTF; + if (objectModel is DatabaseObjectModel) + { + (objectModel as DatabaseObjectModel).Tables.Add(dtUTF); + } + + DatabaseTable dtUTFTOC = null, dtUTFITOC = null, dtUTFITOC_L = null, dtUTFITOC_H = null, dtUTFETOC = null; + + if (dtUTF.Records[0].Fields["CpkMode"]?.Value != null) + { + Mode = (CPKFileMode)(uint)(dtUTF.Records[0].Fields["CpkMode"].Value); + } + if (dtUTF.Records[0].Fields["Tvers"].Value != null) + { + VersionString = dtUTF.Records[0].Fields["Tvers"].Value.ToString(); + } + if (dtUTF.Records[0].Fields["CrcTable"]?.Value != null) + { + + } + + // UTF table parsing works now, so no need to hardcode toc offset - WOOHOO!!! + if (dtUTF.Records[0].Fields["TocOffset"].Value != null) + { + ulong tocOffset = (ulong)dtUTF.Records[0].Fields["TocOffset"].Value; + br.BaseStream.Seek((long)tocOffset, SeekOrigin.Begin); + + utf_om = ReadUTF("TOC ", br, out _TocData); + + dtUTFTOC = utf_om.Tables[0]; + } + if (dtUTF.Records[0].Fields["ItocOffset"].Value != null) + { + // Index TOC + ulong itocOffset = (ulong)dtUTF.Records[0].Fields["ItocOffset"].Value; + br.BaseStream.Seek((long)itocOffset, SeekOrigin.Begin); + + utf_om = ReadUTF("ITOC", br, out _ITocData); + + dtUTFITOC = utf_om.Tables[0]; + + byte[] dtUTFITOC_L_data = (dtUTFITOC.Records[0].Fields["DataL"]?.Value as byte[]); + byte[] dtUTFITOC_H_data = (dtUTFITOC.Records[0].Fields["DataH"]?.Value as byte[]); + + if (dtUTFITOC_L_data != null) + { + DatabaseObjectModel _lutfom = new DatabaseObjectModel(); + UTFDataFormat _lutfdf = new UTFDataFormat(); + Document.Load(_lutfom, _lutfdf, new MemoryStream(dtUTFITOC_L_data)); + dtUTFITOC_L = _lutfom.Tables[0]; + } + if (dtUTFITOC_H_data != null) + { + DatabaseObjectModel _lutfom = new DatabaseObjectModel(); + UTFDataFormat _lutfdf = new UTFDataFormat(); + Document.Load(_lutfom, _lutfdf, new MemoryStream(dtUTFITOC_H_data)); + dtUTFITOC_H = _lutfom.Tables[0]; + } + } + if (dtUTF.Records[0].Fields["GtocOffset"].Value != null) + { + // Groups TOC + ulong gtocOffset = (ulong)dtUTF.Records[0].Fields["GtocOffset"].Value; + br.BaseStream.Seek((long)gtocOffset, SeekOrigin.Begin); + + utf_om = ReadUTF("GTOC", br, out _GTocData); + + DatabaseTable dtUTFGTOC = utf_om.Tables[0]; + + DatabaseTable dtCpkGtocAttr = utf_om.Tables["CpkGtocAttr"]; + List listAttribs = new List(); + if (dtCpkGtocAttr != null) + { + for (int i = 0; i < dtCpkGtocAttr.Records.Count; i++) + { + string attribName = dtCpkGtocAttr.Records[i].Fields["Aname"]?.Value?.ToString(); + listAttribs.Add(attribName); + } + } + } + + if (dtUTFTOC != null) + { + if (objectModel is DatabaseObjectModel) + { + (objectModel as DatabaseObjectModel).Tables.Add(dtUTFTOC); + } + else if (objectModel is FileSystemObjectModel) + { + for (int i = 0; i < dtUTFTOC.Records.Count; i++) + { + string dirName = (string)dtUTFTOC.Records[i].Fields["DirName"].Value; + string fileTitle = (string)dtUTFTOC.Records[i].Fields["FileName"].Value; + string fileName = fileTitle; + if (!String.IsNullOrEmpty(dirName)) + { + fileName = dirName + '/' + fileTitle; + } + + uint compressedLength = (uint)dtUTFTOC.Records[i].Fields["FileSize"].Value; + uint decompressedLength = (uint)dtUTFTOC.Records[i].Fields["ExtractSize"].Value; + ulong offset = (ulong)dtUTFTOC.Records[i].Fields["FileOffset"].Value; + + ulong lTocOffset = (ulong)dtUTF.Records[0].Fields["TocOffset"].Value; + ulong lContentOffset = (ulong)dtUTF.Records[0].Fields["ContentOffset"].Value; + + // HACK: according to kamikat cpk tools, the real content offset is whichever is smaller TocOffset vs ContentOffset + // https://github.com/kamikat/cpktools/blob/master/cpkunpack.py + // this feels EXTREMELY hacky, but it works... for now + ulong lRealContentOffset = Math.Min(lTocOffset, lContentOffset); + + offset += lContentOffset; + + FileSystemFile f = fsom.Items.AddFile(fileName); + + if (dtUTFTOC.Records[i].Fields["ID"] != null) + { + uint id = (uint)dtUTFTOC.Records[i].Fields["ID"].Value; + f.CustomDetails["CRI.CPK.FileID"] = id; + } + if (dtUTFTOC.Records[i].Fields["CRC"] != null) + { + f.CustomDetails["CRI.CPK.CRC"] = ((uint)dtUTFTOC.Records[i].Fields["CRC"].Value).ToString("x"); + } + f.Source = new CompressedEmbeddedFileSource(stream, (long)lContentOffset, compressedLength, decompressedLength); + if (compressedLength != decompressedLength) + { + ((CompressedEmbeddedFileSource)f.Source).CompressionModule = new CPKCompressionModule(); + } + } + } + } + else if (dtUTFITOC_L != null || dtUTFITOC_H != null) + { + ulong lContentOffset = (ulong)dtUTF.Records[0].Fields["ContentOffset"].Value; + ulong offset = lContentOffset; + + List list = new List(); + if (dtUTFITOC_L != null) + { + for (int i = 0; i < dtUTFITOC_L.Records.Count; i++) + { + ushort decompressedLength = (ushort)dtUTFITOC_L.Records[i].Fields["FileSize"].Value; + ushort compressedLength = (ushort)dtUTFITOC_L.Records[i].Fields["ExtractSize"].Value; + + FileSystemFile f = new FileSystemFile(); + + ushort id = (ushort)dtUTFITOC_L.Records[i].Fields["ID"].Value; + f.Name = id.ToString(); + f.CustomDetails["CRI.CPK.FileID"] = id; + + f.Source = new CompressedEmbeddedFileSource(stream, (long)offset, compressedLength, decompressedLength); + list.Add(f); + } + } + if (dtUTFITOC_H != null) + { + for (int i = 0; i < dtUTFITOC_H.Records.Count; i++) + { + uint decompressedLength = (uint)dtUTFITOC_H.Records[i].Fields["FileSize"].Value; + uint compressedLength = (uint)dtUTFITOC_H.Records[i].Fields["ExtractSize"].Value; + + FileSystemFile f = new FileSystemFile(); + + ushort id = (ushort)dtUTFITOC_H.Records[i].Fields["ID"].Value; + f.Name = id.ToString(); + f.CustomDetails["CRI.CPK.FileID"] = id; + + f.Source = new CompressedEmbeddedFileSource(stream, 0, compressedLength, decompressedLength); + list.Add(f); + } + } + + // sort them by ID - this is important because the data is stored contiguously + list.Sort(new Comparison((x, y) => ((ushort)x.CustomDetails["CRI.CPK.FileID"]).CompareTo((ushort)y.CustomDetails["CRI.CPK.FileID"]))); + + for (int i = 0; i < list.Count; i++) + { + CompressedEmbeddedFileSource? source = list[i].Source as CompressedEmbeddedFileSource; + if (source != null) + { + source.Offset = (long)offset; + } + + offset += (uint)source.CompressedLength; + offset = offset.Align((ulong)SectorAlignment); + + fsom.Items.Add(list[i]); + } + } + + if (dtUTF.Records[0].Fields["EtocOffset"].Value != null) + { + ulong etocOffset = (ulong)dtUTF.Records[0].Fields["EtocOffset"].Value; + br.BaseStream.Seek((long)etocOffset, SeekOrigin.Begin); + + utf_om = ReadUTF("ETOC", br, out _ETocData); + dtUTFETOC = utf_om.Tables[0]; + } + + if (dtUTFETOC != null) + { + if (objectModel is DatabaseObjectModel) + { + (objectModel as DatabaseObjectModel).Tables.Add(dtUTFETOC); + } + else if (objectModel is FileSystemObjectModel) + { + for (int i = 0; i < dtUTFETOC.Records.Count; i++) + { + ulong updateDateTime = (ulong)dtUTFETOC.Records[i].Fields["UpdateDateTime"].Value; + string localDir = (string)dtUTFETOC.Records[i].Fields["LocalDir"].Value; + + if (i >= fsom.Items.Count) + continue; + + FileSystemFile? f = fsom.Items[i] as FileSystemFile; + if (f != null) + { + byte[] updateDateTimeBytes = BitConverter.GetBytes(updateDateTime); + // remember, the CPK is big-endian, but the UTF is little-endian + ushort updateDateTimeYear = BitConverter.ToUInt16(new byte[] { updateDateTimeBytes[6], updateDateTimeBytes[7] }, 0); + byte updateDateTimeMonth = updateDateTimeBytes[5]; + byte updateDateTimeDay = updateDateTimeBytes[4]; + byte updateDateTimeHour = updateDateTimeBytes[3]; + byte updateDateTimeMinute = updateDateTimeBytes[2]; + byte updateDateTimeSecond = updateDateTimeBytes[1]; + byte updateDateTimeMs = updateDateTimeBytes[0]; + + f.ModificationTimestamp = new DateTime(updateDateTimeYear, updateDateTimeMonth, updateDateTimeDay, updateDateTimeHour, updateDateTimeMinute, updateDateTimeSecond, updateDateTimeMs); + } + } + } + } + } + + private DatabaseObjectModel ReadUTF(string expectedSignature, Reader br, out byte[] data) + { + string tocSignature = br.ReadFixedLengthString(4); + if (tocSignature != expectedSignature) + throw new InvalidDataFormatException(); + + int unknown1 = br.ReadInt32(); // always 255? + + // UTF table for TOC + long utf_size = br.ReadInt64(); // size of UTF including "@UTF" + + byte[] utf_data = br.ReadBytes(utf_size); + + MemoryStream ma = new MemoryStream(utf_data); + Reader r = new Reader(ma); + string utf_signature = r.ReadFixedLengthString(4); + if (utf_signature != "@UTF") + { + ScrambleDirectoryInformation = true; + // encrypted? + utf_data = DecryptUTF(utf_data); + ma = new MemoryStream(utf_data); + r = new Reader(ma); + } + else + { + ma.Seek(-4, SeekOrigin.Current); + } + + UTFDataFormat utf_df = new UTFDataFormat(); + DatabaseObjectModel utf_om = new DatabaseObjectModel(); + Document.Load(utf_om, utf_df, ma); + + data = utf_data; + return utf_om; + } + /* + void f_DataRequest(object sender, DataRequestEventArgs e) + { + File f = (sender as File); + uint decompressedLength = (uint)f.Properties["DecompressedLength"]; + uint compressedLength = (uint)f.Properties["CompressedLength"]; + ulong offset = (ulong)f.Properties["Offset"]; + Reader br = (Reader)f.Properties["Reader"]; + + br.Accessor.Position = (long)offset; + + byte[] decompressedData = null; + if (compressedLength == 0) + { + decompressedData = br.ReadBytes(decompressedLength); + } + else + { + byte[] compressedData = br.ReadBytes(compressedLength); + decompressedData = //compress() // compressedData; + } + + e.Data = decompressedData; + } + */ + + private DatabaseObjectModel BuildHeaderUTF(FileSystemObjectModel fsom, int tocsize, ulong contentOffset, ulong contentSize, ulong etocOffset, ulong etocLength, ulong? itocOffset, ulong? itocLength) + { + FileSystemFile[] files = fsom.Items.GetFiles(); + + DatabaseTable dt = new DatabaseTable(); + dt.Name = "CpkHeader"; + dt.Fields.Add("UpdateDateTime", null, typeof(Int64)); + dt.Fields.Add("FileSize", null, typeof(Int64)); + dt.Fields.Add("ContentOffset", null, typeof(Int64)); + dt.Fields.Add("ContentSize", null, typeof(Int64)); + dt.Fields.Add("TocOffset", null, typeof(Int64)); + dt.Fields.Add("TocSize", null, typeof(Int64)); + dt.Fields.Add("TocCrc", null, typeof(uint)); + + // added in newer version CpkFileBuilder + // dt.Fields.Add("HtocOffset", null, typeof(Int64)); + // dt.Fields.Add("HtocSize", null, typeof(Int64)); + + dt.Fields.Add("EtocOffset", null, typeof(Int64)); + dt.Fields.Add("EtocSize", null, typeof(Int64)); + dt.Fields.Add("ItocOffset", null, typeof(Int64)); + dt.Fields.Add("ItocSize", null, typeof(Int64)); + dt.Fields.Add("ItocCrc", null, typeof(uint)); + dt.Fields.Add("GtocOffset", null, typeof(Int64)); + dt.Fields.Add("GtocSize", null, typeof(Int64)); + dt.Fields.Add("GtocCrc", null, typeof(uint)); + + // added in newer version CpkFileBuilder + // dt.Fields.Add("HgtocOffset", null, typeof(Int64)); + // dt.Fields.Add("HgtocSize", null, typeof(Int64)); + + dt.Fields.Add("EnabledPackedSize", null, typeof(Int64)); + dt.Fields.Add("EnabledDataSize", null, typeof(Int64)); + dt.Fields.Add("TotalDataSize", null, typeof(Int64)); + dt.Fields.Add("Tocs", null, typeof(uint)); + dt.Fields.Add("Files", null, typeof(uint)); + dt.Fields.Add("Groups", null, typeof(uint)); + dt.Fields.Add("Attrs", null, typeof(uint)); + dt.Fields.Add("TotalFiles", null, typeof(uint)); + dt.Fields.Add("Directories", null, typeof(uint)); + dt.Fields.Add("Updates", null, typeof(uint)); + dt.Fields.Add("Version", null, typeof(Int16)); + dt.Fields.Add("Revision", null, typeof(Int16)); + dt.Fields.Add("Align", null, typeof(Int16)); + dt.Fields.Add("Sorted", null, typeof(Int16)); + + // added in newer version CpkFileBuilder + // dt.Fields.Add("EnableFileName", null, typeof(Int16)); + + dt.Fields.Add("EID", null, typeof(Int16)); + dt.Fields.Add("CpkMode", null, typeof(uint)); + dt.Fields.Add("Tvers", null, typeof(string)); + dt.Fields.Add("Comment", null, typeof(string)); + dt.Fields.Add("Codec", null, typeof(uint)); + dt.Fields.Add("DpkItoc", null, typeof(uint)); + + //added in newer version CpkFileBuilder + // dt.Fields.Add("EnableTocCrc", null, typeof(Int16)); + // dt.Fields.Add("EnableFileCrc", null, typeof(Int16)); + // dt.Fields.Add("CrcMode", null, typeof(uint)); + // dt.Fields.Add("CrcTable", null, typeof(byte[])); + + // cri, go home, you're drunk + ulong enabledPackedSize = 0; + for (uint i = 0; i < files.Length; i++) + { + enabledPackedSize += (ulong) files[i].Size; + } + enabledPackedSize *= 2; + + ulong enabledDataSize = enabledPackedSize; + + dt.Records.Add(new DatabaseRecord(new DatabaseField[] + { + new DatabaseField("UpdateDateTime", (ulong)1), + new DatabaseField("FileSize", null), + new DatabaseField("ContentOffset", contentOffset), // 18432 , should be 20480 + new DatabaseField("ContentSize", contentSize), // 8217472, should be 8564736 (347264 difference!) + new DatabaseField("TocOffset", (ulong)SectorAlignment), + new DatabaseField("TocSize", (ulong)tocsize), + new DatabaseField("TocCrc", null), + + // added in newer version CpkFileBuilder + // new DatabaseField("HtocOffset", 0), + // new DatabaseField("HtocSize", 0), + + new DatabaseField("EtocOffset", etocOffset), + new DatabaseField("EtocSize", etocLength), + new DatabaseField("ItocOffset", itocOffset), + new DatabaseField("ItocSize", itocLength), + new DatabaseField("ItocCrc", null), + new DatabaseField("GtocOffset", null), + new DatabaseField("GtocSize", null), + new DatabaseField("GtocCrc", null), + + // added in newer version CpkFileBuilder + // new DatabaseField("HgtocOffset", null), + // new DatabaseField("HgtocSize", null), + + new DatabaseField("EnabledPackedSize", enabledPackedSize), //16434944 in diva2script.cpk + new DatabaseField("EnabledDataSize", enabledDataSize), + new DatabaseField("TotalDataSize", null), + new DatabaseField("Tocs", null), + new DatabaseField("Files", (uint)files.Length), + new DatabaseField("Groups", (uint)0), + new DatabaseField("Attrs", (uint)0), + new DatabaseField("TotalFiles", null), + new DatabaseField("Directories", null), + new DatabaseField("Updates", null), + new DatabaseField("Version", (ushort)7), + new DatabaseField("Revision", (ushort)0), + new DatabaseField("Align", (ushort)SectorAlignment), + new DatabaseField("Sorted", (ushort)1), + + // added in newer version CpkFileBuilder + // new DatabaseField("EnableFileName", (ushort)0), + + new DatabaseField("EID", (ushort)1), + new DatabaseField("CpkMode", (uint)Mode), + new DatabaseField("Tvers", VersionString), + new DatabaseField("Comment", null), + new DatabaseField("Codec", (uint)0), + new DatabaseField("DpkItoc", (uint)0), + + // added in newer version CpkFileBuilder + // new DatabaseField("EnableTocCrc", (short)0), + // new DatabaseField("EnableFileCrc", (short)0), + // new DatabaseField("CrcMode", (uint)0), + // new DatabaseField("CrcTable", null) + })); + + DatabaseObjectModel db = new DatabaseObjectModel(); + db.Tables.Add(dt); + return db; + } + + private ulong GetUtfTableSize(DatabaseTable dt) + { + ulong size = (ulong)(32 + (dt.Fields.Count * 5)); + for (int i = 0; i < dt.Fields.Count; i++) + { + if (dt.Fields[i].Value == null) + { + // perrow + for (int j = 0; j < dt.Records.Count; j++) + { + size += (ulong)UTFDataFormat.GetLengthForDataType(UTFDataFormat.UTFDataTypeForSystemDataType(dt.Fields[i].DataType)); + } + } + } + return size; + } + + private DatabaseObjectModel BuildTocUTF(FileSystemFile[] files, ulong initialFileOffset, ref IDOFFSET[] sortedOffsets) + { + DatabaseTable dt = new DatabaseTable(); + dt.Name = "CpkTocInfo"; + dt.Fields.Add("DirName", String.Empty, typeof(string)); + dt.Fields.Add("FileName", null, typeof(string)); + dt.Fields.Add("FileSize", null, typeof(uint)); + dt.Fields.Add("ExtractSize", null, typeof(uint)); + dt.Fields.Add("FileOffset", null, typeof(ulong)); + dt.Fields.Add("ID", null, typeof(uint)); + dt.Fields.Add("UserString", "", typeof(string)); + + ulong offset = initialFileOffset; + offset -= (ulong) SectorAlignment; // idk? + + List offsets = new List(files.Length); + for (int i = 0; i < files.Length; i++) + { + offsets.Add(new IDOFFSET(i, (uint)files[i].CustomDetails.GetValueOrDefault("ID", 0U), 0, (ulong) files[i].Size)); + } + offsets.Sort((x, y) => x.ID.CompareTo(y.ID)); + + sortedOffsets = offsets.ToArray(); + + for (int i = 0; i < files.Length; i++) + { + offsets[i] = new IDOFFSET(offsets[i].INDEX, offsets[i].ID, offset, offsets[i].SIZE); + offset += offsets[i].SIZE; + offset = offset.Align((ulong)SectorAlignment); + } + offsets.Sort((x, y) => x.INDEX.CompareTo(y.INDEX)); + + for (int i = 0; i < files.Length; i++) + { + dt.Records.Add(new DatabaseRecord(new DatabaseField[] + { + new DatabaseField("DirName", null), + new DatabaseField("FileName", files[i].Name), + new DatabaseField("FileSize", (uint)files[i].Size), + new DatabaseField("ExtractSize", (uint)files[i].Size), + new DatabaseField("FileOffset", offsets[i].OFFSET), + new DatabaseField("ID", (uint)files[i].CustomDetails.GetValueOrDefault("ID", (uint)i)), + new DatabaseField("UserString", "") + })); + } + + DatabaseObjectModel db = new DatabaseObjectModel(); + db.Tables.Add(dt); + return db; + } + private DatabaseObjectModel BuildEtocUTF(FileSystemFile[] files) + { + DatabaseTable dt = new DatabaseTable(); + dt.Name = "CpkEtocInfo"; + dt.Fields.Add("UpdateDateTime", null, typeof(ulong)); + dt.Fields.Add("LocalDir", String.Empty, typeof(string)); + + for (int i = 0; i < files.Length; i++) + { + DateTime updateDateTime = files[i].ModificationTimestamp.GetValueOrDefault(DateTime.Now); + + // yeaaaaahhh + byte[] updateDateTimeBytes = new byte[8]; + updateDateTimeBytes[0] = (byte)updateDateTime.Millisecond; + updateDateTimeBytes[1] = (byte)updateDateTime.Second; + updateDateTimeBytes[2] = (byte)updateDateTime.Minute; + updateDateTimeBytes[3] = (byte)updateDateTime.Hour; + updateDateTimeBytes[4] = (byte)updateDateTime.Day; + updateDateTimeBytes[5] = (byte)updateDateTime.Month; + + byte[] updateDateTimeYear = BitConverter.GetBytes((ushort)updateDateTime.Year); + updateDateTimeBytes[6] = updateDateTimeYear[0]; + updateDateTimeBytes[7] = updateDateTimeYear[1]; + + dt.Records.Add(new DatabaseRecord(new DatabaseField[] + { + new DatabaseField("UpdateDateTime", BitConverter.ToUInt64(updateDateTimeBytes, 0)), + new DatabaseField("LocalDir", null) + })); + } + + dt.Records.Add(new DatabaseRecord(new DatabaseField[] + { + new DatabaseField("UpdateDateTime", (ulong)0), + new DatabaseField("LocalDir", null) + })); + + DatabaseObjectModel db = new DatabaseObjectModel(); + db.Tables.Add(dt); + return db; + } + private DatabaseObjectModel BuildItocUTF(IDOFFSET[] entries) + { + DatabaseTable dt = new DatabaseTable(); + dt.Name = "CpkExtendId"; + dt.Fields.Add("ID", null, typeof(int)); + dt.Fields.Add("TocIndex", null, typeof(int)); + + for (int i = 0; i < entries.Length; i++) + { + dt.Records.Add(new DatabaseRecord(new DatabaseField[] + { + new DatabaseField("ID", entries[i].ID), + new DatabaseField("TocIndex", entries[i].INDEX) + })); + } + + DatabaseObjectModel db = new DatabaseObjectModel(); + db.Tables.Add(dt); + return db; + } + + /// + /// Applies a simple cipher to decrypt an encrypted UTF sector. + /// + /// The decrypted data. + /// The data to decrypt. + private byte[] DecryptUTF(byte[] input) + { + byte[] result = new byte[input.Length]; + + int m = 0x0000655f, t = 0x00004115; + for (int i = 0; i < input.Length; i++) + { + byte d = input[i]; + d = (byte)(d ^ (byte)(m & 0xff)); + result[i] = d; + m *= t; + } + + return result; + } + + /// + /// Writes the data to the output . + /// + /// A containing the archive content to write. + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + FileSystemObjectModel? fsom = objectModel as FileSystemObjectModel; + if (fsom == null) + throw new ObjectModelNotSupportedException(); + + FileSystemFile[] files = fsom.Items.GetAllFiles(); + + Writer bw = new Writer(stream); + bw.WriteFixedLengthString("CPK "); + + bw.WriteInt32(255); // unknown1 + + UTFDataFormat dfUTF = new UTFDataFormat(); + + ulong contentOffset = 16; + ulong etocLength = 0; + ulong itocLength = 0; + ulong headerLength = 0; + ulong tocLength = 0; + + IDOFFSET[] sortedOffsets = null; + { + // TODO: replace all these calls to build methods with a simple calculation function e.g. UTFDataFormat.GetTableSize() ... + // there is no reason to go through the entire file list just to calculate how big a table should be - mind those variable-length strings though + DatabaseObjectModel _tmp_om = BuildHeaderUTF(fsom, 0, 0, 0, 0, 0, 0, 0); // 704 + MemoryStream _tmp_ma = new MemoryStream(); + Document.Save(_tmp_om, dfUTF, _tmp_ma); + contentOffset += (ulong)_tmp_ma.Length; + contentOffset = contentOffset.Align((ulong)SectorAlignment); + + headerLength = (ulong) _tmp_ma.Length; + + _tmp_om = BuildTocUTF(files, 0, ref sortedOffsets); // 117880 + _tmp_ma = new MemoryStream(); + Document.Save(_tmp_om, dfUTF, _tmp_ma); + + contentOffset += 16; + contentOffset += (ulong)_tmp_ma.Length; + contentOffset = contentOffset.Align((ulong)SectorAlignment); + tocLength = (ulong) _tmp_ma.Length; + + _tmp_om = BuildItocUTF(sortedOffsets); + _tmp_ma = new MemoryStream(); + Document.Save(_tmp_om, dfUTF, _tmp_ma); + + contentOffset = contentOffset.RoundToPower((ulong)2); // this is done before the ITOC, apparently. + + contentOffset += 16; + contentOffset += (ulong)_tmp_ma.Length; + contentOffset = contentOffset.Align((ulong)SectorAlignment); + itocLength = (ulong) _tmp_ma.Length; // 21728 + + _tmp_om = BuildEtocUTF(files); + _tmp_ma = new MemoryStream(); + Document.Save(_tmp_om, dfUTF, _tmp_ma); + // contentOffset += (ulong)_tmp_ma.Length; // lol wtf contentoffset isn't affected by ETOC... + etocLength = (ulong) _tmp_ma.Length; // 21752 + } + + DatabaseObjectModel utfTOC = BuildTocUTF(files, contentOffset, ref sortedOffsets); + + MemoryStream maUTFTOC = new MemoryStream(); + Document.Save(utfTOC, dfUTF, maUTFTOC); + + byte[] utfTOC_data = maUTFTOC.ToArray(); + + + ulong contentSize = 0; + for (uint i = 0; i < files.Length; i++) + { + contentSize += (ulong)files[i].Size; + contentSize = contentSize.Align((ulong)SectorAlignment); + } + + ulong itocOffset = 16 + headerLength; + itocOffset = itocOffset.Align((ulong)SectorAlignment); + itocOffset += (16 + tocLength); + itocOffset = itocOffset.Align((ulong)SectorAlignment); + + itocOffset = itocOffset.RoundToPower(2); + + ulong etocOffset = 0; + etocOffset = contentOffset + contentSize; + + DatabaseObjectModel utfHeader = BuildHeaderUTF(fsom, utfTOC_data.Length + 16 /*includes 16-byte 'TOC ' header from CPK*/, contentOffset, contentSize, etocOffset, etocLength + 16, itocOffset, itocLength + 16); + MemoryStream maUTFHeader = new MemoryStream(); + Document.Save(utfHeader, dfUTF, maUTFHeader); + + byte[] utfHeader_data = maUTFHeader.ToArray(); + bw.WriteInt64(utfHeader_data.Length); + bw.WriteBytes(utfHeader_data); + + int __unknown_checksum = -1677552896; // 9634460; + bw.WriteInt32(__unknown_checksum); + + bw.Align(SectorAlignment); + bw.BaseStream.Seek(-6, SeekOrigin.Current); + bw.WriteFixedLengthString("(c)CRI"); + + WriteChunk(bw, "TOC ", utfTOC_data); + bw.Align(SectorAlignment); + + // here comes the ITOC (indexes TOC) UTF table chunk. + DatabaseObjectModel utfITOC = BuildItocUTF(sortedOffsets); + MemoryStream maUTFITOC = new MemoryStream(); + Document.Save(utfITOC, dfUTF, maUTFITOC); + + byte[] utfITOC_data = maUTFITOC.ToArray(); + bw.BaseStream.Seek(bw.BaseStream.Position.RoundToPower(2), SeekOrigin.Begin); + + WriteChunk(bw, "ITOC", utfITOC_data); + bw.Align(SectorAlignment); + + // here comes the file data. each file is aligned to FileAlignment bytes, apparently. + for (uint i = 0; i < sortedOffsets.Length; i++) + { + byte[]? data = files[sortedOffsets[i].INDEX].Source?.GetData(); + if (data != null) + { + bw.WriteBytes(data); + } + bw.Align(SectorAlignment); + } + + DatabaseObjectModel utfETOC = BuildEtocUTF(files); + MemoryStream maUTFETOC = new MemoryStream(); + Document.Save(utfETOC, dfUTF, maUTFETOC); + + byte[] utfETOC_data = maUTFETOC.ToArray(); + bw.Align(SectorAlignment); + WriteChunk(bw, "ETOC", utfETOC_data); + } + + private void WriteChunk(Writer writer, string chunkID, byte[] chunkData) + { + writer.WriteFixedLengthString(chunkID); + writer.WriteInt32(255); + writer.WriteInt64(chunkData.Length); + writer.WriteBytes(chunkData); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKFileMode.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKFileMode.cs new file mode 100644 index 0000000..015f9f7 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/CPKFileMode.cs @@ -0,0 +1,32 @@ +// +// CPKFileMode.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK; + +public enum CPKFileMode +{ + IDOnly = 0, + FilenameOnly = 1, + IDFilename = 2, + FilenameGroup = 3, + IDGroup = 4, + FilenameIDGroup = 5 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/Internal/IDOFFSET.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/Internal/IDOFFSET.cs new file mode 100644 index 0000000..4a205b2 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/DataFormats/FileSystem/CPK/Internal/IDOFFSET.cs @@ -0,0 +1,41 @@ +// +// IDOFFSET.cs - internal structure for representing an ID, offset, and size for a file in a CPK archive +// +// Author: +// Michael Becker +// +// Copyright (c) 2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK; + +/// +/// Internal structure for representing an ID, offset, and size for a file in a CPK archive. +/// +internal struct IDOFFSET +{ + public int INDEX; + public uint ID; + public ulong OFFSET; + public ulong SIZE; + + public IDOFFSET(int index, uint id, ulong offset, ulong size) + { + INDEX = index; + ID = id; + OFFSET = offset; + SIZE = size; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/MBS.Editor.Plugins.CRI.csproj b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/MBS.Editor.Plugins.CRI.csproj new file mode 100644 index 0000000..3add91d --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.CRI/MBS.Editor.Plugins.CRI.csproj @@ -0,0 +1,11 @@ + + + + + + + net8.0 + enable + enable + + \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/AssemblyInfo.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/AssemblyInfo.cs new file mode 100644 index 0000000..4164eff --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Reflection; + +[assembly: AssemblyCompany("Mike Becker's Software")] \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/MBS.Editor.Plugins.Multimedia.csproj b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/MBS.Editor.Plugins.Multimedia.csproj new file mode 100644 index 0000000..c76dfda --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/MBS.Editor.Plugins.Multimedia.csproj @@ -0,0 +1,12 @@ + + + + + + + net8.0 + enable + enable + false + + \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/PositionVector2.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/PositionVector2.cs new file mode 100644 index 0000000..ece9b23 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/PositionVector2.cs @@ -0,0 +1,123 @@ +// +// PositionVector2.cs - provides a tuple indicating X and Y position +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace UniversalEditor +{ + /// + /// Provides a tuple indicating X and Y position. + /// + public struct PositionVector2 : ICloneable + { + public bool IsEmpty { get; } + + private PositionVector2(bool empty) + { + X = 0; + Y = 0; + IsEmpty = empty; + } + + /// + /// Represents the empty . This field is read-only. + /// + public static readonly PositionVector2 Empty = new PositionVector2(true); + + public double X { get; set; } + public double Y { get; set; } + + public PositionVector2(float x, float y) + { + X = x; + Y = y; + IsEmpty = false; + } + public PositionVector2(double x, double y) + { + X = x; + Y = y; + IsEmpty = false; + } + + public double[] ToDoubleArray() + { + return new double[] { X, Y }; + } + public float[] ToFloatArray() + { + return new float[] { (float)X, (float)Y }; + } + + public static PositionVector2 operator +(PositionVector2 left, PositionVector2 right) + { + return new PositionVector2(left.X + right.X, left.Y + right.Y); + } + public static PositionVector2 operator -(PositionVector2 left, PositionVector2 right) + { + return new PositionVector2(left.X - right.X, left.Y - right.Y); + } + public static PositionVector2 operator *(PositionVector2 left, PositionVector2 right) + { + return new PositionVector2(left.X * right.X, left.Y * right.Y); + } + public static PositionVector2 operator /(PositionVector2 left, PositionVector2 right) + { + return new PositionVector2(left.X / right.X, left.Y / right.Y); + } + + public override string ToString() + { + return ToString(", ", "(", ")"); + } + public string ToString(string separator, string encloseStart, string encloseEnd) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + if (encloseStart != null) + { + sb.Append(encloseStart); + } + sb.Append(String.Format("{0:0.0#####################}", X)); + sb.Append(separator); + sb.Append(String.Format("{0:0.0#####################}", Y)); + if (encloseEnd != null) + { + sb.Append(encloseEnd); + } + return sb.ToString(); + } + + public object Clone() + { + PositionVector2 clone = new PositionVector2(); + clone.X = X; + clone.Y = Y; + return clone; + } + + public double GetLargestComponentValue() + { + if (X > Y) return X; + if (Y > X) return Y; + return 0.0; + } + } +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Compression/CompressionTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Compression/CompressionTests.cs new file mode 100644 index 0000000..85495d0 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Compression/CompressionTests.cs @@ -0,0 +1,141 @@ +using System.Diagnostics; +using MBS.Core.Collections; +using NUnit.Framework.Internal; + +namespace MBS.Editor.Core.Tests.Compression; + +using MBS.Editor.Core.Compression.Modules.Deflate; +using MBS.Editor.Core.Compression.Modules.GZip; +using MBS.Editor.Core.Compression.Modules.LZW; + +[TestFixture] +public class CompressionTests +{ + [SetUp] + public void Setup() + { + } + + public readonly byte[] TEST_DATA = new byte[] { 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF }; + + [Test] + public void GZipCompressionModuleTest() + { + Console.WriteLine("GZipCompressionModuleTest"); + + GZipCompressionModule module = new GZipCompressionModule(); + byte[] compressed = module.Compress(TEST_DATA); + Console.WriteLine("compressed bytes: " + compressed.ToString(" ", "x")); + + module = new GZipCompressionModule(); + byte[] decompressed = module.Decompress(compressed); + Console.WriteLine("decompressed bytes: " + decompressed.ToString(" ", "x")); + + Assert.That(decompressed.Length, Is.EqualTo(TEST_DATA.Length)); + for (int i = 0; i < TEST_DATA.Length; i++) + { + Assert.That(decompressed[i], Is.EqualTo(TEST_DATA[i])); + } + } + + [Test] + public void DeflateCompressionModuleTest() + { + Console.WriteLine("DeflateCompressionModuleTest"); + + DeflateCompressionModule module = new DeflateCompressionModule(); + byte[] compressed = module.Compress(TEST_DATA); + Console.WriteLine("compressed bytes: " + compressed.ToString(" ", "x")); + + module = new DeflateCompressionModule(); + byte[] decompressed = module.Decompress(compressed); + Console.WriteLine("decompressed bytes: " + decompressed.ToString(" ", "x")); + + Assert.That(decompressed.Length, Is.EqualTo(TEST_DATA.Length)); + for (int i = 0; i < TEST_DATA.Length; i++) + { + Assert.That(decompressed[i], Is.EqualTo(TEST_DATA[i])); + } + } + + [Test] + public void ZlibBuiltinCompressionModuleTest() + { + Console.WriteLine("ZlibBuiltinCompressionModuleTest"); + + ZlibBuiltinCompressionModule module = new ZlibBuiltinCompressionModule(); + byte[] compressed = module.Compress(TEST_DATA); + Console.WriteLine("compressed bytes: " + compressed.ToString(" ", "x")); + + module = new ZlibBuiltinCompressionModule(); + byte[] decompressed = module.Decompress(compressed); + Console.WriteLine("decompressed bytes: " + decompressed.ToString(" ", "x")); + + Assert.That(decompressed.Length, Is.EqualTo(TEST_DATA.Length)); + for (int i = 0; i < TEST_DATA.Length; i++) + { + Assert.That(decompressed[i], Is.EqualTo(TEST_DATA[i])); + } + } + + [Test] + public void LZWCompressionModuleTest() + { + Assert.Ignore("we know we don't yet support compression, only decompression for now"); + + Console.WriteLine("LZWCompressionModuleTest"); + + LZWCompressionModule module = new LZWCompressionModule(); + byte[] compressed = module.Compress(TEST_DATA); + Console.WriteLine("compressed bytes: " + compressed.ToString(" ", "x")); + + module = new LZWCompressionModule(); + byte[] decompressed = module.Decompress(compressed); + Console.WriteLine("decompressed bytes: " + decompressed.ToString(" ", "x")); + + Assert.That(decompressed.Length, Is.EqualTo(TEST_DATA.Length)); + for (int i = 0; i < TEST_DATA.Length; i++) + { + Assert.That(decompressed[i], Is.EqualTo(TEST_DATA[i])); + } + } + + [Test] + public void LZWDecompressionOnlyTest() + { + Console.WriteLine("LZWCompressionOnlyTest"); + + byte[] compressed = { 0x1F, 0x9D, 0x90, 0xCA, 0xFC, 0xE9, 0xF2, 0x05, 0xA0, 0xA0, 0xC1, 0x82, 0x23, 0x20, 0x8D, 0x88, 0x42, 0x06, 0x52, 0x18, 0x02, 0x51, 0x24, 0x11, 0x08, 0x73, 0x29, 0xCA, 0x01, 0x30, 0x83, 0x8A, 0x50, 0x1A, 0x03, 0x24, 0x4C, 0xA5, 0x20, 0x90, 0x8C, 0x5C, 0xB9, 0x10, 0x80, 0x46, 0x9A, 0x09, 0x06, 0x32, 0x40, 0x91, 0x50, 0x23, 0x01, 0xCB, 0x00, 0x29, 0xC2, 0x0C, 0x01, 0x33, 0xC1, 0x48, 0x00, 0x23, 0x19, 0x68, 0x80, 0xA1, 0x90, 0x86, 0x42, 0x82, 0x30, 0x1D, 0x3B, 0x0E, 0xC8, 0x52, 0x47, 0xD2, 0x81, 0x12, 0x97, 0x40, 0x9C, 0x04, 0x32, 0x06, 0x4C, 0x24, 0x1A, 0x71, 0x68, 0x14, 0x08, 0x63, 0xE0, 0xE9, 0x81, 0x2B, 0x50, 0x12, 0x55, 0x48, 0x80, 0xC1, 0x8A, 0xCB, 0x41, 0x57, 0x02, 0x4C, 0x42, 0x12, 0xE6, 0x46, 0x02, 0x2C, 0x71, 0xCE, 0x5E, 0x80, 0x82, 0xE9, 0x42, 0x11, 0x48, 0x18, 0xA0, 0x64, 0xE0, 0x9A, 0x40, 0x51, 0x82, 0x43, 0x60, 0xEC, 0x28, 0xC0, 0x04, 0x07, 0xD3, 0xA9, 0x04, 0x80, 0x30, 0x35, 0xD0, 0x04, 0x28, 0x5B, 0xA3, 0x6C, 0x80, 0xB4, 0x5D, 0x3A, 0x63, 0x00, 0x0E, 0x18, 0x40, 0x9F, 0x3E, 0xFD, 0x9B, 0x4C, 0x99, 0xB2, 0xB7, 0x56, 0xBE, 0xDE, 0x1D, 0xDC, 0x0C }; + Console.WriteLine("compressed bytes: " + compressed.ToString(" ", "x")); + + LZWCompressionModule module = new LZWCompressionModule(); + byte[] decompressed = module.Decompress(compressed); + Console.WriteLine("decompressed bytes: " + decompressed.ToString(" ", "x")); + + // Assert.That(decompressed.Length, Is.EqualTo(compressed.Length)); + for (int i = 0; i < 4; i++) + { + Assert.That(decompressed[i], Is.EqualTo(TEST_DATA[i])); + } + } + + [Test] + public void BZip2CompressionModuleTest() + { + Console.WriteLine("BZip2CompressionModuleTest"); + + BZip2CompressionModule module = new BZip2CompressionModule(); + byte[] compressed = module.Compress(TEST_DATA); + Console.WriteLine("compressed bytes: " + compressed.ToString(" ", "x")); + + module = new BZip2CompressionModule(); + byte[] decompressed = module.Decompress(compressed); + Console.WriteLine("decompressed bytes: " + decompressed.ToString(" ", "x")); + + Assert.That(decompressed.Length, Is.EqualTo(TEST_DATA.Length)); + for (int i = 0; i < TEST_DATA.Length; i++) + { + Assert.That(decompressed[i], Is.EqualTo(TEST_DATA[i])); + } + } +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/GlobalUsings.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj new file mode 100644 index 0000000..050e4ca --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj @@ -0,0 +1,19 @@ + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs new file mode 100644 index 0000000..de922a8 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs @@ -0,0 +1,45 @@ + +using MBS.Editor.Core.ObjectModels.FileSystem; + +namespace MBS.Editor.Core.Tests.ObjectModels.FileSystem; + +public class FileSystemItemCollectionTests +{ + [ Test()] + public static void AddFolderWithEmptyNameTest() + { + // the reason this test exists + // is because I screwed something up + // and caused a stack overflow when adding a Folder with empty name =(^.^)= + FileSystemObjectModel fsom = new FileSystemObjectModel(); + + FileSystemFolder folder = fsom.Items.AddFolder(""); + Assert.That(folder, Is.Not.Null); + Assert.That(folder.Name, Is.EqualTo(String.Empty)); + } + [Test()] + public static void AddFolderWithSingleNameTest() + { + // the reason this test exists + // is... just to make sure this works + FileSystemObjectModel fsom = new FileSystemObjectModel(); + + FileSystemFolder folder = fsom.Items.AddFolder("System32"); + Assert.That(folder, Is.Not.Null); + Assert.That(folder.Name, Is.EqualTo("System32")); + } + [Test()] + public static void AddFolderWithPathTest() + { + // the reason THIS test exists + // is because I screwed SOMETHING ELSE up + // and ended up messing up the entire AddFolder implementation (x .x) + FileSystemObjectModel fsom = new FileSystemObjectModel(); + + FileSystemFolder folder = fsom.Items.AddFolder("C/Windows/System32/Drivers"); + Assert.That(folder, Is.Not.Null); + Assert.That(folder.Name, Is.EqualTo("Drivers")); + Assert.That(folder.Parent, Is.Not.Null); + Assert.That(((FileSystemFolder)folder.Parent).Name, Is.EqualTo("System32")); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemTests.cs new file mode 100644 index 0000000..aa57360 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemTests.cs @@ -0,0 +1,36 @@ +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.Tests.ObjectModels.FileSystem; + +[TestFixture] +public class FileSystemTests +{ + [SetUp] + public void Setup() + { + } + + public readonly byte[] TEST_DATA = new byte[] { 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF }; + + [Test] + public void ByteArrayFileSourceTest() + { + ByteArrayFileSource source = new ByteArrayFileSource(TEST_DATA); + byte[] data = source.GetData(4, 2); + + Assert.That(data[0], Is.EqualTo(TEST_DATA[4])); + Assert.That(data[1], Is.EqualTo(TEST_DATA[5])); + } + + [Test] + public void MemoryStreamFileSourceTest() + { + MemoryStream ms = new MemoryStream(TEST_DATA); + StreamFileSource source = new StreamFileSource(ms); + byte[] data = source.GetData(4, 2); + + Assert.That(data[0], Is.EqualTo(TEST_DATA[4])); + Assert.That(data[1], Is.EqualTo(TEST_DATA[5])); + } +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/AFS/AFSDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/AFS/AFSDataFormatTests.cs new file mode 100644 index 0000000..7b58708 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/AFS/AFSDataFormatTests.cs @@ -0,0 +1,35 @@ +using MBS.Editor.Core; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS; + +namespace MBS.Editor.Plugins.CRI.Tests.DataFormats.AFS; + +public class AFSDataFormatTests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + FileSystemObjectModel fsom = new FileSystemObjectModel(); + fsom.Items.AddFile("System.dat", new ByteArrayFileSource(new byte[] { 0xCA, 0xFE, 0xBA, 0xBE })); + + System.IO.MemoryStream ms = new System.IO.MemoryStream(); + + AFSDataFormat afs = new AFSDataFormat(); + Document.Save(fsom, afs, ms); + + ms.Seek(0, System.IO.SeekOrigin.Begin); + + fsom = new FileSystemObjectModel(); + Document.Load(fsom, afs, ms); + + Assert.That(fsom.Items.GetFiles().Length, Is.EqualTo(1)); + Assert.That((fsom.Items["System.dat"] as FileSystemFile)?.Source?.GetData(2, 1)[0], Is.EqualTo(0xBA)); + + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs new file mode 100644 index 0000000..a2678c9 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs @@ -0,0 +1,170 @@ +using System; +using MBS.Core; +using MBS.Core.Collections; +using MBS.Editor.Core; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK; +using NUnit.Framework.Internal; + +namespace MBS.Editor.Plugins.CRI.Tests.DataFormats.CPK; + +public class CPKDataFormatTests +{ + private System.Collections.Generic.Dictionary testDataStreams = new System.Collections.Generic.Dictionary(); + + [SetUp] + public void Setup() + { + MBS.Core.Reflection.ManifestResourceStream[] strms = MBS.Core.Reflection.ManifestResourceStream.GetManifestResourceStreamsForAssembly(System.Reflection.Assembly.GetExecutingAssembly()); + for (int i = 0; i < strms.Length; i++) + { + testDataStreams[strms[i].Name] = strms[i].Stream; + } + } + + [Test(Description = "Ensures the CPKDataFormat can store exactly one file, uncompressed, with no filename masking.")] + public void EditorReadWriteSingleFileUncompressedUnmaskedTest() + { + byte[] TEST_DATA = new byte[] { 0xCA, 0xFE, 0xBA, 0xBE }; + FileSystemObjectModel fsom = new FileSystemObjectModel(); + fsom.Items.AddFile("System.dat", new ByteArrayFileSource(TEST_DATA)); + + System.IO.MemoryStream ms = new System.IO.MemoryStream(); + + CPKDataFormat afs = new CPKDataFormat(); + Document.Save(fsom, afs, ms); + + byte[] stdata = ms.ToArray(); + System.IO.File.WriteAllBytes("/tmp/tst.cpk", stdata); + + ms.Seek(0, System.IO.SeekOrigin.Begin); + + fsom = new FileSystemObjectModel(); + Document.Load(fsom, afs, ms); + + Assert.That(fsom.Items.GetFiles().Length, Is.EqualTo(1), "File system must have exactly one file"); + + FileSystemFile? System_dat = fsom.Items["System.dat"] as FileSystemFile; + Assert.That(System_dat, Is.Not.Null, "The file 'System.dat' must exist and be a File"); + Assert.That(System_dat.Source, Is.Not.Null, "The file 'System.dat' must have an associated FileSource"); + Assert.That(System_dat.Source.Length, Is.EqualTo(TEST_DATA.Length)); + + Console.WriteLine(String.Format("System.dat source offset: {0} length: {1}", ((CompressedEmbeddedFileSource)System_dat.Source).Offset, System_dat.Source.Length)); + + byte[] data = System_dat.Source.GetData(); + Console.WriteLine("System.dat bytes: " + data.ToString(" ", "x")); + + Assert.That(System_dat.Source.GetData(2, 1)[0], Is.EqualTo(0xBA)); + } + + private void SampleStreamTest(string streamName) + { + if (testDataStreams.ContainsKey(streamName)) + { + System.IO.Stream TEST_STREAM = testDataStreams[streamName]; + SampleStreamTest(TEST_STREAM); + } + else + { + Console.Error.WriteLine("test data stream not found: '" + streamName + "'"); + Assert.Ignore(); + } + } + private void SampleStreamTest(System.IO.Stream stream) + { + stream.Seek(0, System.IO.SeekOrigin.Begin); + + CPKDataFormat afs = new CPKDataFormat(); + FileSystemObjectModel fsom = new FileSystemObjectModel(); + Document.Load(fsom, afs, stream); + + Assert.That(fsom.Items.Count, Is.EqualTo(2)); + Assert.That(fsom.Items[0].Name, Is.EqualTo("folder1")); + Assert.That(fsom.Items[1].Name, Is.EqualTo("folder2")); + + Assert.That(fsom.Items[0] is FileSystemFolder); + Assert.That(fsom.Items[1] is FileSystemFolder); + + Assert.That(((FileSystemFolder)fsom.Items[0]).Items.Count, Is.EqualTo(2)); + Assert.That(((FileSystemFolder)fsom.Items[0]).Items[0].Name, Is.EqualTo("data1.bmp")); + Assert.That(((FileSystemFile)((FileSystemFolder)fsom.Items[0]).Items[0]).Size, Is.EqualTo(50228)); + + byte[] BM4 = ((FileSystemFile)((FileSystemFolder)fsom.Items[0]).Items[0]).Source.GetData(); + Assert.That(BM4[0], Is.EqualTo((byte)'B')); + Assert.That(BM4[1], Is.EqualTo((byte)'M')); + + Assert.That(((FileSystemFolder)fsom.Items[0]).Items[1].Name, Is.EqualTo("data2.bmp")); + Assert.That(((FileSystemFile)((FileSystemFolder)fsom.Items[0]).Items[1]).Size, Is.EqualTo(50228)); + + Assert.That(((FileSystemFolder)fsom.Items[1]).Items.Count, Is.EqualTo(3)); + Assert.That(((FileSystemFolder)fsom.Items[1]).Items[0].Name, Is.EqualTo("data3.bmp")); + Assert.That(((FileSystemFile)((FileSystemFolder)fsom.Items[1]).Items[0]).Size, Is.EqualTo(50228)); + + Assert.That(((FileSystemFolder)fsom.Items[1]).Items[1].Name, Is.EqualTo("data4.bmp")); + Assert.That(((FileSystemFile)((FileSystemFolder)fsom.Items[1]).Items[1]).Size, Is.EqualTo(50228)); + + Assert.That(((FileSystemFolder)fsom.Items[1]).Items[2].Name, Is.EqualTo("voice1.ahx")); + Assert.That(((FileSystemFile)((FileSystemFolder)fsom.Items[1]).Items[2]).Size, Is.EqualTo(2120)); + } + + [Test(Description = "Ensures that CPK files generated by CPKMG are understood by MBS Editor")] + public void CPKFileBuilderUncompressedUnmaskedTest() + { + SampleStreamTest("MBS.Editor.Plugins.CRI.Tests.Resources.TestData.sample_data_uncompressed_unmasked.cpk"); + } + + [Test(Description = "Ensures that CPK files generated by CPKMG, including with IDs and filenames, are understood by MBS Editor")] + public void CPKFileBuilderUncompressedUnmaskedWithIDsAndFileNamesTest() + { + SampleStreamTest("MBS.Editor.Plugins.CRI.Tests.Resources.TestData.sample_data_uncompressed_unmasked_idfn.cpk"); + } + + [Test(Description = "Ensures that CPK files generated by CPKMG, using default settings, are understood by MBS Editor")] + public void CPKFileBuilderDefaultTest() + { + // this includes CRILAYLA compression, yay! + SampleStreamTest("MBS.Editor.Plugins.CRI.Tests.Resources.TestData.sample_data.cpk"); + } + + [Test(Description = "Ensures that CPK files generated by CPKMG, with filename information masked, are understood by MBS Editor")] + public void CPKFileBuilderUncompressedMaskedTest() + { + SampleStreamTest("MBS.Editor.Plugins.CRI.Tests.Resources.TestData.sample_data_uncompressed_masked.cpk"); + } + + [Test(Description = "Ensures the CPKDataFormat can store exactly one file, CRILAYLA compressed, with no filename masking.")] + public void SingleFileLaylaCompressedUnmaskedTest() + { + byte[] TEST_DATA = new byte[] { 0xCA, 0xFE, 0xBA, 0xBE }; + FileSystemObjectModel fsom = new FileSystemObjectModel(); + fsom.Items.AddFile("System.dat", new ByteArrayFileSource(TEST_DATA)); + + System.IO.MemoryStream ms = new System.IO.MemoryStream(); + + CPKDataFormat afs = new CPKDataFormat(); + Document.Save(fsom, afs, ms); + + byte[] stdata = ms.ToArray(); + System.IO.File.WriteAllBytes("/tmp/tst.cpk", stdata); + + ms.Seek(0, System.IO.SeekOrigin.Begin); + + fsom = new FileSystemObjectModel(); + Document.Load(fsom, afs, ms); + + Assert.That(fsom.Items.GetFiles().Length, Is.EqualTo(1), "File system must have exactly one file"); + + FileSystemFile? System_dat = fsom.Items["System.dat"] as FileSystemFile; + Assert.That(System_dat, Is.Not.Null, "The file 'System.dat' must exist and be a File"); + Assert.That(System_dat.Source, Is.Not.Null, "The file 'System.dat' must have an associated FileSource"); + Assert.That(System_dat.Source.Length, Is.EqualTo(TEST_DATA.Length)); + + Console.WriteLine(String.Format("System.dat source offset: {0} length: {1}", ((CompressedEmbeddedFileSource)System_dat.Source).Offset, System_dat.Source.Length)); + + byte[] data = System_dat.Source.GetData(); + Console.WriteLine("System.dat bytes: " + data.ToString(" ", "x")); + + Assert.That(System_dat.Source.GetData(2, 1)[0], Is.EqualTo(0xBA)); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/GlobalUsings.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj new file mode 100644 index 0000000..cd028c0 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj @@ -0,0 +1,26 @@ + + + net8.0 + disable + enable + false + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/Resources/TestData/sample_data.cpk b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/Resources/TestData/sample_data.cpk new file mode 100644 index 0000000000000000000000000000000000000000..a73390da06fa9cb9756d9f07a49532574d576d26 GIT binary patch literal 148232 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 @JgYt6?QT6F)8(q}RA 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
P}9D?XmCZ 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

HXlCDf7$zt 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

KMI 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%0AMzg*7P4VuP}o57w$PyehrKuPtGZ18 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 cFHX!Y$Vmk%O3Y3LCW811xL7zviUNu% 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%0AF(2mMYh^EhITvpBlTUU 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

%L2w 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 C-57WP literal 0 HcmV?d00001 From 0654b86aa941022b7a366d19344f2ffa9db68f27 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 13 Jun 2024 10:02:08 -0400 Subject: [PATCH 2/4] update framework-dotnet --- framework-dotnet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework-dotnet b/framework-dotnet index 5b1c1d5..a24ae26 160000 --- a/framework-dotnet +++ b/framework-dotnet @@ -1 +1 @@ -Subproject commit 5b1c1d5559fdc5d97fe47565f55f4d25b3fa071e +Subproject commit a24ae26d5775be4387a58f61a3dc26057e0c1171 From aef4eb4f37777a52c5d69e4b6a1cb86bd82d9f00 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 20 Mar 2025 18:51:08 -0400 Subject: [PATCH 3/4] major update commit --- .gitmodules | 6 + desktop-framework-dotnet | 1 + editor-dotnet.sln | 115 +++++- .../MBS.Editor.Web.Server.csproj | 15 + .../src/app/MBS.Editor.Web.Server/Program.cs | 102 +++++ .../src/app/MBS.Editor.Web.Server/TODO.txt | 12 + .../src/app/MBS.Editor/MBS.Editor.csproj | 2 +- editor-dotnet/src/install-engines.sh | 23 ++ .../src/lib/MBS.Editor.Core/DataFormat.cs | 59 ++- .../DataFormats/Chunked/ChunkedDataFormat.cs | 142 +++++++ .../FileSystem/IFF/IFFDataFormat.cs | 45 +++ .../FileSystem/IFF/RIFFDataFormat.cs | 27 ++ .../PropertyList/INI/INIDataFormat.cs | 205 ++++++++++ .../PropertyList/INI/INIDataFormatToken.cs | 9 + .../src/lib/MBS.Editor.Core/IO/Reader.cs | 27 +- .../MBS.Editor.Core/IO/ReaderWriterBase.cs | 25 +- .../src/lib/MBS.Editor.Core/ObjectModel.cs | 11 +- .../ObjectModelNotSupportedException.cs | 14 +- .../ObjectModels/Chunked/Chunk.cs | 63 ++++ .../Chunked/ChunkedObjectModel.cs | 23 ++ .../ObjectModels/Chunked/DataChunk.cs | 29 ++ .../ObjectModels/Chunked/GroupChunk.cs | 29 ++ .../ObjectModels/Chunked/IChunkContainer.cs | 23 ++ .../ObjectModels/FileSystem/FileSource.cs | 8 + .../FileSources/EmbeddedFileSource.cs | 2 +- .../FileSystem/FileSystemItemCollection.cs | 10 + .../PropertyList/IPropertyListContainer.cs | 9 + .../PropertyList/PropertyListComment.cs | 6 + .../PropertyList/PropertyListGroup.cs | 14 + .../PropertyList/PropertyListItem.cs | 58 +++ .../PropertyList/PropertyListObjectModel.cs | 18 + .../PropertyList/PropertyListProperty.cs | 41 ++ .../Schema/PropertyDataTypeDefinition.cs | 27 ++ .../PropertyList/Schema/PropertyDefinition.cs | 41 ++ .../PropertyList/Schema/PropertyListSchema.cs | 14 + .../EmbeddedResourceTest.cs | 65 ++++ .../MBS.Editor.Testing.csproj | 19 + .../EditorApplication.cs | 34 +- .../MBS.Editor.UserInterface/EditorWindow.cs | 20 + .../MBS.Editor.UserInterface.csproj | 2 +- .../lib/MBS.Editor.Web/Controls/PageHeader.cs | 31 ++ .../lib/MBS.Editor.Web/MBS.Editor.Web.csproj | 14 + .../src/lib/MBS.Editor.Web/Pages/MainPage.cs | 123 ++++++ .../MBS.Editor.Plugins.Kronosaur/Class1.cs | 6 + .../MBS.Editor.Plugins.Kronosaur.csproj | 9 + .../DataFormats/Mechanic/MEKDataFormat.cs | 70 ++++ .../PropertyList/MEKBaseDataFormat.cs | 92 +++++ .../PropertyList/MEKPropertyType.cs | 10 + .../MBS.Editor.Plugins.Mekada.csproj | 14 + .../Mechanic/MechanicObjectModel.cs | 11 + .../Audio/Synthesized/MIDI/MIDICommand.cs | 143 +++++++ .../Audio/Synthesized/MIDI/MIDICommandType.cs | 38 ++ .../Audio/Synthesized/MIDI/MIDIDataFormat.cs | 353 ++++++++++++++++++ .../Audio/Synthesized/MIDI/MIDIEventType.cs | 46 +++ .../Synthesized/MIDI/MIDIFileFormatType.cs | 46 +++ .../Synthesized/MIDI/MIDIMetaEventType.cs | 46 +++ .../Audio/Synthesized/RMI/RMIDataFormat.cs | 114 ++++++ .../Picture/ILBM/ILBMCompression.cs | 27 ++ .../Picture/ILBM/ILBMDataFormat.cs | 164 ++++++++ .../DataFormats/Picture/ILBM/ILBMMasking.cs | 46 +++ .../ObjectModels/Audio/AudioMetadata.cs | 26 ++ .../Audio/AudioObjectModelBase.cs | 26 ++ .../Synthesized/SynthesizedAudioCommand.cs | 114 ++++++ .../SynthesizedAudioCommandNote.cs | 82 ++++ .../SynthesizedAudioCommandRest.cs | 29 ++ .../SynthesizedAudioCommandTempo.cs | 49 +++ .../SynthesizedAudioCommandText.cs | 48 +++ .../SynthesizedAudioCommandTimeSignature.cs | 66 ++++ .../SynthesizedAudioObjectModel.cs | 109 ++++++ .../SynthesizedAudioPredefinedNote.cs | 98 +++++ .../SynthesizedAudioStylePlugin.cs | 63 ++++ .../Synthesized/SynthesizedAudioTrack.cs | 101 +++++ .../SynthesizedAudioVibratoType.cs | 30 ++ .../ObjectModels/Palette/PaletteEntry.cs | 71 ++++ .../Palette/PaletteObjectModel.cs | 85 +++++ .../Picture/PictureObjectModel.cs | 55 +++ .../FileSystem/IFF/IFFDataFormatTests.cs | 49 +++ .../MBS.Editor.Core.Tests.csproj | 5 + .../FileSystemItemCollectionTests.cs | 2 +- .../PropertyList/INIDataFormatTests.cs | 80 ++++ .../DataFormats/FileSystem/IFF/VENUS.IFF | Bin 0 -> 26978 bytes .../PropertyList/INI/CompleteTest.ini | 16 + .../DataFormats/CPK/CPKDataFormatTests.cs | 25 +- .../Database/UTF/UTFDataFormatTests.cs | 8 + .../MBS.Editor.Plugins.CRI.Tests.csproj | 1 + .../DataFormats/MEK/MEKDataFormatTests.cs | 59 +++ .../GlobalUsings.cs | 1 + .../MBS.Editor.Plugins.Mekada.Tests.csproj | 39 ++ .../Resources/TestData/Mechanics/0.mek | Bin 0 -> 389 bytes .../Resources/TestData/Mechanics/1.mek | Bin 0 -> 16561 bytes .../TestData/Mechanics/WithCarInLot.mek | Bin 0 -> 72760 bytes .../Synthesized/RMI/RMIDataFormatTests.cs | 117 ++++++ .../Picture/ILBM/ILBMDataFormatTests.cs | 62 +++ .../GlobalUsings.cs | 1 + ...MBS.Editor.Plugins.Multimedia.Tests.csproj | 42 +++ .../Audio/Synthesized/RMI/Mountain.rmi | Bin 0 -> 38444 bytes .../TestData/DataFormats/Picture/ILBM/BLK.IFF | Bin 0 -> 2564 bytes .../DataFormats/Picture/ILBM/VENUS.IFF | Bin 0 -> 26978 bytes framework-dotnet | 2 +- web-framework-dotnet | 1 + 100 files changed, 4278 insertions(+), 82 deletions(-) create mode 160000 desktop-framework-dotnet create mode 100644 editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj create mode 100644 editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs create mode 100644 editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt create mode 100755 editor-dotnet/src/install-engines.sh create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj create mode 100644 editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs create mode 100644 editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj create mode 100644 editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs create mode 100644 editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/DataFormats/FileSystem/IFF/VENUS.IFF create mode 100644 editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/0.mek create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/1.mek create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/WithCarInLot.mek create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Audio/Synthesized/RMI/Mountain.rmi create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/BLK.IFF create mode 100644 editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/VENUS.IFF create mode 160000 web-framework-dotnet diff --git a/.gitmodules b/.gitmodules index 479b021..3dc70c7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "framework-dotnet"] path = framework-dotnet url = git@gitea.azcona-becker.net:alcetech/framework-dotnet +[submodule "desktop-framework-dotnet"] + path = desktop-framework-dotnet + url = git@gitea.azcona-becker.net:alcetech/desktop-framework-dotnet +[submodule "web-framework-dotnet"] + path = web-framework-dotnet + url = git@gitea.azcona-becker.net:alcetech/web-framework-dotnet diff --git a/desktop-framework-dotnet b/desktop-framework-dotnet new file mode 160000 index 0000000..57316e3 --- /dev/null +++ b/desktop-framework-dotnet @@ -0,0 +1 @@ +Subproject commit 57316e3557c1c3a566d1a24bd60675c02d1f8744 diff --git a/editor-dotnet.sln b/editor-dotnet.sln index 499bead..a0cc482 100644 --- a/editor-dotnet.sln +++ b/editor-dotnet.sln @@ -14,8 +14,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor.UserInterface", "editor-dotnet\src\lib\MBS.Editor.UserInterface\MBS.Editor.UserInterface.csproj", "{C4316562-555A-4A79-9D71-15737976DF8B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{4ED8C38B-47EF-4368-9965-CF627465B45A}" - ProjectSection(SolutionItems) = preProject - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Editor", "editor-dotnet\src\app\MBS.Editor\MBS.Editor.csproj", "{A936C411-0184-43F8-A343-0DE8C3B7B42E}" EndProject @@ -27,8 +25,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BDC147D8-4D9 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{80A728D5-7C00-4B59-A37E-321C54CC554F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Desktop", "framework-dotnet\framework-dotnet\src\lib\MBS.Desktop\MBS.Desktop.csproj", "{4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBS.Core", "framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj", "{7565CFB4-9761-4064-B18F-5E2644730BC0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{451AD529-16B4-4049-9D0C-0C79B3DDFA52}" @@ -45,6 +41,46 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Core.Tests", "ed EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.CRI.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.CRI.Tests\MBS.Editor.Plugins.CRI.Tests.csproj", "{2747FFC9-55AA-4A76-B0E9-D8A839E94E47}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "desktop-framework-dotnet", "desktop-framework-dotnet", "{1BEBE6E9-D723-4A76-8210-D470DEB8C9C7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "desktop-framework-dotnet", "desktop-framework-dotnet", "{68FC9B7D-A168-47CE-9106-A2501F5E4814}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9C1544A0-92FF-46F3-A8F3-93A6EB599EDD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{418A36B7-1856-4945-AB9D-D66956A6A549}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Desktop", "desktop-framework-dotnet\desktop-framework-dotnet\src\lib\MBS.Desktop\MBS.Desktop.csproj", "{11862DE3-B214-42C5-9CFE-72FFE7F29F09}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "engines", "engines", "{C91C4319-D6B5-448C-BC28-3F5F65EC4EB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Desktop.Engines.GTK3", "desktop-framework-dotnet\desktop-framework-dotnet\src\engines\MBS.Desktop.Engines.GTK3\MBS.Desktop.Engines.GTK3.csproj", "{A738CB41-831D-4BB3-A7F5-87DB19FAD87B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Mekada", "editor-dotnet\src\plugins\MBS.Editor.Plugins.Mekada\MBS.Editor.Plugins.Mekada.csproj", "{9D1F7A94-B165-4581-B68D-4E2CF9F83D30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Mekada.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.Mekada.Tests\MBS.Editor.Plugins.Mekada.Tests.csproj", "{55865BF2-E3C3-4823-A295-C85F6D98901F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Testing", "editor-dotnet\src\lib\MBS.Editor.Testing\MBS.Editor.Testing.csproj", "{93A3D49C-A6EC-4F19-A410-F222DE1C9358}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Kronosaur", "editor-dotnet\src\plugins\MBS.Editor.Plugins.Kronosaur\MBS.Editor.Plugins.Kronosaur.csproj", "{5D627852-158F-460D-9E5F-56F80BDB9A0B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{D5423E32-5CD5-4207-8B56-235D470E7BBB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Core.Tests", "framework-dotnet\framework-dotnet\src\tests\MBS.Core.Tests\MBS.Core.Tests.csproj", "{13F3D325-9EB8-4479-BB3F-1D663F924A8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Plugins.Multimedia.Tests", "editor-dotnet\src\tests\MBS.Editor.Plugins.Multimedia.Tests\MBS.Editor.Plugins.Multimedia.Tests.csproj", "{BDE1CD42-771E-4902-93DB-5B17C616C172}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Web.Server", "editor-dotnet\src\app\MBS.Editor.Web.Server\MBS.Editor.Web.Server.csproj", "{A74A8939-9E5A-4811-9D03-7B53E164A979}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "web-framework-dotnet", "web-framework-dotnet", "{B24A3EC9-CAC1-4162-A71F-4E085E828DAD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5A57B76D-C7E1-48A4-AB10-23E804590287}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{91E8E5EA-8098-4929-9E87-6C2D55F35A02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Web", "web-framework-dotnet\src\lib\MBS.Web\MBS.Web.csproj", "{09CD7984-6CB9-42BC-9A9E-31FA1C21697F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBS.Editor.Web", "editor-dotnet\src\lib\MBS.Editor.Web\MBS.Editor.Web.csproj", "{C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,10 +99,6 @@ Global {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A936C411-0184-43F8-A343-0DE8C3B7B42E}.Release|Any CPU.Build.0 = Release|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143}.Release|Any CPU.Build.0 = Release|Any CPU {7565CFB4-9761-4064-B18F-5E2644730BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7565CFB4-9761-4064-B18F-5E2644730BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU {7565CFB4-9761-4064-B18F-5E2644730BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -91,13 +123,55 @@ Global {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Debug|Any CPU.Build.0 = Debug|Any CPU {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Release|Any CPU.ActiveCfg = Release|Any CPU {2747FFC9-55AA-4A76-B0E9-D8A839E94E47}.Release|Any CPU.Build.0 = Release|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11862DE3-B214-42C5-9CFE-72FFE7F29F09}.Release|Any CPU.Build.0 = Release|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B}.Release|Any CPU.Build.0 = Release|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30}.Release|Any CPU.Build.0 = Release|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55865BF2-E3C3-4823-A295-C85F6D98901F}.Release|Any CPU.Build.0 = Release|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93A3D49C-A6EC-4F19-A410-F222DE1C9358}.Release|Any CPU.Build.0 = Release|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D627852-158F-460D-9E5F-56F80BDB9A0B}.Release|Any CPU.Build.0 = Release|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13F3D325-9EB8-4479-BB3F-1D663F924A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDE1CD42-771E-4902-93DB-5B17C616C172}.Release|Any CPU.Build.0 = Release|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A74A8939-9E5A-4811-9D03-7B53E164A979}.Release|Any CPU.Build.0 = Release|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F}.Release|Any CPU.Build.0 = Release|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} - {451AD529-16B4-4049-9D0C-0C79B3DDFA52} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} = {75210F45-D690-4A61-9CD8-96B09E5DAAC5} {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {8FFB417A-2CDC-429F-ABE0-19B3015530D3} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} @@ -107,13 +181,32 @@ Global {B9747AFE-160D-4807-B989-B3F0ACCA3634} = {CC86007D-8193-4EAA-932D-A96B5F09847E} {BDC147D8-4D97-4663-9408-BC822E1E0B3C} = {B9747AFE-160D-4807-B989-B3F0ACCA3634} {80A728D5-7C00-4B59-A37E-321C54CC554F} = {BDC147D8-4D97-4663-9408-BC822E1E0B3C} - {4F2B8AF8-E1A4-4114-B4DA-4789A3A21143} = {80A728D5-7C00-4B59-A37E-321C54CC554F} {7565CFB4-9761-4064-B18F-5E2644730BC0} = {80A728D5-7C00-4B59-A37E-321C54CC554F} + {451AD529-16B4-4049-9D0C-0C79B3DDFA52} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {5978938E-19F6-42AE-B588-7719A65ABCA7} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} {CDA151F8-5BA7-47DB-883D-CBC2DD94F0DF} = {4ED8C38B-47EF-4368-9965-CF627465B45A} {78B11A3E-1371-48D8-9B8E-AE6ED2380A50} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} = {F05001E1-6FFB-48AE-BF7F-7F39A24D3B70} {7A349FC6-BCE7-465D-ADBC-7A21242E2C78} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} {2747FFC9-55AA-4A76-B0E9-D8A839E94E47} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {68FC9B7D-A168-47CE-9106-A2501F5E4814} = {1BEBE6E9-D723-4A76-8210-D470DEB8C9C7} + {9C1544A0-92FF-46F3-A8F3-93A6EB599EDD} = {68FC9B7D-A168-47CE-9106-A2501F5E4814} + {418A36B7-1856-4945-AB9D-D66956A6A549} = {9C1544A0-92FF-46F3-A8F3-93A6EB599EDD} + {11862DE3-B214-42C5-9CFE-72FFE7F29F09} = {418A36B7-1856-4945-AB9D-D66956A6A549} + {C91C4319-D6B5-448C-BC28-3F5F65EC4EB7} = {9C1544A0-92FF-46F3-A8F3-93A6EB599EDD} + {A738CB41-831D-4BB3-A7F5-87DB19FAD87B} = {C91C4319-D6B5-448C-BC28-3F5F65EC4EB7} + {9D1F7A94-B165-4581-B68D-4E2CF9F83D30} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {55865BF2-E3C3-4823-A295-C85F6D98901F} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {93A3D49C-A6EC-4F19-A410-F222DE1C9358} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} + {5D627852-158F-460D-9E5F-56F80BDB9A0B} = {451AD529-16B4-4049-9D0C-0C79B3DDFA52} + {D5423E32-5CD5-4207-8B56-235D470E7BBB} = {BDC147D8-4D97-4663-9408-BC822E1E0B3C} + {13F3D325-9EB8-4479-BB3F-1D663F924A8D} = {D5423E32-5CD5-4207-8B56-235D470E7BBB} + {BDE1CD42-771E-4902-93DB-5B17C616C172} = {74AD8C3F-B0B8-472F-A847-1FFFB1667B34} + {A74A8939-9E5A-4811-9D03-7B53E164A979} = {4ED8C38B-47EF-4368-9965-CF627465B45A} + {5A57B76D-C7E1-48A4-AB10-23E804590287} = {B24A3EC9-CAC1-4162-A71F-4E085E828DAD} + {91E8E5EA-8098-4929-9E87-6C2D55F35A02} = {5A57B76D-C7E1-48A4-AB10-23E804590287} + {09CD7984-6CB9-42BC-9A9E-31FA1C21697F} = {91E8E5EA-8098-4929-9E87-6C2D55F35A02} + {C40F7294-C2FD-4642-A35E-6AEE7DE9F2A1} = {C86F60F9-BBC1-4554-A3B0-D553F9C157A8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D0B64EB-14E9-4013-AA33-33716704909B} diff --git a/editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj b/editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj new file mode 100644 index 0000000..7ebf669 --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.Web.Server/MBS.Editor.Web.Server.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs b/editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs new file mode 100644 index 0000000..6154355 --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.Web.Server/Program.cs @@ -0,0 +1,102 @@ +using System.Text.Json.Nodes; +using System.Xml; +using MBS.Core; +using MBS.Editor.Web.Pages; +using MBS.Web; + +public class Program : WebApplication +{ + protected override int DefaultPort => 54920; + + public Program() + { + Commands.Add(new Command("File", "_File", new CommandItem[] + { + new CommandReferenceCommandItem("FileOpen") + })); + Commands.Add(new Command("FileOpen", "_Open")); + } + + protected override WebServer CreateWebServer() + { + WebServer ws = base.CreateWebServer(); + ws.Routes.Add(new WebRoute("/", new WebHandler(delegate(WebContext ctx) + { + ctx.Response.ContentType = "application/xhtml+xml"; + + MainPage page = new MainPage(); + page.Title = "MBS Editor"; + + MBS.Web.UI.WebControls.CommandBar toolbar = new MBS.Web.UI.WebControls.CommandBar(); + toolbar.Items.Add(new CommandReferenceCommandItem("FileOpen")); + page.Controls.Add(toolbar); + + XmlWriter sw = XmlWriter.Create(ctx.Response.Stream); + page.Render(sw); + sw.Close(); + }))); + ws.Routes.Add(new WebRoute("/api/local/browse", new WebHandler(delegate(WebContext ctx) + { + StreamWriter sw = new StreamWriter(ctx.Response.Stream); + + string relativePath = ""; + if (ctx.Request.Query.ContainsKey("path")) + { + List oPath = ctx.Request.Query["path"]; + if (oPath.Count > 0) + { + relativePath = oPath[0]; + } + } + + JsonObject json = new JsonObject(); + + string homeDirectory = "/home/beckermj/Documents"; + string absolutePath = String.Join("/", new string[] { homeDirectory, relativePath }); + if (!System.IO.Directory.Exists(absolutePath)) + { + json.Add("result", "failure"); + json.Add("message", "path not found"); + + sw.Write(json.ToJsonString()); + sw.Close(); + return; + } + + json.Add("result", "success"); + + JsonArray ary = new JsonArray(); + string[] files = System.IO.Directory.GetFiles(absolutePath, "*", SearchOption.TopDirectoryOnly); + string[] folders = System.IO.Directory.GetDirectories(absolutePath, "*", SearchOption.TopDirectoryOnly); + foreach (string file in folders) + { + JsonObject o = new JsonObject(); + string title = System.IO.Path.GetFileName(file); + o.Add("title", title); + o.Add("type", "folder"); + o.Add("path", file); + ary.Add(o); + } + foreach (string file in files) + { + JsonObject o = new JsonObject(); + string title = System.IO.Path.GetFileName(file); + o.Add("title", title); + o.Add("type", "file"); + o.Add("path", file); + ary.Add(o); + } + + json.Add("items", ary); + + sw.Write(json.ToJsonString()); + sw.Close(); + }))); + return ws; + } + + public static void Main(string[] args) + { + (new Program()).Start(); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt b/editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt new file mode 100644 index 0000000..5232bbe --- /dev/null +++ b/editor-dotnet/src/app/MBS.Editor.Web.Server/TODO.txt @@ -0,0 +1,12 @@ +This is MBS Editor Web Server. + +example URL: + +Edit existing registered Document: +https://localhost:54920/doc/61b30ad3c7754ae2bc7abf949ee83289/edit + +Create document and register it: +https://localhost:54920/doc/new + +- redirects to /doc/.../edit when document is created + diff --git a/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj b/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj index c84f015..76d4962 100644 --- a/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj +++ b/editor-dotnet/src/app/MBS.Editor/MBS.Editor.csproj @@ -3,8 +3,8 @@ - + diff --git a/editor-dotnet/src/install-engines.sh b/editor-dotnet/src/install-engines.sh new file mode 100755 index 0000000..9617e51 --- /dev/null +++ b/editor-dotnet/src/install-engines.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +CONFIDENCE=Debug +NET_VERSION=net8.0 + +if [ ! -d app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/engines ]; then + + mkdir app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/engines + +fi + +for dir in ../../desktop-framework-dotnet/desktop-framework-dotnet/src/engines/* ; do + + echo "Building $dir" + + pushd $dir + dotnet build + popd + + echo "Copying $dir" + cp $dir/bin/$CONFIDENCE/$NET_VERSION/*.dll app/MBS.Editor/bin/$CONFIDENCE/$NET_VERSION/engines + +done diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs index 566e0ea..24b2df8 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormat.cs @@ -1,22 +1,73 @@ +// +// DataFormat.cs - translates ObjectModel to serialized data in a particular format +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2024 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + namespace MBS.Editor.Core; public abstract class DataFormat { + protected virtual void BeforeLoadInternal(Stack objectModels) + { + } + protected virtual void AfterLoadInternal(Stack objectModels) + { + } + public void Load(ObjectModel objectModel, Stream stream) { - LoadInternal(objectModel, stream); + Stack stack = new Stack(); + stack.Push(objectModel); + BeforeLoadInternal(stack); + + ObjectModel omb = stack.Pop(); + + LoadInternal(omb, stream); + + stack.Push(omb); + AfterLoadInternal(stack); } protected abstract void LoadInternal(ObjectModel objectModel, Stream stream); + protected virtual void BeforeSaveInternal(Stack objectModels) + { + } + protected virtual void AfterSaveInternal(Stack objectModels) + { + } + public void Save(ObjectModel objectModel, Stream stream) { - SaveInternal(objectModel, stream); + Stack stack = new Stack(); + stack.Push(objectModel); + BeforeSaveInternal(stack); + + ObjectModel omb = stack.Pop(); + + SaveInternal(omb, stream); + + stack.Push(omb); + AfterSaveInternal(stack); } protected abstract void SaveInternal(ObjectModel objectModel, Stream stream); - - public static T FromType() where T : DataFormat, new() { T objectModel = new T(); diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs new file mode 100644 index 0000000..281cf7b --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/Chunked/ChunkedDataFormat.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Core; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.DataFormats.FileSystem.Chunked; + +public class ChunkedDataFormat : DataFormat +{ + protected virtual int KeyLength { get; } = 4; + + ///

+ /// Gets or sets the endianness of multibyte values. For IFF and AIFF, set to + /// . For RIFF, set to . + /// + /// + public Endianness Endianness { get; set; } = Endianness.LittleEndian; + + protected virtual int ChunkAlignment { get; } = 0; + protected virtual bool IsGroupChunk(string name) + { + return false; + } + + private void ReadChunk(Reader reader, IChunkContainer parent) + { + string chunkName = reader.ReadFixedLengthString(4); + int chunkLength = reader.ReadInt32(); + + if (IsGroupChunk(chunkName)) + { + string typeName = reader.ReadFixedLengthString(4); + + // is group chunk + GroupChunk folder = new GroupChunk(chunkName, typeName); + + while (!reader.BaseStream.EndOfStream()) + { + ReadChunk(reader, folder); + } + + parent.Chunks.Add(folder); + } + else + { + long offset = reader.BaseStream.Position; + + DataChunk file = new DataChunk(chunkName); + file.Source = new EmbeddedFileSource(reader.BaseStream, offset, chunkLength); + parent.Chunks.Add(file); + + // CMAP=color map, 768 bytes, 3x256 colors, rgb + + reader.BaseStream.Seek(chunkLength, SeekOrigin.Current); + reader.Align(ChunkAlignment); + } + } + + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModel); + + Reader reader = new Reader(stream); + reader.Endianness = Endianness; + while (!reader.BaseStream.EndOfStream()) + { + ReadChunk(reader, chunked); + } + } + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModel); + + Writer writer = new Writer(stream); + writer.Endianness = Endianness; + foreach (Chunk chunk in chunked.Chunks) + { + WriteChunk(writer, chunk); + } + } + + private void WriteChunk(Writer writer, Chunk chunk) + { + writer.WriteFixedLengthString(chunk.Name, KeyLength); + writer.WriteInt32(CalculateChunkSize(chunk)); + + if (chunk is GroupChunk g) + { + writer.WriteFixedLengthString(g.TypeName, KeyLength); + foreach (Chunk chunk2 in g.Chunks) + { + WriteChunk(writer, chunk2); + } + } + else if (chunk is DataChunk d) + { + if (d.Source != null) + { + writer.WriteBytes(d.Source.GetData()); + } + } + } + + private int CalculateChunkSize(Chunk chunk) + { + int sz = 0; + if (chunk is GroupChunk g) + { + sz += 4; + foreach (Chunk c in g.Chunks) + { + sz += CalculateChunkSize(c); + } + } + else if (chunk is DataChunk d) + { + sz = 0; + if (d.Source != null) + { + sz = (int)d.Source.Length; + } + } + return sz; + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs new file mode 100644 index 0000000..05a249a --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/IFFDataFormat.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Core; +using MBS.Editor.Core.DataFormats.FileSystem.Chunked; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; + +namespace MBS.Editor.Core.DataFormats.FileSystem.IFF; + +public class IFFDataFormat : ChunkedDataFormat +{ + public List GroupChunkNames { get; } = new List(); + public IFFDataFormat() + { + Endianness = Endianness.BigEndian; + GroupChunkNames.Add("FORM"); + GroupChunkNames.Add("LIST"); + GroupChunkNames.Add("CAT"); + } + + protected override bool IsGroupChunk(string name) + { + return GroupChunkNames.Contains(name); + } + + protected override int ChunkAlignment => 2; +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs new file mode 100644 index 0000000..d040eab --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/FileSystem/IFF/RIFFDataFormat.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.DataFormats.FileSystem.IFF; + +public class RIFFDataFormat : IFFDataFormat +{ + public RIFFDataFormat() + { + GroupChunkNames.Add("RIFF"); + Endianness = IO.Endianness.LittleEndian; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs new file mode 100644 index 0000000..d66b347 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormat.cs @@ -0,0 +1,205 @@ + +using System.Formats.Asn1; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Text.RegularExpressions; +using MBS.Core.Settings; + +namespace MBS.Editor.Core; + +public class INIDataFormat : DataFormat +{ + public char CommentPrefix { get; set; } = ';'; + + private static DataFormatMetadata? _dfr = null; + public static DataFormatMetadata Metadata + { + get + { + if (_dfr == null) + { + _dfr = new DataFormatMetadata(); + + SettingsGroup sg = new SettingsGroup("General", new Setting[] + { + new TextSetting("CommentPrefix", "Comment Prefix", ";") + }); + _dfr.ExportSettings.SettingsGroups.Add(sg); + } + return _dfr; + } + } + + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + PropertyListObjectModel? plom = objectModel as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), objectModel?.GetType()); + + IPropertyListContainer currentGroup = plom; + INIDataFormatToken insideToken = INIDataFormatToken.None; + StringBuilder currentString = new StringBuilder(); + bool escaped = false; + + string currentPropertyName = ""; + string currentGroupName = ""; + + StreamReader sr = new StreamReader(stream); + while (!sr.EndOfStream) + { + int val = sr.Read(); + if (val == -1) + break; + + char c = (char)val; + if (insideToken == INIDataFormatToken.Group) + { + if (c == ']') + { + insideToken = INIDataFormatToken.None; + } + else + { + currentGroupName += c; + } + continue; + } + + if (c == CommentPrefix) + { + if (insideToken == INIDataFormatToken.None) + { + insideToken = INIDataFormatToken.Comment; + } + else + { + currentString.Append(c); + } + } + else if (c == '"') + { + if (insideToken == INIDataFormatToken.None) + { + insideToken = INIDataFormatToken.String; + } + else if (insideToken == INIDataFormatToken.String && !escaped) + { + insideToken = INIDataFormatToken.None; + } + else + { + currentString.Append(c); + } + } + else if (c == '\\') + { + if (insideToken == INIDataFormatToken.None || insideToken == INIDataFormatToken.Comment) + { + currentString.Append(c); + } + else if (insideToken == INIDataFormatToken.String) + { + if (!escaped) + { + escaped = true; + } + else + { + currentString.Append(c); + } + } + } + else if (c == '\n') + { + if (currentGroupName != "") + { + PropertyListGroup group = new PropertyListGroup(currentGroupName); + currentGroupName = ""; + + plom.Items.Add(group); + currentGroup = group; + } + else + { + PropertyListProperty property = new PropertyListProperty(currentPropertyName, currentString.ToString()); + currentString.Clear(); + + currentGroup.Items.Add(property); + } + } + else if (c == '=') + { + if (insideToken == INIDataFormatToken.None) + { + currentPropertyName = currentString.ToString(); + currentString.Clear(); + } + else + { + currentString.Append(c); + } + } + else if (c == '[') + { + if (insideToken == INIDataFormatToken.None) + { + insideToken = INIDataFormatToken.Group; + continue; + } + else + { + currentString.Append(c); + } + } + else if (c == ']') + { + if (insideToken != INIDataFormatToken.None) + { + currentString.Append(c); + } + } + else + { + currentString.Append(c); + } + } + } + + private void WriteItem(StreamWriter sw, PropertyListItem item) + { + if (item is PropertyListProperty p) + { + sw.WriteLine(String.Format("{0}={1}", p.Name, p.Value)); + } + else if (item is PropertyListGroup g) + { + sw.WriteLine(String.Format("[{0}]", g.Name)); + foreach (PropertyListItem item2 in g.Items) + { + WriteItem(sw, item2); + } + } + else if (item is PropertyListComment c) + { + string[] lines = c.Value.Split(System.Environment.NewLine); + foreach (string line in lines) + { + sw.WriteLine(String.Format("{0} {1}", CommentPrefix, line)); + } + } + } + + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + PropertyListObjectModel? plom = objectModel as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), objectModel?.GetType()); + + StreamWriter sw = new StreamWriter(stream); + foreach (PropertyListItem item in plom.Items) + { + WriteItem(sw, item); + } + sw.Flush(); + } +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs new file mode 100644 index 0000000..845dd01 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/DataFormats/PropertyList/INI/INIDataFormatToken.cs @@ -0,0 +1,9 @@ +namespace MBS.Editor.Core; + +public enum INIDataFormatToken +{ + None = 0, + Comment, + String, + Group +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs index 305afe3..f925c44 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs @@ -609,7 +609,7 @@ public class Reader : ReaderWriterBase public int ReadInt24() { byte[] buffer = ReadBytes((uint)3); - byte[] _buffer = new byte[3]; + byte[] _buffer = new byte[4]; if (base.Endianness == Endianness.LittleEndian) { _buffer[0] = buffer[0]; @@ -1798,31 +1798,6 @@ public class Reader : ReaderWriterBase return line; } } - - /// - /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. - /// - public void Close() - { - BaseStream.Close(); - } - - /// - /// Aligns the to the specified number of bytes. If the current - /// position of the is not a multiple of the specified number of bytes, - /// the position will be increased by the amount of bytes necessary to bring it to the - /// aligned position. - /// - /// The number of bytes on which to align the . - /// Any additional padding bytes that should be included after aligning to the specified boundary. - public void Align(int alignTo, int extraPadding = 0) - { - long paddingCount = ((alignTo - (BaseStream.Position % alignTo)) % alignTo); - paddingCount += extraPadding; - - if (BaseStream.Position + paddingCount < BaseStream.Length) - BaseStream.Position += paddingCount; - } public string ReadStringUntilAny(char[] anyOf) { diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs b/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs index 74aa39d..7ee3cc5 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs @@ -66,14 +66,25 @@ public class ReaderWriterBase long paddingCount = ((alignTo - (_st.Position % alignTo)) % alignTo); paddingCount += extraPadding; - if (_st.Position == _st.Length) + if (paddingCount > 0) { - byte[] buffer = new byte[paddingCount]; - _st.Write(buffer, 0, buffer.Length); - } - else - { - _st.Position += paddingCount; + if (_st.Position == _st.Length) + { + byte[] buffer = new byte[paddingCount]; + _st.Write(buffer, 0, buffer.Length); + } + else + { + _st.Position += paddingCount; + } } } + + /// + /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. + /// + public void Close() + { + BaseStream.Close(); + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs index c342e97..35bf2eb 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModel.cs @@ -32,5 +32,14 @@ public class ObjectModel } return objectModel; } - + + public static T CastOrThrow(ObjectModel? objectModel) where T : ObjectModel + { + T om = objectModel as T; + if (om == null) + { + throw new ObjectModelNotSupportedException(typeof(T), objectModel?.GetType()); + } + return om; + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs index 80e9a03..a1334e7 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModelNotSupportedException.cs @@ -1,3 +1,5 @@ +using MBS.Core; + namespace MBS.Editor.Core; public class ObjectModelNotSupportedException : Exception @@ -29,9 +31,19 @@ public class ObjectModelNotSupportedException : Exception public Type? ExpectedObjectModelType { get; } public Type? ActualObjectModelType { get; } - public ObjectModelNotSupportedException(string? message, Type expectedObjectModelType, Type actualObjectModelType) + private static Dictionary __c1(Type expected, Type actual) { + Dictionary dict = new Dictionary(); + dict["expected"] = expected.FullName; + dict["actual"] = actual.FullName; + return dict; + } + public ObjectModelNotSupportedException(Type expectedObjectModelType, Type? actualObjectModelType) : this("The object model is not supported (expected $(expected), got $(actual))", expectedObjectModelType, actualObjectModelType) { } + public ObjectModelNotSupportedException(string? message, Type expectedObjectModelType, Type? actualObjectModelType) : base(StringExtensions.Format(message, __c1(expectedObjectModelType, actualObjectModelType))) + { + ExpectedObjectModelType = expectedObjectModelType; + ActualObjectModelType = actualObjectModelType; } } \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs new file mode 100644 index 0000000..1808ca2 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/Chunk.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public abstract class Chunk +{ + public class ChunkCollection + : System.Collections.ObjectModel.Collection + { + + public Chunk? this[string name] + { + get + { + foreach (Chunk item in this) + { + if (item.Name == name) + return item; + } + return null; + } + } + + public Chunk? this[string name, string typeName] + { + get + { + foreach (Chunk item in this) + { + if (item is GroupChunk g) + { + if (g.Name == name && g.TypeName == typeName) + return g; + } + } + return null; + } + } + } + + public Chunk(string name) + { + Name = name; + } + public string Name { get; set; } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs new file mode 100644 index 0000000..3dff116 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/ChunkedObjectModel.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public class ChunkedObjectModel : ObjectModel, IChunkContainer +{ + public Chunk.ChunkCollection Chunks { get; } = new Chunk.ChunkCollection(); +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs new file mode 100644 index 0000000..a97ffd7 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/DataChunk.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Editor.Core.ObjectModels.FileSystem; + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public class DataChunk : Chunk +{ + public DataChunk(string name) : base(name) + { + } + public FileSource? Source { get; set; } = null; +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs new file mode 100644 index 0000000..c0b67e0 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/GroupChunk.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public class GroupChunk : Chunk, IChunkContainer +{ + public GroupChunk(string name, string typeName) : base(name) + { + TypeName = typeName; + } + public string TypeName { get; set; } + + public Chunk.ChunkCollection Chunks { get; } = new Chunk.ChunkCollection(); +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs new file mode 100644 index 0000000..4e5aa4d --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/Chunked/IChunkContainer.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Core.ObjectModels.Chunked; + +public interface IChunkContainer +{ + Chunk.ChunkCollection Chunks { get; } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs index 34cd688..c9da54b 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSource.cs @@ -1,3 +1,4 @@ + namespace MBS.Editor.Core.ObjectModels.FileSystem; public abstract class FileSource @@ -22,4 +23,11 @@ public abstract class FileSource */ return msInput.ToArray(); } + + public Stream GetStream() + { + byte[] data = GetData(); + MemoryStream msInput = new MemoryStream(data); + return msInput; + } } \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs index 9327a10..c4a08db 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSources/EmbeddedFileSource.cs @@ -13,7 +13,7 @@ public class EmbeddedFileSource : FileSource } protected override byte[] GetDataInternal(long offset, long length) { - if (Offset + offset + length >= Stream.Length) + if (Offset + offset + length > Stream.Length) { throw new ArgumentOutOfRangeException("embedded file offset + requested offset + requested length extends past the actual length of the underlying stream"); } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs index 38cfef2..39c4c41 100644 --- a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/FileSystem/FileSystemItemCollection.cs @@ -178,4 +178,14 @@ public class FileSystemItemCollection } return list.ToArray(); } + + public T? GetChild(string v) where T : FileSystemItem + { + FileSystemItem? item = this[v]; + if (item is T) + { + return (T)item; + } + return null; + } } diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs new file mode 100644 index 0000000..932fe29 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/IPropertyListContainer.cs @@ -0,0 +1,9 @@ +namespace MBS.Editor.Core; + +public interface IPropertyListContainer +{ + + PropertyListItem.PropertyListItemCollection Items { get; } + IPropertyListContainer? Parent { get; } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs new file mode 100644 index 0000000..06c20d3 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListComment.cs @@ -0,0 +1,6 @@ +namespace MBS.Editor.Core; + +public class PropertyListComment : PropertyListItem +{ + public string Value { get; set; } = ""; +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs new file mode 100644 index 0000000..2fceee0 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListGroup.cs @@ -0,0 +1,14 @@ +namespace MBS.Editor.Core; + +public class PropertyListGroup : PropertyListItem, IPropertyListContainer +{ + + public PropertyListItem.PropertyListItemCollection Items { get; } + + public PropertyListGroup(string name) + { + Name = name; + Items = new PropertyListItemCollection(this); + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs new file mode 100644 index 0000000..c62338e --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListItem.cs @@ -0,0 +1,58 @@ +namespace MBS.Editor.Core; + +public abstract class PropertyListItem +{ + + public class PropertyListItemCollection + : System.Collections.ObjectModel.Collection + { + private IPropertyListContainer _Parent = null; + + public PropertyListItemCollection(IPropertyListContainer parent) + { + _Parent = parent; + } + + protected override void ClearItems() + { + foreach (PropertyListItem item in this) + { + item.Parent = null; + } + base.ClearItems(); + } + protected override void InsertItem(int index, PropertyListItem item) + { + base.InsertItem(index, item); + item.Parent = _Parent; + } + protected override void RemoveItem(int index) + { + this[index].Parent = null; + base.RemoveItem(index); + } + + } + + public string Name { get; set; } = ""; + public IPropertyListContainer? Parent { get; internal set; } + + public PropertyListObjectModel? ParentObjectModel + + { + get + { + IPropertyListContainer? parent = Parent; + while (parent != null) + { + if (parent is PropertyListObjectModel) + { + return (parent as PropertyListObjectModel); + } + parent = parent.Parent; + } + return null; + } + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs new file mode 100644 index 0000000..d0f7f43 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListObjectModel.cs @@ -0,0 +1,18 @@ +using MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +namespace MBS.Editor.Core; + +public class PropertyListObjectModel : ObjectModel, IPropertyListContainer +{ + + public PropertyListItem.PropertyListItemCollection Items { get; } + public PropertyListSchema? Schema { get; set; } = null; + + public IPropertyListContainer? Parent => null; + + public PropertyListObjectModel() + { + Items = new PropertyListItem.PropertyListItemCollection(this); + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs new file mode 100644 index 0000000..4e2ae02 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/PropertyListProperty.cs @@ -0,0 +1,41 @@ +using MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +namespace MBS.Editor.Core; + +public class PropertyListProperty : PropertyListItem +{ + + private object? _Value = null; + public object? Value + { + get { return _Value; } + set { _Value = value; } + } + + public T? GetValueAs() + { + PropertyListObjectModel? plom = ParentObjectModel; + if (plom != null) + { + PropertyListSchema? schema = plom.Schema; + if (schema != null) + { + foreach (PropertyDefinition def in schema.PropertyDefinitions) + { + if (def.PropertyName == Name) + { + //return def.ConvertTo(def.DataType, Value); + } + } + } + } + return default(T); + } + + public PropertyListProperty(string name, object value) + { + Name = name; + Value = value; + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs new file mode 100644 index 0000000..4c0d881 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDataTypeDefinition.cs @@ -0,0 +1,27 @@ +namespace MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +public class PropertyValueConverter +{ + public class PropertyValueConverterCollection + : System.Collections.ObjectModel.Collection + + { + + } +} +public class PropertyValueConverter : PropertyValueConverter +{ + private Func __convertTo = null; + private Func __convertFrom = null; + public PropertyValueConverter(Func convertTo, Func convertFrom) + { + __convertTo = convertTo; + __convertFrom = convertFrom; + } + + public TOutput? ConvertTo(TInput value) + { + return default; + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs new file mode 100644 index 0000000..c1e0bfc --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyDefinition.cs @@ -0,0 +1,41 @@ + +using System.Diagnostics.Contracts; +using System.Text.Json.Serialization; +using MBS.Core.Collections.Generic; + +namespace MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +public class PropertyDefinition +{ + public class PropertyDefinitionCollection + : System.Collections.ObjectModel.Collection + { + + } + + public string PropertyName { get; } + public Type DataType { get; } + public PropertyValueConverter.PropertyValueConverterCollection Converters { get; } = new PropertyValueConverter.PropertyValueConverterCollection(); + + public PropertyDefinition(string propertyName, string displayTitle, Type dataType, PropertyValueConverter[]? valueConverters = null) + { + PropertyName = propertyName; + DataType = dataType; + if (valueConverters != null) + { + Converters.AddRange(valueConverters); + } + } + + public TTo ConvertTo(TFrom value) + { + foreach (PropertyValueConverter converter in Converters) + { + if (converter is PropertyValueConverter cvt) + { + return cvt.ConvertTo(value); + } + } + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs new file mode 100644 index 0000000..01422a9 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Core/ObjectModels/PropertyList/Schema/PropertyListSchema.cs @@ -0,0 +1,14 @@ +using MBS.Core.Collections.Generic; + +namespace MBS.Editor.Core.ObjectModels.PropertyList.Schema; + +public class PropertyListSchema +{ + public PropertyDefinition.PropertyDefinitionCollection PropertyDefinitions { get; } = new PropertyDefinition.PropertyDefinitionCollection(); + + public PropertyListSchema(PropertyDefinition[] propertyDefinitions) + { + PropertyDefinitions.AddRange(propertyDefinitions); + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs b/editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs new file mode 100644 index 0000000..b7c91ea --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Testing/EmbeddedResourceTest.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; + +namespace MBS.Editor.Testing; + +public class EmbeddedResourceTest +{ + private System.Collections.Generic.Dictionary testDataStreams = new System.Collections.Generic.Dictionary(); + + [SetUp] + public void SetUp() + { + InitializeResources(); + } + + protected void InitializeResources() + { + MBS.Core.Reflection.ManifestResourceStream[] strms = MBS.Core.Reflection.ManifestResourceStream.GetManifestResourceStreamsForAssembly(this.GetType().Assembly); + for (int i = 0; i < strms.Length; i++) + { + testDataStreams[strms[i].Name] = strms[i].Stream; + } + } + + protected Stream? CreateResourceStream(string name) + { + if (TryCreateResourceStream(name, out Stream? st)) + { + return st; + } + return null; + } + + protected bool TryCreateResourceStream(string name, out System.IO.Stream? st) + { + st = null; + if (testDataStreams.ContainsKey(name)) + { + System.IO.Stream TEST_STREAM = testDataStreams[name]; + st = TEST_STREAM; + return true; + } + else + { + Console.Error.WriteLine("test data stream not found: '{0}'", name); + } + return false; + } + + protected void SampleStreamTest(string streamName) + { + if (TryCreateResourceStream(streamName, out System.IO.Stream? st)) + { + SampleStreamTest(st); + } + else + { + Assert.Ignore(); + } + } + + protected void SampleStreamTest(Stream st) + { + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj b/editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj new file mode 100644 index 0000000..e5eafa1 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Testing/MBS.Editor.Testing.csproj @@ -0,0 +1,19 @@ + + + + + + + + + + + + net8.0 + enable + enable + false + false + + + diff --git a/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs index f9f2537..033e05e 100644 --- a/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs +++ b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorApplication.cs @@ -1,14 +1,38 @@ namespace MBS.Editor.UserInterface; +using System; +using MBS.Core; using MBS.Desktop; +using MBS.Desktop.Controls; public class EditorApplication : DesktopApplication { + protected override void OnStartup(EventArgs e) + { + base.OnStartup(e); - protected override int StartInternal() - { - return 0; - } + Console.WriteLine("editor: OnStartup"); + + this.Commands.Add(new Command("FileExit", "E_xit")); + this.Commands.Add(new Command("File", "_File", new CommandItem[] + { + new CommandReferenceCommandItem("FileExit") + })); + + this.AttachCommandEventHandler("FileExit", delegate (object sender, CommandEventArgs e) + { + this.Stop(); + }); + } + + protected override void OnActivated(ApplicationActivatedEventArgs e) + { + base.OnActivated(e); + + Console.WriteLine("editor: OnActivated"); + + EditorWindow window = new EditorWindow(); + window.Show(); + } - } diff --git a/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs new file mode 100644 index 0000000..3d56548 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.UserInterface/EditorWindow.cs @@ -0,0 +1,20 @@ +namespace MBS.Editor.UserInterface; + +using System; +using MBS.Core; +using MBS.Desktop; +using MBS.Desktop.Controls; + +// [ContainerLayout(typeof(EditorWindow), "MBS.Editor.UserInterface.EditorWindow.glade")] +public class EditorWindow : MainWindow +{ + + public EditorWindow() + { + Title = "MBS Editor"; + DefaultSize = new Core.Drawing.Dimension2D(1000, 700); + + Controls.Add(new Label("Strong Bad!") { UseMarkup = true }); + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj b/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj index dd82f9d..4e87b11 100644 --- a/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj +++ b/editor-dotnet/src/lib/MBS.Editor.UserInterface/MBS.Editor.UserInterface.csproj @@ -2,7 +2,7 @@ - + diff --git a/editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs b/editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs new file mode 100644 index 0000000..3ee6c62 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Web/Controls/PageHeader.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Web.UI; + +namespace MBS.Editor.Web.Controls; + +public class PageHeader : WebControl +{ + protected override string TagName => "div"; + protected override IEnumerable GetStyleClasses() + { + return new string[] { "uwt-page-header" }; + } + +} diff --git a/editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj b/editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj new file mode 100644 index 0000000..c987729 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Web/MBS.Editor.Web.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs b/editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs new file mode 100644 index 0000000..b726c65 --- /dev/null +++ b/editor-dotnet/src/lib/MBS.Editor.Web/Pages/MainPage.cs @@ -0,0 +1,123 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Web; +using MBS.Web.UI; + +using MBS.Editor.Web.Controls; +using MBS.Web.UI.HtmlControls; +using System.Text; +using MBS.Core.Drawing; +using MBS.Web.UI.WebControls; +using MBS.Core; + +namespace MBS.Editor.Web.Pages; + +public class MainPage : WebPage +{ + private class WebStyleSheet2 + { + + + } + + + public MainPage() + { + Dictionary> dict = new Dictionary>(); + /* + WebStyleSheet2 wss = new WebStyleSheet2(); + + WebStyleSheetSelector selector = WebStyleSheetSelector.Parse("div.uwt-page-header"); + + wss.Selectors.Add("div", "uwt-page-header"); + */ + + dict["div.uwt-page-header"] = new Dictionary(); + dict["div.uwt-page-header"]["border-bottom"] = new object[] { Measurement.Parse("1px"), "solid", Color.FromString("#E0E0E0") }; + + StyleSheets.Add(WebStyleSheet.FromContent("text/css", FormatCss(dict))); + } + + protected override IEnumerable GetHeaderControls() + { + return new Control[] + { + + + }; + } + + private string FormatCss(Dictionary> dict) + { + StringBuilder sb = new StringBuilder(); + foreach (KeyValuePair> kvp in dict) + { + sb.Append(kvp.Key); + sb.Append(" { "); + foreach (KeyValuePair kvp2 in kvp.Value) + { + sb.Append(kvp2.Key); + sb.Append(": "); + + if (kvp2.Value is Color) + { + sb.Append(((Color)kvp2.Value).ToHexadecimalHTML()); + } + else if (kvp2.Value is string) + { + sb.Append("\""); + sb.Append(kvp2.Value); + sb.Append("\""); + } + else if (kvp2.Value is object[]) + { + object[] objs = (object[])kvp2.Value; + for (int i = 0; i < objs.Length; i++) + { + sb.Append(objs[i]); + if (i < objs.Length - 1) + { + sb.Append(' '); + } + } + } + else + { + sb.Append(kvp2.Value); + } + } + sb.Append(" } "); + } + return sb.ToString(); + } + + protected override IEnumerable GetBodyControls() + { + CommandBar cb = new CommandBar(); + cb.ClientId = "mainToolbar"; + cb.Items.Add(new CommandReferenceCommandItem("FileOpen")); + + return new Control[] + { + new PageHeader(), + cb + }; + } + + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs new file mode 100644 index 0000000..63247f8 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/Class1.cs @@ -0,0 +1,6 @@ +namespace MBS.Editor.Plugins.Kronosaur; + +public class Class1 +{ + +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Kronosaur/MBS.Editor.Plugins.Kronosaur.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs new file mode 100644 index 0000000..1b354d2 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/Mechanic/MEKDataFormat.cs @@ -0,0 +1,70 @@ +using MBS.Editor.Core; +using MBS.Editor.Core.ObjectModels.PropertyList.Schema; +using MBS.Editor.Plugins.Mekada.DataFormats.PropertyList; + +namespace MBS.Editor.Plugins.Mekada.DataFormats.Mechanic; + +public class MEKDataFormat : MEKBaseDataFormat +{ + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + + PropertyListObjectModel plom = new PropertyListObjectModel(); + plom.Schema = new PropertyListSchema(new PropertyDefinition[] + { + new PropertyDefinition("1403", "Money", typeof(float)), + new PropertyDefinition("1405", "Total Time Played", typeof(int), new PropertyValueConverter[] + { + new PropertyValueConverter(delegate (int value) + { + TimeSpan ts = new TimeSpan(0, 0, value); + return ts; + }, + delegate (TimeSpan value) + { + int seconds = (value.Hours * 3600) + (value.Minutes * 60) + (value.Seconds); + return seconds; + }) + }) + }); + objectModels.Push(plom); + } + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + PropertyListObjectModel? plom = objectModels.Pop() as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), plom?.GetType()); + + Console.WriteLine("{ "); + foreach (PropertyListItem item in plom.Items) + { + RenderItem(item); + } + Console.WriteLine(" }"); + + } + + private void RenderItem(PropertyListItem item) + { + if (item is PropertyListGroup grp) + { + Console.WriteLine("\"" + grp.Name + "\": { "); + foreach (PropertyListItem item2 in grp.Items) + { + RenderItem(item2); + if (grp.Items.IndexOf(item2) < grp.Items.Count - 1) + { + Console.Write(", "); + } + } + Console.WriteLine(" }"); + } + else if (item is PropertyListProperty prop) + { + Console.Write("\"" + item.Name + "\": \"" + prop.Value.ToString() + "\""); + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs new file mode 100644 index 0000000..387bffb --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKBaseDataFormat.cs @@ -0,0 +1,92 @@ +using System.Text.RegularExpressions; +using MBS.Editor.Core; +using MBS.Editor.Core.IO; + +namespace MBS.Editor.Plugins.Mekada.DataFormats.PropertyList; + +public class MEKBaseDataFormat : DataFormat +{ + protected override void LoadInternal(ObjectModel objectModel, Stream stream) + { + PropertyListObjectModel? plom = objectModel as PropertyListObjectModel; + if (plom == null) + throw new ObjectModelNotSupportedException(typeof(PropertyListObjectModel), objectModel?.GetType()); + + Reader r = new Reader(stream); + + while (true) + { + PropertyListItem item = ReadItem(r, plom); + if (item == null) + break; + + plom.Items.Add(item); + } + } + + private PropertyListItem? ReadItem(Reader r, IPropertyListContainer parent) + { + long start = r.BaseStream.Position; + + ushort index = r.ReadUInt16(); + MEKPropertyType type = (MEKPropertyType) r.ReadUInt16(); + int length = r.ReadInt32(); + if (index == 0 && type == MEKPropertyType.None && length == 0) + return null; + + if (type == MEKPropertyType.Group) + { + PropertyListGroup group = new PropertyListGroup(index.ToString()); + + while (r.BaseStream.Position < length + start) + { + PropertyListItem? item = ReadItem(r, group); + if (item != null) + { + group.Items.Add(item); + } + } + + return group; + } + else if (type == MEKPropertyType.Float) + { + if (length == 4) + { + float value = r.ReadSingle(); + return new PropertyListProperty(index.ToString(), value); + } + else + { + throw new NotImplementedException(); + } + } + else if (type == MEKPropertyType.Int32) + { + if (length == 4) + { + int value = r.ReadInt32(); + return new PropertyListProperty(index.ToString(), value); + } + else + { + throw new NotImplementedException(); + } + } + else if (type == MEKPropertyType.String) + { + string value = r.ReadFixedLengthString(length); + return new PropertyListProperty(index.ToString(), value); + } + else + { + byte[] data = r.ReadBytes(length); + return new PropertyListProperty(index.ToString(), data); + } + } + + protected override void SaveInternal(ObjectModel objectModel, Stream stream) + { + throw new NotImplementedException(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs new file mode 100644 index 0000000..7e2e7f0 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/DataFormats/PropertyList/MEKPropertyType.cs @@ -0,0 +1,10 @@ +namespace MBS.Editor.Plugins.Mekada; + +public enum MEKPropertyType : ushort +{ + None = 0, + Int32 = 8192, + Float = 16384, + String = 24576, + Group = 32768 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj new file mode 100644 index 0000000..d7fe524 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/MBS.Editor.Plugins.Mekada.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs new file mode 100644 index 0000000..bc5692e --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Mekada/ObjectModels/Mechanic/MechanicObjectModel.cs @@ -0,0 +1,11 @@ +using MBS.Editor.Core; + +namespace MBS.Editor.Plugins.Mekada; + +public class MechanicObjectModel : ObjectModel +{ + public string Name { get; set; } = ""; + + public float Money { get; set; } = 0.0f; + +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs new file mode 100644 index 0000000..4f64d69 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommand.cs @@ -0,0 +1,143 @@ +// +// MIDICommand.cs - represents a command in a MIDI synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Represents a command in a MIDI synthesized audio file. +/// +public class MIDICommand +{ + /// + /// Gets or sets the type of the to send. + /// + /// The type of the to send. + public MIDICommandType CommandType + { + get + { + MIDICommandType result; + switch (Command) + { + case 0: + { + result = MIDICommandType.None; + return result; + } + case 8: + { + result = MIDICommandType.NoteOff; + return result; + } + case 9: + { + result = MIDICommandType.NoteOn; + return result; + } + case 10: + { + result = MIDICommandType.KeyAfterTouch; + return result; + } + case 11: + { + result = MIDICommandType.ControlChange; + return result; + } + case 12: + { + result = MIDICommandType.ProgramChange; + return result; + } + case 13: + { + result = MIDICommandType.ChannelAfterTouch; + return result; + } + case 14: + { + result = MIDICommandType.PitchWheelChange; + return result; + } + } + result = MIDICommandType.Unknown; + return result; + } + set + { + switch (value) + { + case MIDICommandType.None: + { + Command = 0; + return; + } + case MIDICommandType.NoteOff: + { + Command = 8; + return; + } + case MIDICommandType.NoteOn: + { + Command = 9; + return; + } + case MIDICommandType.KeyAfterTouch: + { + Command = 10; + return; + } + case MIDICommandType.ControlChange: + { + Command = 11; + return; + } + case MIDICommandType.ProgramChange: + { + Command = 12; + return; + } + case MIDICommandType.ChannelAfterTouch: + { + Command = 13; + return; + } + case MIDICommandType.PitchWheelChange: + { + Command = 14; + return; + } + } + Command = 0; + } + } + + /// + /// Gets or sets the channel on which to send this . + /// + /// The channel on which to send this . + public byte Channel { get; set; } = 0; + /// + /// Gets or sets the value of the to send. + /// + /// The value of the to send. + public byte Command { get; set; } = 0; +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs new file mode 100644 index 0000000..7f3f2b9 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDICommandType.cs @@ -0,0 +1,38 @@ +// +// MIDICommandType.cs - indicates the type of MIDI command +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI command. +/// +public enum MIDICommandType +{ + Unknown = -1, + None, + NoteOff = 8, + NoteOn, + KeyAfterTouch, + ControlChange, + ProgramChange, + ChannelAfterTouch, + PitchWheelChange +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs new file mode 100644 index 0000000..6d24af3 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIDataFormat.cs @@ -0,0 +1,353 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using System.Globalization; +using System.Reflection; +using MBS.Core; +using MBS.Editor.Core; +using MBS.Editor.Core.DataFormats.FileSystem.Chunked; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +public class MIDIDataFormat : ChunkedDataFormat +{ + public MIDIDataFormat() + { + Endianness = Endianness.BigEndian; + } + + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + objectModels.Push(new ChunkedObjectModel()); + } + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModels.Pop()); + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + + System.Collections.Generic.Dictionary notesForNoteNumber = new System.Collections.Generic.Dictionary(); + foreach (Chunk c in chunked.Chunks) + { + if (c.Name == "MThd") + { + Stream s = ((DataChunk)c).Source.GetStream(); + Reader r = new Reader(s); + r.Endianness = Endianness.BigEndian; + + ushort format = r.ReadUInt16(); + ushort trackCount = r.ReadUInt16(); + ushort tickdiv = r.ReadUInt16(); + } + else if (c.Name == "MTrk") + { + int ofs = 0; + Stream s = null; + try + { + s = ((DataChunk)c).Source.GetStream(); + } + catch (Exception ex) + { + return; + } + + Reader r = new Reader(s); + r.Endianness = Endianness.BigEndian; + + SynthesizedAudioTrack track = new SynthesizedAudioTrack(); + + while (!r.BaseStream.EndOfStream()) + { + int deltaTime = r.ReadVariableLengthInt32(); + MIDIEventType commandAndChannel = (MIDIEventType) r.ReadByte(); + byte channel = (byte)((byte)commandAndChannel & (byte)MIDIEventType.MIDIChannelMask); + MIDIEventType command = (MIDIEventType)((byte)commandAndChannel >> 4); + + if (commandAndChannel == MIDIEventType.Meta) + { + // this is special cased because it does not include a channel number + MIDIMetaEventType metaEventType = (MIDIMetaEventType)r.ReadByte(); + int length = r.ReadVariableLengthInt32(); + switch (metaEventType) + { + case MIDIMetaEventType.Text: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + track.Commands.Add(new SynthesizedAudioCommandText(text)); + break; + } + case MIDIMetaEventType.CopyrightNotice: + { + break; + } + case MIDIMetaEventType.SequenceName: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + track.Name = text; + break; + } + case MIDIMetaEventType.ProgramName: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + break; + } + case MIDIMetaEventType.DeviceName: + { + string text = r.ReadFixedLengthString(length).TrimNull(); + break; + } + case MIDIMetaEventType.EndOfTrack: + { + audio.Tracks.Add(track); + break; + } + case MIDIMetaEventType.TimeSignature: + { + byte numerator = r.ReadByte(); + byte denominatorPower = r.ReadByte(); + byte denominator = (byte)Math.Pow(2.0, denominatorPower); + byte ticksPerMetronomeClick = r.ReadByte(); + byte numberOf32ndNotesPerQuarterNote = r.ReadByte(); + track.Commands.Add(new SynthesizedAudioCommandTimeSignature(numerator, denominator, ticksPerMetronomeClick, numberOf32ndNotesPerQuarterNote)); + break; + } + case MIDIMetaEventType.SetTempo: + { + int tempo = (int)r.ReadInt24(); + track.Commands.Add(new SynthesizedAudioCommandTempo((double)tempo)); + break; + } + case MIDIMetaEventType.KeySignature: + { + byte sf = r.ReadByte(); // -7 = 7 flats, -1 = 1 flat, 0 = none (key of C), 1 = + bool minorKey = r.ReadBoolean(); + break; + } + default: + { + Console.WriteLine("ue: MIDI: warning: meta event type {0} ({1}) [{2} bytes] unhandled", metaEventType, (byte)metaEventType, length); + r.BaseStream.Seek(length, SeekOrigin.Current); + break; + } + } + } + else + { + switch (command) + { + case MIDIEventType.ProgramChange: + { + byte programNumber = r.ReadByte(); + break; + } + case MIDIEventType.NoteOn: + { + byte noteNumber = r.ReadByte(); + byte velocity = r.ReadByte(); + + if (!notesForNoteNumber.ContainsKey(noteNumber)) + { + notesForNoteNumber[noteNumber] = new SynthesizedAudioCommandNote(); + } + SynthesizedAudioCommandNote note = notesForNoteNumber[noteNumber]; + note.Frequency = noteNumber; + note.Position = deltaTime + ofs; + note.Intensity = velocity; + ofs += deltaTime; + track.Commands.Add(note); + break; + } + case MIDIEventType.NoteOff: + { + byte noteNumber = r.ReadByte(); + byte velocity = r.ReadByte(); + + if (notesForNoteNumber.ContainsKey(noteNumber)) + { + notesForNoteNumber[noteNumber].Length = deltaTime; + notesForNoteNumber.Remove(noteNumber); + } + break; + } + case MIDIEventType.ControlChange: + { + byte controllerNumber = r.ReadByte(); + byte value = r.ReadByte(); + // track.Commands.Add(new SynthesizedAudioCommandControlChange(controllerNumber, value)); + break; + } + case MIDIEventType.PolyphonicKeyPressureAftertouch: + { + byte noteNumber = r.ReadByte(); + byte pressure = r.ReadByte(); + // track.Commands.Add(new SynthesizedAudioCommandPolyphonicKeyPressureAftertouch(controllerNumber, value)); + break; + } + case MIDIEventType.ChannelPressureAftertouch: + { + byte pressure = r.ReadByte(); + // track.Commands.Add(new SynthesizedAudioCommandChannelPressureAftertouch(controllerNumber, value)); + break; + } + case MIDIEventType.PitchWheelChange: + { + short value = r.ReadInt16(); + // track.Commands.Add(new SynthesizedAudioCommandPitchWheel(value)); + break; + } + } + } + } + } + } + } + + private void WriteMIDIMetaEvent(Writer w, int deltaTime, MIDIMetaEventType metaType) + { + WriteMIDIMetaEvent(w, deltaTime, metaType, (Action?)null); + } + private void WriteMIDIMetaEvent(Writer w, int deltaTime, MIDIMetaEventType metaType, string data) + { + WriteMIDIMetaEvent(w, deltaTime, metaType, delegate (Writer w2) + { + w2.WriteFixedLengthString(data); + }); + } + private void WriteMIDIMetaEvent(Writer w, int deltaTime, MIDIMetaEventType metaType, Action? dataFunc = null) + { + byte[] data; + if (dataFunc == null) + { + data = new byte[0]; + } + else + { + MemoryStream ms = new MemoryStream(); + Writer w2 = new Writer(ms); + + dataFunc(w2); + + ms.Close(); + data = ms.ToArray(); + } + + w.WriteVariableLengthInt32(deltaTime); + w.WriteByte((byte)MIDIEventType.Meta); + w.WriteByte((byte)metaType); + w.WriteVariableLengthInt32(data.Length); + w.WriteBytes(data); + } + + protected override void BeforeSaveInternal(Stack objectModels) + { + base.BeforeSaveInternal(objectModels); + + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + + ChunkedObjectModel chunked = new ChunkedObjectModel(); + + DataChunk MThd = new DataChunk("MThd"); + chunked.Chunks.Add(MThd); + + foreach (SynthesizedAudioTrack track in audio.Tracks) + { + DataChunk MTrk = new DataChunk("MTrk"); + + MemoryStream ms = new MemoryStream(); + Writer w = new Writer(ms); + + if (track.Name != null) + { + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.SequenceName, track.Name); + } + + foreach (SynthesizedAudioCommand command in track.Commands) + { + WriteCommand(w, command); + } + + + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.EndOfTrack); + + ms.Close(); + + MTrk.Source = new ByteArrayFileSource(ms.ToArray()); + chunked.Chunks.Add(MTrk); + } + + objectModels.Push(chunked); + } + + private void WriteCommand(Writer w, SynthesizedAudioCommand command) + { + if (command is SynthesizedAudioCommandNote note) + { + WriteMIDIEvent(w, 0, MIDIEventType.NoteOn, delegate(Writer w) + { + byte noteNumber = (byte)(note.Frequency); + byte velocity = 0; + w.WriteByte(noteNumber); + w.WriteByte(velocity); + }); + } + else if (command is SynthesizedAudioCommandTempo tempo) + { + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.SetTempo, delegate (Writer w) + { + w.WriteInt24((int)(tempo.Tempo)); + }); + } + else if (command is SynthesizedAudioCommandTimeSignature ts) + { + WriteMIDIMetaEvent(w, 0, MIDIMetaEventType.TimeSignature, delegate (Writer w) + { + byte numerator = (byte)ts.Numerator; + w.WriteByte(numerator); + + byte denominatorPower = (byte)Math.Log2((byte)ts.Denominator); + w.WriteByte(denominatorPower); + + byte ticksPerMetronomeClick = (byte)ts.TicksPerMetronomeClick; + w.WriteByte(ticksPerMetronomeClick); + + byte numberOf32ndNotesPerQuarterNote = (byte)ts.NumberOf32ndNotesPerQuarterNote; + w.WriteByte(numberOf32ndNotesPerQuarterNote); + }); + } + } + + private void WriteMIDIEvent(Writer w, int deltaTime, MIDIEventType eventType, Action func) + { + w.WriteVariableLengthInt32(deltaTime); + + byte channel = 0; + MIDIEventType type = MIDIEventType.NoteOn; + byte commandAndChannel = (byte)type; + w.WriteByte(commandAndChannel); + + func(w); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs new file mode 100644 index 0000000..d33e7d4 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIEventType.cs @@ -0,0 +1,46 @@ +// +// MIDIEventType.cs - indicates the type of MIDI event +// +// Author: +// Michael Becker +// +// Copyright (c) 2019 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI event. +/// +public enum MIDIEventType : byte +{ + None, + + MIDIChannelMask = 0x0F, + MIDIEventMask = 0xF0, + + NoteOff = 0x8, + NoteOn = 0x9, + PolyphonicKeyPressureAftertouch = 0xA, + ControlChange = 0xB, + ProgramChange = 0xC, + ChannelPressureAftertouch = 0xD, + PitchWheelChange = 0xE, + + + SysEx = 0xF0, + Escape = 0xF7, + Meta = 0xFF +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs new file mode 100644 index 0000000..9e0d5c9 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIFileFormatType.cs @@ -0,0 +1,46 @@ +// +// MIDIFileFormatType.cs - indicates the type of MIDI file format +// +// Author: +// Michael Becker +// +// Copyright (c) 2019-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI file format. +/// +public enum MIDIFileFormatType : short +{ + /// + /// Single track (format 0). Consists of a header-chunk and a single track-chunk. The single track chunk will contain all the + /// note and tempo information. + /// + SingleTrack = 0, + /// + /// Simultaneous multi-track (format 1). Consists of a header-chunk and one or more track-chunks, with all tracks being played + /// simultaneously. The first track of a Format 1 file is special, and is also known as the 'Tempo Map'. It should contain all + /// meta-events of the types Time Signature, and Set Tempo. The meta-events Sequence/Track Name, Sequence Number, Marker, and + /// SMTPE Offset. should also be on the first track of a Format 1 file. + /// + SimultaneousMultitrack = 1, + /// + /// Independent multi-track (format 2). Consists of a header-chunk and one or more track-chunks, where each track represents an + /// independent sequence. + /// + IndependentMultitrack = 2 +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs new file mode 100644 index 0000000..ee2eb1c --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/MIDI/MIDIMetaEventType.cs @@ -0,0 +1,46 @@ +// +// MIDIMetaEventType.cs - indicates the type of MIDI meta event +// +// Author: +// Michael Becker +// +// Copyright (c) 2019 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; + +/// +/// Indicates the type of MIDI meta event. +/// +public enum MIDIMetaEventType : byte +{ + SequenceNumber = 0x00, + Text = 0x01, + CopyrightNotice = 0x02, + SequenceName = 0x03, + InstrumentName = 0x04, + Lyric = 0x05, + Marker = 0x06, + CuePoint = 0x07, + ProgramName = 0x08, + DeviceName = 0x09, + ChannelPrefix = 0x20, + EndOfTrack = 0x2F, + SetTempo = 0x51, + SMPTEOffset = 0x54, + TimeSignature = 0x58, + KeySignature = 0x59, + SequencerSpecific = 0x7F +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs new file mode 100644 index 0000000..b67fba8 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Audio/Synthesized/RMI/RMIDataFormat.cs @@ -0,0 +1,114 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + + +using MBS.Core; +using MBS.Editor.Core; +using MBS.Editor.Core.DataFormats.FileSystem.IFF; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; +using MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.MIDI; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.RMI; + +public class RMIDataFormat : RIFFDataFormat +{ + + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + objectModels.Push(new ChunkedObjectModel()); + } + // f8ffdcb7-e2ec-40b1-aa8a-beeb5076c1c9 + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + ChunkedObjectModel chunked = ObjectModel.CastOrThrow(objectModels.Pop()); + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + + if (chunked.Chunks.Count == 0) + { + throw new InvalidDataFormatException("RIFF-MIDI document does not contain any RIFF chunks"); + } + + GroupChunk? root = chunked.Chunks["RIFF", "RMID"] as GroupChunk; + if (root == null) + { + throw new InvalidDataFormatException("RIFF-MIDI document does not contain an RIFF-RMID group chunk"); + } + + DataChunk? filData = root.Chunks["data"] as DataChunk; + if (filData != null) + { + byte[]? data = filData.Source?.GetData(); + if (data != null) + { + MIDIDataFormat mid = new MIDIDataFormat(); + Document.Load(audio, mid, filData.Source?.GetStream()); + } + } + + GroupChunk info = (GroupChunk)root.Chunks["LIST", "INFO"]; + if (info != null) + { + DataChunk? filIART = info.Chunks["IART"] as DataChunk; + if (filIART != null) + { + byte[]? data = filIART.Source?.GetData(); + if (data != null) + { + string artist = System.Text.Encoding.UTF8.GetString(data).TrimNull(); + audio.Metadata.Artist = artist; + } + } + DataChunk? filICOP = info.Chunks["ICOP"] as DataChunk; + if (filICOP != null) + { + byte[]? data = filICOP.Source?.GetData(); + if (data != null) + { + string copyright = System.Text.Encoding.UTF8.GetString(data).TrimNull(); + audio.Metadata.Copyright = copyright; + } + } + } + } + + protected override void BeforeSaveInternal(Stack objectModels) + { + base.BeforeSaveInternal(objectModels); + + SynthesizedAudioObjectModel audio = ObjectModel.CastOrThrow(objectModels.Pop()); + ChunkedObjectModel chunked = new ChunkedObjectModel(); + + GroupChunk rmid = new GroupChunk("RIFF", "RMID"); + + MemoryStream ms = new MemoryStream(); + MIDIDataFormat mid = new MIDIDataFormat(); + Document.Save(audio, mid, ms); + + DataChunk data = new DataChunk("data"); + data.Source = new StreamFileSource(ms); + rmid.Chunks.Add(data); + + chunked.Chunks.Add(rmid); + objectModels.Push(chunked); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs new file mode 100644 index 0000000..2abd25c --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMCompression.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; + +/// +/// Choice of compression algorithm applied to the rows of all source and mask planes. +/// +public enum ILBMCompression : byte +{ + None = 0x00, + ByteRun1 = 0x01 +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs new file mode 100644 index 0000000..9787549 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMDataFormat.cs @@ -0,0 +1,164 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Core.Drawing; +using MBS.Editor.Core; +using MBS.Editor.Core.DataFormats.FileSystem.IFF; +using MBS.Editor.Core.IO; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Palette; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Picture; + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; + +public class ILBMDataFormat : IFFDataFormat +{ + public ILBMMasking Masking { get; set; } = ILBMMasking.None; + public ILBMCompression Compression { get; set; } = ILBMCompression.None; + protected override void BeforeLoadInternal(Stack objectModels) + { + base.BeforeLoadInternal(objectModels); + objectModels.Push(new ChunkedObjectModel()); + } + protected override void AfterLoadInternal(Stack objectModels) + { + base.AfterLoadInternal(objectModels); + + ObjectModel om = objectModels.Pop(); + ChunkedObjectModel? fsom = om as ChunkedObjectModel; + if (fsom == null) + throw new ObjectModelNotSupportedException(typeof(ChunkedObjectModel), om.GetType()); + + om = objectModels.Pop(); + PictureObjectModel? pic = om as PictureObjectModel; + if (pic == null) + throw new ObjectModelNotSupportedException(typeof(PictureObjectModel), om.GetType()); + + + GroupChunk? fldFORM = fsom.Chunks[0] as GroupChunk; + if (fldFORM?.Name != "FORM" || fldFORM?.TypeName != "ILBM") + throw new InvalidDataFormatException(); + + #region BMHD + { + DataChunk? filBMHD = fldFORM.Chunks[0] as DataChunk; + if (filBMHD?.Name != "BMHD" || filBMHD?.Source == null) + throw new InvalidDataFormatException(); + + MemoryStream ms = new MemoryStream(filBMHD.Source.GetData()); + Reader reader = new Reader(ms); + reader.Endianness = Endianness.BigEndian; + + // thanks http://etwright.org/lwsdk/docs/filefmts/ilbm.html + + // raster width & height in pixels + ushort width = reader.ReadUInt16(); + ushort height = reader.ReadUInt16(); + pic.Width = width; + pic.Height = height; + + // pixel position for this image + ushort x = reader.ReadUInt16(); + ushort y = reader.ReadUInt16(); + + byte nPlanes = reader.ReadByte(); + ILBMMasking masking = (ILBMMasking)reader.ReadByte(); + ILBMCompression compression = (ILBMCompression)reader.ReadByte(); + byte pad1 = reader.ReadByte(); // should be 0 + ushort transparentColor = reader.ReadUInt16(); + byte xAspect = reader.ReadByte(); + byte yAspect = reader.ReadByte(); + ushort pageWidth = reader.ReadUInt16(); + ushort pageHeight = reader.ReadUInt16(); + } + #endregion + #region CMAP + { + DataChunk? filCMAP = fldFORM.Chunks["CMAP"] as DataChunk; + if (filCMAP != null) + { + byte[]? data = filCMAP.Source?.GetData(); + if (data != null) + { + pic.Palette = new PaletteObjectModel(); + for (int i = 0; i < data.Length; i += 3) + { + byte r = data[i]; + byte g = data[i + 1]; + byte b = data[i + 2]; + + Color color = Color.FromRGBAByte(r, g, b); + pic.Palette.Entries.Add(color); + } + } + } + } + #endregion + + #region BODY + { + + System.IO.MemoryStream ms = new MemoryStream(); + Writer w = new Writer(ms); + + DataChunk? filBODY = fldFORM.Chunks["BODY"] as DataChunk; + if (filBODY != null) + { + byte[]? data = filBODY.Source?.GetData(); + if (data != null) + { + for (int i = 0; i < data.Length; i += 3) + { + sbyte n = (sbyte) data[i]; + if (n >= 0 && n <= 127) + { + int count = n + 1; + for (int j = 0; j < count; j++) + { + byte next = data[i + 1]; + w.WriteByte(next); + i++; + } + } + else if (n == -128) + { + // nop + } + else + { + int count = -n; + byte next = data[i + 1]; + for (int j = 0; j < count; j++) + { + w.WriteByte(next); + } + i++; + } + } + } + } + } + #endregion + } + + protected override void BeforeSaveInternal(Stack objectModels) + { + base.BeforeSaveInternal(objectModels); + } + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs new file mode 100644 index 0000000..cfbdb41 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/DataFormats/Picture/ILBM/ILBMMasking.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; + +/// +/// Choice of masking technique. +/// +public enum ILBMMasking : byte +{ + /// + /// Designates an opaque rectangular image. + /// + None = 0x00, + /// + /// Mask plane is interleaved with the bitplanes in the BODY chunk. + /// + Mask = 0x01, + /// + /// Pixels in the source planes matching transparentColor are to be considered "transparent." + /// + /// + /// transparentColor isn't a "color number" since it's matched with numbers formed by the source bitmap rather than the possibly + /// deeper destination bitmap. Note that having a transparent color implies ignoring one of the color registers. + /// + TransparentColor = 0x02, + /// + /// Indicates the reader may construct a mask by lassoing the image as in MacPaint. To do this, put a 1 pixel border of + /// transparentColor around the image rectangle. Then do a seed fill from this border. Filled pixels are to be transparent. + /// + Lasso = 0x03 +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs new file mode 100644 index 0000000..27e70eb --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioMetadata.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio; + +public class AudioMetadata +{ + public string Title { get; set; } = ""; + public string Artist { get; set; } = ""; + public string Copyright { get; set; } = ""; + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs new file mode 100644 index 0000000..dabade1 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/AudioObjectModelBase.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Editor.Core; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio; + +public abstract class AudioObjectModelBase : ObjectModel +{ + public AudioMetadata Metadata { get; set; } = new AudioMetadata(); + +} \ No newline at end of file diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs new file mode 100644 index 0000000..bfd3352 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommand.cs @@ -0,0 +1,114 @@ +// +// SynthesizedAudioCommand.cs - represents a command (e.g. note, control change) in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.ObjectModel; +using MBS.Core; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a command (e.g. note, control change) in a synthesized audio file. +/// +public class SynthesizedAudioCommand : ICloneable +{ + public class SynthesizedAudioCommandCollection : Collection + { + public SynthesizedAudioCommandRest Add(double length) + { + SynthesizedAudioCommandRest rest = new SynthesizedAudioCommandRest(); + rest.Length = length; + + base.Add(rest); + return rest; + } + public SynthesizedAudioCommandNote Add(SynthesizedAudioPredefinedNote note, double length, int octave, float volume) + { + SynthesizedAudioCommandNote command = new SynthesizedAudioCommandNote(); + command.Length = length; + command.Frequency = SynthesizedAudioPredefinedNoteConverter.GetFrequency(note, octave); + + base.Add(command); + return command; + } + + public event EventHandler ItemsChanged; + protected virtual void OnItemsChanged(EventArgs e) + { + ItemsChanged?.Invoke(this, e); + } + protected override void InsertItem(int index, SynthesizedAudioCommand item) + { + base.InsertItem(index, item); + OnItemsChanged(EventArgs.Empty); + } + + protected override void RemoveItem(int index) + { + base.RemoveItem(index); + OnItemsChanged(EventArgs.Empty); + } + protected override void ClearItems() + { + base.ClearItems(); + OnItemsChanged(EventArgs.Empty); + } + protected override void SetItem(int index, SynthesizedAudioCommand item) + { + base.SetItem(index, item); + OnItemsChanged(EventArgs.Empty); + } + + public T NextOfType(T item, SeekDirection direction = SeekDirection.Forward) where T : SynthesizedAudioCommand + { + int index = IndexOf(item); + if (index < 0) index = 0; + if (index > Count - 1) index = 0; + + if (direction == SeekDirection.Backward) + { + if (index < 0) index = Count - 1; + if (index > Count - 1) index = Count - 1; + + for (int i = index - 1; i >= 0; i--) + { + if (this[i] is T) + return (T)this[i]; + } + return (T)this[Count - 1]; + } + else + { + for (int i = index + 1; i < Count; i++) + { + if (this[i] is T) + return (T)this[i]; + } + return (T)this[0]; + } + } + } + + public virtual object Clone() + { + return base.MemberwiseClone(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs new file mode 100644 index 0000000..1e4e8eb --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandNote.cs @@ -0,0 +1,82 @@ +// +// SynthesizedAudioCommandNote.cs - represents a note command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a note command in a synthesized audio file. +/// +public class SynthesizedAudioCommandNote : SynthesizedAudioCommand +{ + public bool Protected { get; set; } = false; + public int Position { get; set; } = 0; + public double Length { get; set; } = 0; + public string Phoneme { get; set; } = null; + public string Lyric { get; set; } = null; + public double Frequency { get; set; } = 0; + public int PreUtterance { get; set; } = 0; + public int VoiceOverlap { get; set; } = 0; + public int Intensity { get; set; } = 0; + public int Modulation { get; set; } = 0; + public int PBType { get; set; } = 0; + public double[] Pitches { get; set; } = new double[0]; + public string[] Envelope { get; set; } = new string[0]; + public double[] VBR { get; set; } = new double[0]; + public int Accent { get; set; } = 50; + public int PitchBendDepth { get; set; } = 8; + public int PitchBendLength { get; set; } = 0; + public int Decay { get; set; } = 50; + public bool PortamentoFalling { get; set; } = false; + public int Opening { get; set; } = 127; + public bool PortamentoRising { get; set; } = false; + public int VibratoLength { get; set; } = 0; + public SynthesizedAudioVibratoType VibratoType { get; set; } = SynthesizedAudioVibratoType.None; + + public override object Clone() + { + return new SynthesizedAudioCommandNote + { + Envelope = this.Envelope.Clone() as string[], + Intensity = this.Intensity, + Length = this.Length, + Lyric = this.Lyric, + Modulation = this.Modulation, + PBType = this.PBType, + Phoneme = this.Phoneme, + Pitches = this.Pitches.Clone() as double[], + PortamentoFalling = this.PortamentoFalling, + PortamentoRising = this.PortamentoRising, + PreUtterance = this.PreUtterance, + Protected = this.Protected, + Position = this.Position, + Frequency = this.Frequency, + VBR = this.VBR.Clone() as double[], + VoiceOverlap = this.VoiceOverlap + }; + } + + public override string ToString() + { + return String.Format("{0} [{1}] <{2}, {3}>", Lyric, Phoneme, Position, Length); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs new file mode 100644 index 0000000..62c46ed --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandRest.cs @@ -0,0 +1,29 @@ +// +// SynthesizedAudioCommandRest.cs - represents a rest in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a rest in a synthesized audio file. +/// +public class SynthesizedAudioCommandRest : SynthesizedAudioCommandNote +{ +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs new file mode 100644 index 0000000..b1e25bc --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTempo.cs @@ -0,0 +1,49 @@ +// +// SynthesizedAudioCommandTempo.cs - represents a tempo change command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a tempo change command in a synthesized audio file. +/// +public class SynthesizedAudioCommandTempo : SynthesizedAudioCommand +{ + public double Tempo { get; set; } = 0.0; + + public SynthesizedAudioCommandTempo() + { + } + public SynthesizedAudioCommandTempo(double tempo) + { + this.Tempo = tempo; + } + public override string ToString() + { + return "MM = " + this.Tempo.ToString() + " BPM"; + } + public override object Clone() + { + return new SynthesizedAudioCommandTempo + { + Tempo = this.Tempo + }; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs new file mode 100644 index 0000000..2e1841c --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandText.cs @@ -0,0 +1,48 @@ +// +// SynthesizedAudioCommandText.cs - represents a text command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a text command in a synthesized audio file. +/// +public class SynthesizedAudioCommandText : SynthesizedAudioCommand +{ + public string Text { get; set; } = string.Empty; + public SynthesizedAudioCommandText() + { + } + public SynthesizedAudioCommandText(string text) + { + this.Text = text; + } + public override string ToString() + { + return "\"" + this.Text + "\""; + } + public override object Clone() + { + return new SynthesizedAudioCommandText + { + Text = this.Text.Clone() as string + }; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs new file mode 100644 index 0000000..b5d255a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioCommandTimeSignature.cs @@ -0,0 +1,66 @@ +// +// SynthesizedAudioCommandTimeSignature.cs - represents a time signature change command in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a time signature change command in a synthesized audio file. +/// +public class SynthesizedAudioCommandTimeSignature : SynthesizedAudioCommand +{ + public byte Numerator { get; set; } = 0; + public byte Denominator { get; set; } = 0; + public byte TicksPerMetronomeClick { get; set; } = 0; + public byte NumberOf32ndNotesPerQuarterNote { get; set; } = 0; + + public SynthesizedAudioCommandTimeSignature() + { + } + public SynthesizedAudioCommandTimeSignature(byte numerator, byte denominator, byte ticksPerMetronomeClick, byte numberOf32ndNotesPerQuarterNote) + { + this.Numerator = numerator; + this.Denominator = denominator; + this.TicksPerMetronomeClick = ticksPerMetronomeClick; + this.NumberOf32ndNotesPerQuarterNote = numberOf32ndNotesPerQuarterNote; + } + public override string ToString() + { + return string.Concat(new string[] + { + "TS = ", + this.Numerator.ToString(), + "/", + this.Denominator.ToString(), + "; ♪ = ", + this.NumberOf32ndNotesPerQuarterNote.ToString() + }); + } + public override object Clone() + { + return new SynthesizedAudioCommandTimeSignature + { + Denominator = this.Denominator, + NumberOf32ndNotesPerQuarterNote = this.NumberOf32ndNotesPerQuarterNote, + Numerator = this.Numerator, + TicksPerMetronomeClick = this.TicksPerMetronomeClick + }; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs new file mode 100644 index 0000000..f43ec15 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioObjectModel.cs @@ -0,0 +1,109 @@ +// +// SynthesizedAudioObjectModel.cs - provides an ObjectModel for manipulating synthesized audio files (e.g. MIDI, VSQ, etc.) +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Collections.Generic; +using MBS.Editor.Core; +// using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Voicebank; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Provides an for manipulating synthesized audio files (e.g. MIDI, VSQ, etc.). +/// +public class SynthesizedAudioObjectModel : AudioObjectModelBase +{ + /* + private static ObjectModelReference _omr = null; + protected override ObjectModelReference MakeReferenceInternal() + { + if (_omr == null) + { + _omr = base.MakeReferenceInternal(); + _omr.Path = new string[] { "Multimedia", "Audio", "Synthesized Audio" }; + } + return _omr; + } + */ + + public short ChannelCount { get; set; } = 2; + + public string Name { get; set; } = string.Empty; + + public double Tempo { get; set; } = 120.0; + + public SynthesizedAudioTrack.SynthesizedAudioTrackCollection Tracks { get; } = new SynthesizedAudioTrack.SynthesizedAudioTrackCollection(); + // public VoicebankObjectModel.VoicebankObjectModelCollection Voices { get; } = new VoicebankObjectModel.VoicebankObjectModelCollection(); + + /* + private CriteriaObject[] _CriteriaObjects = null; + + private CriteriaProperty PROPERTY_LYRIC = new CriteriaProperty("Lyric", typeof(string)); + protected override CriteriaObject[] GetCriteriaObjectsInternal() + { + if (_CriteriaObjects == null) + { + _CriteriaObjects = new CriteriaObject[] + { + new CriteriaObject("Note", new CriteriaProperty[] + { + PROPERTY_LYRIC + }) + }; + } + return _CriteriaObjects; + } + protected override CriteriaResult[] FindInternal(CriteriaQuery query) + { + List list = new List(); + for (int i = 0; i < Tracks.Count; i++) + { + for (int j = 0; j < Tracks[i].Commands.Count; j++) + { + if (Tracks[i].Commands[j] is SynthesizedAudioCommandNote) + { + if (query.Check(PROPERTY_LYRIC, (Tracks[i].Commands[j] as SynthesizedAudioCommandNote).Lyric)) + { + list.Add(new CriteriaResult(Tracks[i].Commands[j])); + } + } + } + } + return list.ToArray(); + } + */ + + public override void CopyTo(ObjectModel destination) + { + SynthesizedAudioObjectModel clone = destination as SynthesizedAudioObjectModel; + clone.Name = (this.Name.Clone() as string); + clone.Tempo = this.Tempo; + foreach (SynthesizedAudioTrack track in this.Tracks) + { + clone.Tracks.Add(track.Clone() as SynthesizedAudioTrack); + } + } + public override void Clear() + { + this.Name = string.Empty; + this.Tempo = 120.0; + this.Tracks.Clear(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs new file mode 100644 index 0000000..5b268cd --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioPredefinedNote.cs @@ -0,0 +1,98 @@ +// +// SynthesizedAudioPredefinedNote.cs - defines various commonly-used note pitches for synthesized audio files +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Defines various commonly-used note pitches for synthesized audio files. +/// +public enum SynthesizedAudioPredefinedNote +{ + A = 0, + ASharp, + BFlat = ASharp, + B, + C, + CSharp, + DFlat = CSharp, + D, + DSharp, + EFlat = DSharp, + E, + F, + FSharp, + GFlat = FSharp, + G, + GSharp, + AFlat = GSharp +} +/// +/// Converts to and from values and their associated frequencies. +/// +public static class SynthesizedAudioPredefinedNoteConverter +{ + public static double GetFrequency(SynthesizedAudioPredefinedNote note, int octave) + { + return GetFrequency((int)note, octave); + } + public static double GetFrequency(int note, int octave) + { + return GetFrequency(note + ((octave - 4) * 12)); + } + public static double GetFrequency(int note) + { + /* + C5 = the C an octave above middle C. This is 3 half steps above A4 and so the frequency is + f3 = 440 * (1.059463..)^3 = 523.3 Hz + If your calculator does not have the ability to raise to powers, then use the fact that + (1.059463..)^3 = (1.059463..)*(1.059463..)*(1.059463..) + That is, you multiply it by itself 3 times. + + Middle C is 9 half steps below A4 and the frequency is: + f -9 = 440 * (1.059463..)^(-9) = 261.6 Hz + If you don't have powers on your calculator, remember that the negative sign on the power means you divide instead of multiply. For this example, you divide by (1.059463..) 9 times. + */ + + double d = Math.Pow(2, (double)1 / 12); + int halfSteps = note; + double value = 440 * Math.Pow(d, halfSteps); + return value; + } + + public static int GetNote(double frequency) + { + double d = Math.Pow(2, (double)1 / 12); + double halfSteps = Math.Pow(frequency / 440, (1 / d)); + int note = (int)Math.Round((halfSteps - 1) * 12) + 1; + return note; + } + + public static double ChangeFrequency(double oldFrequency, int detuneBy) + { + if (detuneBy == 0) return oldFrequency; + + int note = GetNote(oldFrequency); + double freq = GetFrequency(note + detuneBy); + return freq; + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs new file mode 100644 index 0000000..219b54a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioStylePlugin.cs @@ -0,0 +1,63 @@ +// +// SynthesizedAudioStylePlugin.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +public class SynthesizedAudioStylePlugin +{ + private Guid mvarID = Guid.Empty; + private string mvarName = string.Empty; + private Version mvarVersion = new Version(1, 0); + public Guid ID + { + get + { + return this.mvarID; + } + set + { + this.mvarID = value; + } + } + public string Name + { + get + { + return this.mvarName; + } + set + { + this.mvarName = value; + } + } + public Version Version + { + get + { + return this.mvarVersion; + } + set + { + this.mvarVersion = value; + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs new file mode 100644 index 0000000..5693d9a --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioTrack.cs @@ -0,0 +1,101 @@ +// +// SynthesizedAudioTrack.cs - represents a track in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +using MBS.Core.Drawing; + +// using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Voicebank; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Represents a track in a synthesized audio file. +/// +public class SynthesizedAudioTrack : ICloneable +{ + public class SynthesizedAudioTrackCollection : Collection + { + private Dictionary tracksByID = new Dictionary(); + public SynthesizedAudioTrack this[string ID] + { + get + { + SynthesizedAudioTrack result; + if (this.tracksByID.ContainsKey(ID)) + { + result = this.tracksByID[ID]; + } + else + { + result = null; + } + return result; + } + } + + protected override void InsertItem(int index, SynthesizedAudioTrack item) + { + base.InsertItem(index, item); + + if (!string.IsNullOrEmpty(item.ID)) + tracksByID[item.ID] = item; + } + protected override void RemoveItem(int index) + { + if (!string.IsNullOrEmpty(this[index].ID)) + tracksByID.Remove(this[index].ID); + + base.RemoveItem(index); + } + protected override void ClearItems() + { + base.ClearItems(); + tracksByID.Clear(); + } + } + + public string ID { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Comment { get; set; } = string.Empty; + public SynthesizedAudioCommand.SynthesizedAudioCommandCollection Commands { get; } = new SynthesizedAudioCommand.SynthesizedAudioCommandCollection(); + // public VoicebankObjectModel Synthesizer { get; set; } = null; + public bool IsMuted { get; set; } = false; + public bool IsSolo { get; set; } = false; + public byte Panpot { get; set; } = 64; + public byte Volume { get; set; } = 0; + public object Clone() + { + SynthesizedAudioTrack clone = new SynthesizedAudioTrack(); + foreach (SynthesizedAudioCommand command in this.Commands) + { + clone.Commands.Add(command.Clone() as SynthesizedAudioCommand); + } + clone.ID = (this.ID.Clone() as string); + clone.Name = (this.Name.Clone() as string); + return clone; + } + + public double Tempo { get; set; } = 120; + public Color Color { get; set; } = Color.Empty; +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs new file mode 100644 index 0000000..987d672 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Audio/Synthesized/SynthesizedAudioVibratoType.cs @@ -0,0 +1,30 @@ +// +// SynthesizedAudioVibratoType.cs - indicates the type of vibrato applied to a note in a synthesized audio file +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; + +/// +/// Indicates the type of vibrato applied to a note in a synthesized audio file. +/// +public enum SynthesizedAudioVibratoType +{ + None +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs new file mode 100644 index 0000000..a4f999f --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteEntry.cs @@ -0,0 +1,71 @@ +// +// PaletteEntry.cs - represents a color entry in a palette +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using MBS.Core.Drawing; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Palette +{ + /// + /// Represents a color entry in a palette. + /// + public class PaletteEntry : ICloneable + { + public class PaletteEntryCollection + : System.Collections.ObjectModel.Collection + { + public PaletteEntry Add(Color color, string colorName = "") + { + PaletteEntry entry = new PaletteEntry(); + entry.Name = colorName; + entry.Color = color; + + Add(entry); + return entry; + } + } + + public PaletteEntry(Color color = default(Color), string name = "") + { + Name = name; + Color = color; + } + + /// + /// Gets or sets the name of this . + /// + /// The name of this . + public string Name { get; set; } = String.Empty; + /// + /// Gets or sets the color of this . + /// + /// The color of this . + public Color Color { get; set; } = Color.Empty; + + public object Clone() + { + PaletteEntry clone = new PaletteEntry(); + clone.Name = Name; + clone.Color = Color; + return clone; + } + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs new file mode 100644 index 0000000..7a7a7a6 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Palette/PaletteObjectModel.cs @@ -0,0 +1,85 @@ +// +// PaletteObjectModel.cs - provides an ObjectModel for manipulating color palettes +// +// Author: +// Michael Becker +// +// Copyright (c) 2011-2020 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System; +using MBS.Editor.Core; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Palette +{ + /// + /// Provides an for manipulating color palettes. + /// + public class PaletteObjectModel : ObjectModel + { + /* + private static ObjectModelReference _omr = null; + protected override ObjectModelReference MakeReferenceInternal() + { + if (_omr == null) + { + _omr = new ObjectModelReference(GetType(), new Guid("{c852b1d7-d034-43b4-b850-00e583e47fef}")); + _omr.Path = new string[] { "Multimedia", "Color palette" }; + } + return _omr; + } + */ + + public PaletteObjectModel() + { + } + public PaletteObjectModel(PaletteEntry[] entries) + { + if (entries != null) + { + for (int i = 0; i < entries.Length; i++) + { + Entries.Add(entries[i]); + } + } + } + + public override void Clear() + { + Entries.Clear(); + } + + public override void CopyTo(ObjectModel where) + { + PaletteObjectModel clone = (where as PaletteObjectModel); + clone.Name = (Name.Clone() as string); + for (int i = 0; i < Entries.Count; i++) + { + clone.Entries.Add(Entries[i].Clone() as PaletteEntry); + } + } + + /// + /// Gets or sets the name of this palette. + /// + /// The name of this palette. + public string Name { get; set; } = String.Empty; + /// + /// Gets a collection of instances representing the color entries in this palette. + /// + /// The color entries in this palette. + public PaletteEntry.PaletteEntryCollection Entries { get; } = new PaletteEntry.PaletteEntryCollection(); + } +} diff --git a/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs new file mode 100644 index 0000000..3af1a35 --- /dev/null +++ b/editor-dotnet/src/plugins/MBS.Editor.Plugins.Multimedia/ObjectModels/Picture/PictureObjectModel.cs @@ -0,0 +1,55 @@ +// +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . +// + +using System.Numerics; +using MBS.Core.Drawing; +using MBS.Editor.Core; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Palette; + +namespace MBS.Editor.Plugins.Multimedia.ObjectModels.Picture; + +public class PictureObjectModel : ObjectModel +{ + private Dictionary pixels = new Dictionary(); + + public PaletteObjectModel? Palette { get; set; } = null; + + public int Width { get; set; } + public int Height { get; set; } + + public IEnumerable> GetPixels() + { + return pixels; + } + + public Color GetPixel(int x, int y) + { + Vector2D key = new Vector2D(x, y); + if (pixels.ContainsKey(key)) + { + return pixels[key]; + } + return Color.Empty; + } + + public void SetPixel(int x, int y, Color color) + { + pixels[new Vector2D(x, y)] = color; + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs new file mode 100644 index 0000000..80104ec --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/DataFormats/FileSystem/IFF/IFFDataFormatTests.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Editor.Core.DataFormats.FileSystem.IFF; +using MBS.Editor.Core.ObjectModels.Chunked; +using MBS.Editor.Core.ObjectModels.FileSystem; +using MBS.Editor.Testing; + +namespace MBS.Editor.Core.Tests.DataFormats.FileSystem.IFF; + +[TestFixture] +public class IFFDataFormatTests : EmbeddedResourceTest +{ + + [Test] + public void CheckVenusTotalSize() + { + if (TryCreateResourceStream("MBS.Editor.Core.Tests.Resources.TestData.DataFormats.FileSystem.IFF.VENUS.IFF", out Stream? st)) + { + if (st == null) + return; + + ChunkedObjectModel fsom = new ChunkedObjectModel(); + + + IFFDataFormat iff = new IFFDataFormat(); + Document.Load(fsom, iff, st); + + Assert.That(fsom.Chunks.Count, Is.EqualTo(1)); + Assert.That((fsom.Chunks[0] as GroupChunk).Chunks.Count, Is.EqualTo(3)); + } + } + + +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj index 050e4ca..92af4a8 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/MBS.Editor.Core.Tests.csproj @@ -6,6 +6,10 @@ false true + + + + @@ -15,5 +19,6 @@ + \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs index de922a8..5f68bd9 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/FileSystem/FileSystemItemCollectionTests.cs @@ -5,7 +5,7 @@ namespace MBS.Editor.Core.Tests.ObjectModels.FileSystem; public class FileSystemItemCollectionTests { - [ Test()] + [Test()] public static void AddFolderWithEmptyNameTest() { // the reason this test exists diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs new file mode 100644 index 0000000..087f57b --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/ObjectModels/PropertyList/INIDataFormatTests.cs @@ -0,0 +1,80 @@ +namespace MBS.Editor.Core.Tests.ObjectModels.PropertyList; + +using System.Text; +using MBS.Editor.Core.ObjectModels.PropertyList; +using MBS.Editor.Testing; + +public class INIDataFormatTests : EmbeddedResourceTest +{ + private PropertyListObjectModel CreateSimplePropertyListWithSections() + { + PropertyListObjectModel plom = new PropertyListObjectModel(); + + PropertyListProperty prop = new PropertyListProperty("Test 1", 3127); + plom.Items.Add(prop); + + prop = new PropertyListProperty("String Property", "This is a Test"); + plom.Items.Add(prop); + + PropertyListGroup group = new PropertyListGroup("Section 2"); + group.Items.Add(new PropertyListProperty("Property 2", 923.1072)); + plom.Items.Add(group); + + return plom; + } + + [Test()] + public void INIDataFormatTest() + { + PropertyListObjectModel plom = CreateSimplePropertyListWithSections(); + INIDataFormat ini = new INIDataFormat(); + + int old_count = plom.Items.Count; + + MemoryStream ms = new MemoryStream(); + Document.Save(plom, ini, ms); + ms.Flush(); + + byte[] data = ms.ToArray(); + ms = new MemoryStream(data); + + plom = new PropertyListObjectModel(); + ini = new INIDataFormat(); + + Document.Load(plom, ini, ms); + + Assert.That(plom.Items.Count, Is.EqualTo(old_count)); + } + + [Test()] + public void ReallyBadINIDataFormatTest() + { + if (TryCreateResourceStream("Resources.TestData.ObjectModels.PropertyList.INI.CompleteTest.ini", out Stream? st)) + { + if (st == null) + return; + + PropertyListObjectModel plom = new PropertyListObjectModel(); + INIDataFormat ini = new INIDataFormat(); + Document.Load(plom, ini, st); + + Assert.That(plom.Items.Count == 4); // comments are included by default + Assert.That(plom.Items[0], Is.InstanceOf()); + + Assert.That(plom.Items[1], Is.InstanceOf()); + + PropertyListGroup? g = plom.Items[1] as PropertyListGroup; + if (g != null) + { + Assert.That(g.Items.Count, Is.EqualTo(3)); + Assert.That(g.Items[0].Name, Is.EqualTo("Property1")); + + PropertyListProperty? prop = g.Items[1] as PropertyListProperty; + Assert.That(prop, Is.Not.Null); + Assert.That(prop.Value, Is.Not.Null); + Assert.That(prop.Value, Is.EqualTo("This is a property with quotes.")); + } + } + } + +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/DataFormats/FileSystem/IFF/VENUS.IFF b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/DataFormats/FileSystem/IFF/VENUS.IFF new file mode 100644 index 0000000000000000000000000000000000000000..443778ef421211b00c1b80bb693f82a1c75e6669 GIT binary patch literal 26978 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 HlQE0m8OL)a literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini new file mode 100644 index 0000000..a96d07c --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Core.Tests/Resources/TestData/ObjectModels/PropertyList/INI/CompleteTest.ini @@ -0,0 +1,16 @@ +; this is a really bad INI file which should be able +; to be completely parsed by MBS Editor + +[Section1] +Property1=Value2 +PropertyWithQuotes="This is a property with quotes." +PropertyWithoutQuotes=Quotes really do not matter in INI files. + +[Section with a space in its name] +; this should work, and this comment should be included +; in the PropertyListObjectModel +Property Key with Space=Another unquoted value +What happens when=there are multiple equal=values it should be=just the first one + +[This is [probably a [really bad] Idea]] +Version=1.0.50726.42 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs index a2678c9..03b310e 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/CPK/CPKDataFormatTests.cs @@ -5,22 +5,17 @@ using MBS.Editor.Core; using MBS.Editor.Core.ObjectModels.FileSystem; using MBS.Editor.Core.ObjectModels.FileSystem.FileSources; using MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK; +using MBS.Editor.Testing; using NUnit.Framework.Internal; namespace MBS.Editor.Plugins.CRI.Tests.DataFormats.CPK; -public class CPKDataFormatTests +public class CPKDataFormatTests : EmbeddedResourceTest { - private System.Collections.Generic.Dictionary testDataStreams = new System.Collections.Generic.Dictionary(); - [SetUp] public void Setup() { - MBS.Core.Reflection.ManifestResourceStream[] strms = MBS.Core.Reflection.ManifestResourceStream.GetManifestResourceStreamsForAssembly(System.Reflection.Assembly.GetExecutingAssembly()); - for (int i = 0; i < strms.Length; i++) - { - testDataStreams[strms[i].Name] = strms[i].Stream; - } + InitializeResources(); } [Test(Description = "Ensures the CPKDataFormat can store exactly one file, uncompressed, with no filename masking.")] @@ -57,20 +52,6 @@ public class CPKDataFormatTests Assert.That(System_dat.Source.GetData(2, 1)[0], Is.EqualTo(0xBA)); } - - private void SampleStreamTest(string streamName) - { - if (testDataStreams.ContainsKey(streamName)) - { - System.IO.Stream TEST_STREAM = testDataStreams[streamName]; - SampleStreamTest(TEST_STREAM); - } - else - { - Console.Error.WriteLine("test data stream not found: '" + streamName + "'"); - Assert.Ignore(); - } - } private void SampleStreamTest(System.IO.Stream stream) { stream.Seek(0, System.IO.SeekOrigin.Begin); diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs new file mode 100644 index 0000000..27416c1 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/DataFormats/Database/UTF/UTFDataFormatTests.cs @@ -0,0 +1,8 @@ +using System; + +namespace MBS.Editor.Plugins.CRI.Tests.DataFormats.Database.UTF; + +public class UTFDataFormatTests +{ + +} diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj index cd028c0..8a81931 100644 --- a/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.CRI.Tests/MBS.Editor.Plugins.CRI.Tests.csproj @@ -16,6 +16,7 @@ + diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs new file mode 100644 index 0000000..c28e646 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/DataFormats/MEK/MEKDataFormatTests.cs @@ -0,0 +1,59 @@ +using MBS.Editor.Core; +using MBS.Editor.Plugins.Mekada.DataFormats.Mechanic; +using MBS.Editor.Testing; + +namespace MBS.Editor.Plugins.Mekada.Tests; + +public class MEKDataFormatTests : EmbeddedResourceTest +{ + [SetUp] + public void Setup() + { + InitializeResources(); + } + + [Test] + public void Test1() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Mekada.Tests.Resources.TestData.Mechanics.0.mek", out Stream? st)) + { + MechanicObjectModel mekom = new MechanicObjectModel(); + MEKDataFormat mekdf = new MEKDataFormat(); + Document.Load(mekom, mekdf, st); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void Test2() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Mekada.Tests.Resources.TestData.Mechanics.1.mek", out Stream? st)) + { + MechanicObjectModel mekom = new MechanicObjectModel(); + MEKDataFormat mekdf = new MEKDataFormat(); + Document.Load(mekom, mekdf, st); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void WithCarInLot() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Mekada.Tests.Resources.TestData.Mechanics.WithCarInLot.mek", out Stream? st)) + { + MechanicObjectModel mekom = new MechanicObjectModel(); + MEKDataFormat mekdf = new MEKDataFormat(); + Document.Load(mekom, mekdf, st); + } + else + { + Assert.Ignore(); + } + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj new file mode 100644 index 0000000..98421d3 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/MBS.Editor.Plugins.Mekada.Tests.csproj @@ -0,0 +1,39 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/0.mek b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/0.mek new file mode 100644 index 0000000000000000000000000000000000000000..7930b12db286aa974a0bcab6ffeb72b4acd9b11c GIT binary patch literal 389 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 IhW}UKKM2k0Q2+n{ literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/WithCarInLot.mek b/editor-dotnet/src/tests/MBS.Editor.Plugins.Mekada.Tests/Resources/TestData/Mechanics/WithCarInLot.mek new file mode 100644 index 0000000000000000000000000000000000000000..222a921e5c1d0495c73529e79561d4b9c789b31c GIT binary patch literal 72760 zcmeI$dsLI>9S8828mhC{vLm*2<#;=Ei)*a6*rgSYcnqsuBib$Mrg*{01W_{a78P3* z?Xh?%7G^b4tCo#)Mo|mFL9kd!wGt~>>O@^BUTRb(%%)}4SoXXX?C<2qbIkwya*ptt z*>@uZ1d^x`!1dqWv-TeI6H&4H19$Ywp$PA+8U!U#rg_ zY`=a;Tz{asS39fkri#E^x+K?y*-4FmIvk*NpiH_;MCu+dS8ndbmmEO-woR03asaoHMVVK+mQiTpqo-la1e;AnHv{ozB{MOHz-H`}emFu$)CwkGiDZ@v?Tx)*r1G znpIK}=+Yu-C#~o1Tg2KK%(^w0RK0cFNW}ZRcFSjn(^e5cRZUjt5cT5w7rNAYhF^?X$33H)$OVomoqOu@9rp~{hCXA@ zvo%8ZMw+92zP;v;tj|fL?KDU0ecdaU<<#dp<^s)$hwrcDlef3BcGA6y=4ie5M(v0R zbfJ5rEN3d-$nG`WgJ|dcJJ;Blrb=?Oo!yUwvYc%E#IWCY8MZZv^)E$IkB;AG6%VnT zY#eBA;PhwN_ysdxmONX>#!o#+bvEv_Uca74SvzIpPFuG90M`g~`5K=RuPv!Zi| z=EPgvpL?&QPGz|?@qEXa(j2wAVdi_z0!fa}g_M+HW=z)ztw(boj&iVe(m6|Ww4FoF z9ANe6`H$wP%dfBPW;uG!p*gw-Bu<^p_5gbRqdD5Yv;CV`?k5sY+RpbrTEp5YK8AEU zezcxFzM1WfUb04LJ8A#yJLa=`bZyCU9}nBYa`gO1bF`grOtmr>dXA$x>QepMK~_&T zel)ju^9w9DTjEJ`v%Ggk2CiGW$I03`{q^H9fjQZ{rY=kB%UC<PRjJFbzu{$jxQ z4eDzjG_H$kpN|R5sqO46N9WP2MeA8k-FF|9JGbb!(Sh~oJyzyinccG@Fh|buGscjx*L zmXqzPw13xrH99769Ozkx_K)U1>+59oWakr_i&=NW6pE>3+8^Kn>v<_ zJN^7f`$y~DzE#9rWcvW^-}B4zS#EI<{p(&{%GxPAx6yh#j*MrX>hlj;w@Le|Sv~q$ zQRXs!UnR@Q_HpX@cFuG(a6a*esBVMBw>V)NR)E$)_PcXPF~ zy0x*@8hxlB<5aEds#a^aSlmu$VHqvT?V zv(dTNy|=!l-kM=8C@i>r_x9cI@7A8KwYm%ir-Evm(MF?jTtdQZ zpU?k0^R&YKpTA`K{YS?euU`Ll*229DRvlcq@o@ImkGK8p)WM4LrS7kyU!3yE+2;By z-(2_k@4bQ1WHOn}`3{FULA=dupWm;E(hMCnG)fm`PB0tCjWJDp`KN<>JYyJ`v*pb` zhTevFeNt9}qu8Fm)3$I?dVXD0;_RtmVH%w-%49N{&8CC|b5)hg@AqByXuOxoM(A~4 zdeSe~=3Mz_;mMMu`V-r$@*-S$zi_@g?L@_vzwWlR`kGGXN0hDqSyf)7C%@mW4O7O( z>O6T7jfH(Zd69M7`?&L-F3XPEl0LwmsV!ZvtH^!s{k4Xe;gMAvdV97FDp@~b$*hrA zPQG)t&f~p&q5K~uZ(B#t8vo2HOZ+M4x-o;g_kW`6@LxXFc;wAqk7_0v2cC3f}cUjAd9haOd_vJ41RJp2L#h&9;P1nA8qe6&V>B5fKq4G&P#6 YP_Y6+#3Be$fB*mftO-^6Y1+I0Kj{Xy{Qv*} literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs new file mode 100644 index 0000000..ef609ef --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Audio/Synthesized/RMI/RMIDataFormatTests.cs @@ -0,0 +1,117 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of editor-dotnet. +// +// editor-dotnet is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// editor-dotnet is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with editor-dotnet. If not, see . + +using MBS.Editor.Core; +using MBS.Editor.Plugins.Multimedia.DataFormats.Audio.Synthesized.RMI; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Audio.Synthesized; +using MBS.Editor.Testing; + +namespace MBS.Editor.Plugins.Multimedia.Tests.DataFormats.Audio.Synthesized.RMI; + +public class RMIDataFormatTests : EmbeddedResourceTest +{ + [Test] + public void LoadMetadataTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + Assert.That(audio.Metadata.Artist, Is.EqualTo("Edvard Grieg")); + Assert.That(audio.Metadata.Copyright, Is.EqualTo("1995 Midisoft Corporation")); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void LoadTrackMetadataTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + Assert.That(audio.Tracks.Count, Is.EqualTo(12)); + Assert.That(audio.Tracks[1].Name, Is.EqualTo("High Strings")); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void SaveTrackMetadataTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + MemoryStream ms = new MemoryStream(); + Document.Save(audio, rmi, ms); + + audio = new SynthesizedAudioObjectModel(); + ms.Seek(0, SeekOrigin.Begin); + Document.Load(audio, rmi, ms); + + Assert.That(audio.Tracks.Count, Is.EqualTo(12)); + Assert.That(audio.Tracks[1].Name, Is.EqualTo("High Strings")); + } + else + { + Assert.Ignore(); + } + } + + [Test] + public void SaveTrackCommandsTest() + { + if (TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Audio.Synthesized.RMI.Mountain.rmi", out Stream? st) && st != null) + { + SynthesizedAudioObjectModel audio = new SynthesizedAudioObjectModel(); + RMIDataFormat rmi = new RMIDataFormat(); + + Document.Load(audio, rmi, st); + + MemoryStream ms = new MemoryStream(); + Document.Save(audio, rmi, ms); + + int cc = audio.Tracks[1].Commands.Count; + + audio = new SynthesizedAudioObjectModel(); + ms.Seek(0, SeekOrigin.Begin); + Document.Load(audio, rmi, ms); + + Assert.That(audio.Tracks[1].Commands.Count, Is.EqualTo(cc)); + } + else + { + Assert.Ignore(); + } + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs new file mode 100644 index 0000000..f47eb3b --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/DataFormats/Picture/ILBM/ILBMDataFormatTests.cs @@ -0,0 +1,62 @@ +using MBS.Core.Drawing; +using MBS.Editor.Core; +using MBS.Editor.Plugins.Multimedia.DataFormats.Picture.ILBM; +using MBS.Editor.Plugins.Multimedia.ObjectModels.Picture; +using MBS.Editor.Testing; + +namespace MBS.Editor.Plugins.Multimedia.Tests.DataFormats.Picture.ILBM; + +public class ILBMDataFormatTests : EmbeddedResourceTest +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Venus_Size_EqualTo_300x150() + { + if (!TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Picture.ILBM.VENUS.IFF", out Stream? st)) + { + Assert.Ignore(); + return; + } + if (st == null) + { + Assert.Ignore(); + return; + } + + PictureObjectModel pic = new PictureObjectModel(); + + ILBMDataFormat iff = new ILBMDataFormat(); + Document.Load(pic, iff, st); + + Assert.That(pic.Width, Is.EqualTo(300)); + Assert.That(pic.Height, Is.EqualTo(150)); + } + + [Test] + public void Venus_Palettte_Contains_ColorMap() + { + if (!TryCreateResourceStream("MBS.Editor.Plugins.Multimedia.Tests.Resources.TestData.DataFormats.Picture.ILBM.VENUS.IFF", out Stream? st)) + { + Assert.Ignore(); + return; + } + if (st == null) + { + Assert.Ignore(); + return; + } + + PictureObjectModel pic = new PictureObjectModel(); + + ILBMDataFormat iff = new ILBMDataFormat(); + Document.Load(pic, iff, st); + + Assert.That(pic.Palette, Is.Not.Null); + Assert.That(pic.Palette.Entries.Count, Is.EqualTo(256)); + Assert.That(pic.Palette.Entries[1].Color, Is.EqualTo(Color.FromString("#FFAF17"))); + } +} \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj new file mode 100644 index 0000000..3f65521 --- /dev/null +++ b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/MBS.Editor.Plugins.Multimedia.Tests.csproj @@ -0,0 +1,42 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Audio/Synthesized/RMI/Mountain.rmi b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Audio/Synthesized/RMI/Mountain.rmi new file mode 100644 index 0000000000000000000000000000000000000000..da79d91c9b63301340c43f4d8166a2d09837c829 GIT binary patch literal 38444 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 literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/BLK.IFF b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/BLK.IFF new file mode 100644 index 0000000000000000000000000000000000000000..1bddef9a5bd306371e9691dc4890a3fe72467d7e GIT binary patch literal 2564 zcmZ?s5AtPT;QZt1*2z{z#zhKf?)!XW?*Ar1d`0mAilG&V*rq201|I#VEF(4 zKLZ2;`5;b1L&Jds2O2=KAbGH&2B3tqqpv%V$HKtC!QkZY5(%_x!gB@)WUT-H>OTXN k0}>h)7!8lnWiMhk<{!eF#8pmoCt0G*nYCIA2c literal 0 HcmV?d00001 diff --git a/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/VENUS.IFF b/editor-dotnet/src/tests/MBS.Editor.Plugins.Multimedia.Tests/Resources/TestData/DataFormats/Picture/ILBM/VENUS.IFF new file mode 100644 index 0000000000000000000000000000000000000000..443778ef421211b00c1b80bb693f82a1c75e6669 GIT binary patch literal 26978 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 HlQE0m8OL)a literal 0 HcmV?d00001 diff --git a/framework-dotnet b/framework-dotnet index a24ae26..223c34c 160000 --- a/framework-dotnet +++ b/framework-dotnet @@ -1 +1 @@ -Subproject commit a24ae26d5775be4387a58f61a3dc26057e0c1171 +Subproject commit 223c34c734ee60b4f4738b48181afb7221e18c39 diff --git a/web-framework-dotnet b/web-framework-dotnet new file mode 160000 index 0000000..732daae --- /dev/null +++ b/web-framework-dotnet @@ -0,0 +1 @@ +Subproject commit 732daae00ebeb6bb31454bd954bdd8174ef1fd0e From f2aba8025afecca97f8c14f7148c3bc3efd55cf9 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 20 Mar 2025 18:58:08 -0400 Subject: [PATCH 4/4] update build script (eww) --- editor | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/editor b/editor index c35329e..0458ab2 100755 --- a/editor +++ b/editor @@ -5,12 +5,23 @@ SRC_PATH=editor-dotnet/src/app/$APP_NAME APP_PATH=$SRC_PATH/bin/Debug/net$DOTNET_VERSION RELEASE=0 +PREREQUISITES="desktop-framework-dotnet/desktop-framework-dotnet/src/engines/MBS.Desktop.Engines.GTK3" + if [ ! $RELEASE -eq 1 ] || [ ! -f $APP_PATH/$APP_NAME ]; then pushd $SRC_PATH dotnet build popd + # also build desktop-framework-dotnet + for prereq in $PREREQUISITES; do + pushd $prereq + dotnet build + popd + done + + cp desktop-framework-dotnet/desktop-framework-dotnet/src/engines/MBS.Desktop.Engines.GTK3/bin/Debug/net$DOTNET_VERSION/*.dll $APP_PATH/engines + fi $APP_PATH/$APP_NAME