begin conversion from Universal Editor 4.0 to MBS Editor 5
This commit is contained in:
parent
62f6a45689
commit
292d828ac8
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
# ---> VisualStudioCode
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
|
||||
121
editor-dotnet.sln
Normal file
121
editor-dotnet.sln
Normal file
@ -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
|
||||
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj" />
|
||||
<ProjectReference Include="..\..\lib\MBS.Editor.Core\MBS.Editor.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
51
editor-dotnet/src/app/MBS.Editor.TestProject/Program.cs
Normal file
51
editor-dotnet/src/app/MBS.Editor.TestProject/Program.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,19 @@
|
||||
using MBS.Editor.UserInterface;
|
||||
// <one line to give the program's name and a brief idea of what it does.>
|
||||
// Copyright (C) <year> <name of author>
|
||||
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
using MBS.Editor.UserInterface;
|
||||
|
||||
return (new EditorApplication()).Start();
|
||||
30
editor-dotnet/src/install-plugins.sh
Executable file
30
editor-dotnet/src/install-plugins.sh
Executable file
@ -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
|
||||
157
editor-dotnet/src/lib/MBS.Editor.Core/Checksum/CRCUtilities.cs
Normal file
157
editor-dotnet/src/lib/MBS.Editor.Core/Checksum/CRCUtilities.cs
Normal file
@ -0,0 +1,157 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MBS.Editor.Core.Checksum;
|
||||
|
||||
internal static class CRCUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of slicing lookup tables to generate.
|
||||
/// </summary>
|
||||
internal const int SlicingDegree = 16;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="polynomial">The generating CRC polynomial</param>
|
||||
/// <param name="isReversed">Whether the polynomial is in reversed bit order</param>
|
||||
/// <returns>A linear array of 256 * <see cref="SlicingDegree"/> elements</returns>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mixes the first four bytes of input with <paramref name="checkValue"/>
|
||||
/// using normal ordering before calling <see cref="UpdateDataCommon"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Array of data to checksum</param>
|
||||
/// <param name="offset">Offset to start reading <paramref name="input"/> from</param>
|
||||
/// <param name="crcTable">The table to use for slicing-by-16 lookup</param>
|
||||
/// <param name="checkValue">Checksum state before this update call</param>
|
||||
/// <returns>A new unfinalized checksum value</returns>
|
||||
/// <seealso cref="UpdateDataForReversedPoly"/>
|
||||
/// <remarks>
|
||||
/// Assumes input[offset]..input[offset + 15] are valid array indexes.
|
||||
/// For performance reasons, this must be checked by the caller.
|
||||
/// </remarks>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mixes the first four bytes of input with <paramref name="checkValue"/>
|
||||
/// using reflected ordering before calling <see cref="UpdateDataCommon"/>.
|
||||
/// </summary>
|
||||
/// <param name="input">Array of data to checksum</param>
|
||||
/// <param name="offset">Offset to start reading <paramref name="input"/> from</param>
|
||||
/// <param name="crcTable">The table to use for slicing-by-16 lookup</param>
|
||||
/// <param name="checkValue">Checksum state before this update call</param>
|
||||
/// <returns>A new unfinalized checksum value</returns>
|
||||
/// <seealso cref="UpdateDataForNormalPoly"/>
|
||||
/// <remarks>
|
||||
/// Assumes input[offset]..input[offset + 15] are valid array indexes.
|
||||
/// For performance reasons, this must be checked by the caller.
|
||||
/// </remarks>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A shared method for updating an unfinalized CRC checksum using slicing-by-16.
|
||||
/// </summary>
|
||||
/// <param name="input">Array of data to checksum</param>
|
||||
/// <param name="offset">Offset to start reading <paramref name="input"/> from</param>
|
||||
/// <param name="crcTable">The table to use for slicing-by-16 lookup</param>
|
||||
/// <param name="x1">First byte of input after mixing with the old CRC</param>
|
||||
/// <param name="x2">Second byte of input after mixing with the old CRC</param>
|
||||
/// <param name="x3">Third byte of input after mixing with the old CRC</param>
|
||||
/// <param name="x4">Fourth byte of input after mixing with the old CRC</param>
|
||||
/// <returns>A new unfinalized checksum value</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Even though the first four bytes of input are fed in as arguments,
|
||||
/// <paramref name="offset"/> should be the same value passed to this
|
||||
/// function's caller (either <see cref="UpdateDataForNormalPoly"/> or
|
||||
/// <see cref="UpdateDataForReversedPoly"/>). This method will get inlined
|
||||
/// into both functions, so using the same offset produces faster code.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
using System.Formats.Tar;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MBS.Editor.Core.Checksum;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <code>getValue</code>. The complete checksum object can also be reset
|
||||
/// so it can be used again with new data.
|
||||
/// </summary>
|
||||
public abstract class ChecksumModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets the data checksum as if no update was ever called.
|
||||
/// </summary>
|
||||
protected abstract void ResetInternal();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the data checksum as if no update was ever called.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
ResetInternal();
|
||||
}
|
||||
|
||||
protected abstract void UpdateInternal(int bval);
|
||||
protected abstract long GetValueInternal();
|
||||
|
||||
private long checkValue;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data checksum computed so far.
|
||||
/// </summary>
|
||||
public long Value { get { return GetValueInternal(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Adds one byte to the data checksum.
|
||||
/// </summary>
|
||||
/// <param name = "bval">
|
||||
/// the data value to add. The high byte of the int is ignored.
|
||||
/// </param>
|
||||
public void Update(int bval)
|
||||
{
|
||||
UpdateInternal(bval);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MBS.Editor.Core.Checksum.Modules.BZip2CRC;
|
||||
|
||||
/// <summary>
|
||||
/// CRC-32 with unreversed data and reversed output
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.)
|
||||
/// </remarks>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// The CRC data checksum so far.
|
||||
/// </summary>
|
||||
private uint checkValue;
|
||||
|
||||
#endregion Instance Fields
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a default instance of <see cref="BZip2Crc"></see>
|
||||
/// </summary>
|
||||
public BZip2CRCChecksumModule()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the CRC data checksum as if no update was ever called.
|
||||
/// </summary>
|
||||
protected override void ResetInternal()
|
||||
{
|
||||
checkValue = crcInit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the CRC data checksum computed so far.
|
||||
/// </summary>
|
||||
/// <remarks>Reversed Out = true</remarks>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the checksum with the int bval.
|
||||
/// </summary>
|
||||
/// <param name = "bval">
|
||||
/// the byte is taken as the lower 8 bits of bval
|
||||
/// </param>
|
||||
/// <remarks>Reversed Data = false</remarks>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void UpdateInternal(int bval)
|
||||
{
|
||||
checkValue = unchecked(crcTable[(byte)(((checkValue >> 24) & 0xFF) ^ bval)] ^ (checkValue << 8));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates the CRC data checksum with the bytes taken from
|
||||
/// a block of data.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Contains the data to update the CRC with.</param>
|
||||
public void Update(byte[] buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
Update(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update CRC data checksum based on a portion of a block of data
|
||||
/// </summary>
|
||||
/// <param name = "segment">
|
||||
/// The chunk of data to add
|
||||
/// </param>
|
||||
public void Update(ArraySegment<byte> segment)
|
||||
{
|
||||
Update(segment.Array, segment.Offset, segment.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal helper function for updating a block of data using slicing.
|
||||
/// </summary>
|
||||
/// <param name="data">The array containing the data to add</param>
|
||||
/// <param name="offset">Range start for <paramref name="data"/> (inclusive)</param>
|
||||
/// <param name="count">The number of bytes to checksum starting from <paramref name="offset"/></param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="data">The array containing the data to add</param>
|
||||
/// <param name="offset">Range start for <paramref name="data"/> (inclusive)</param>
|
||||
/// <param name="end">Range end for <paramref name="data"/> (exclusive)</param>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private void SlowUpdateLoop(byte[] data, int offset, int end)
|
||||
{
|
||||
while (offset != end)
|
||||
{
|
||||
Update(data[offset++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) { }
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace MBS.Editor.Core.Compression;
|
||||
|
||||
public abstract class DualStreamCompressionModule<TInputStream, TOutputStream> : 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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<BZip2InputStream, BZip2OutputStream>
|
||||
{
|
||||
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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
namespace MBS.Editor.Core.Compression.Modules.BZip2;
|
||||
|
||||
/// <summary>
|
||||
/// Defines internal values for both compression and decompression
|
||||
/// </summary>
|
||||
internal static class BZip2Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Random numbers used to randomise repetitive blocks
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// When multiplied by compression parameter (1-9) gives the block size for compression
|
||||
/// 9 gives the best compression but uses the most memory.
|
||||
/// </summary>
|
||||
public const int BaseBlockSize = 100000;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int MaximumAlphaSize = 258;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int MaximumCodeLength = 23;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int RunA = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int RunB = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int GroupCount = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int GroupSize = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int NumberOfIterations = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int MaximumSelectors = (2 + (900000 / GroupSize));
|
||||
|
||||
/// <summary>
|
||||
/// Backend constant
|
||||
/// </summary>
|
||||
public const int OvershootBytes = 20;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MBS.Editor.Core.Compression.Modules.BZip2;
|
||||
|
||||
/// <summary>
|
||||
/// BZip2Exception represents exceptions specific to BZip2 classes and code.
|
||||
/// </summary>
|
||||
public class BZip2Exception : CompressionException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="BZip2Exception" />.
|
||||
/// </summary>
|
||||
public BZip2Exception()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="BZip2Exception" /> with its message string.
|
||||
/// </summary>
|
||||
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||
public BZip2Exception(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="BZip2Exception" />.
|
||||
/// </summary>
|
||||
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||
public BZip2Exception(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
using System.IO.Compression;
|
||||
using MBS.Editor.Core.Compression;
|
||||
|
||||
namespace MBS.Editor.Core.Compression.Modules.Deflate;
|
||||
|
||||
public class DeflateCompressionModule : SystemCompressionModule<DeflateStream>
|
||||
{
|
||||
protected override DeflateStream CreateCompressor(Stream stream)
|
||||
{
|
||||
return new DeflateStream(stream, GetSystemCompressionLevel());
|
||||
}
|
||||
protected override DeflateStream CreateDecompressor(Stream stream)
|
||||
{
|
||||
return new DeflateStream(stream, CompressionMode.Decompress);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
using System.IO.Compression;
|
||||
using MBS.Editor.Core.Compression;
|
||||
|
||||
namespace MBS.Editor.Core.Compression.Modules.GZip;
|
||||
|
||||
public class GZipCompressionModule : SystemCompressionModule<GZipStream>
|
||||
{
|
||||
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);
|
||||
}
|
||||
*/
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
namespace MBS.Editor.Core.Compression.Modules.LZW.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// This class contains constants used for LZW
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "kept for backwards compatibility")]
|
||||
sealed public class LzwConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Magic number found at start of LZW header: 0x1f 0x9d
|
||||
/// </summary>
|
||||
public const int MAGIC = 0x1f9d;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of bits per code
|
||||
/// </summary>
|
||||
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
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Mask for 'number of compression bits'
|
||||
/// </summary>
|
||||
public const int BIT_MASK = 0x1f;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the presence of a fourth header byte
|
||||
/// </summary>
|
||||
public const int EXTENDED_MASK = 0x20;
|
||||
|
||||
//public const int FREE_MASK = 0x40;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved bits
|
||||
/// </summary>
|
||||
public const int RESERVED_MASK = 0x60;
|
||||
|
||||
/// <summary>
|
||||
/// Block compression: if table is full and compression rate is dropping,
|
||||
/// clear the dictionary.
|
||||
/// </summary>
|
||||
public const int BLOCK_MODE_MASK = 0x80;
|
||||
|
||||
/// <summary>
|
||||
/// LZW file header size (in bytes)
|
||||
/// </summary>
|
||||
public const int HDR_SIZE = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Initial number of bits per code
|
||||
/// </summary>
|
||||
public const int INIT_BITS = 9;
|
||||
|
||||
private LzwConstants()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,571 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MBS.Editor.Core.Compression.Modules.LZW.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <example> This sample shows how to unzip a compressed file
|
||||
/// <code>
|
||||
/// 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
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class LzwInputStream : Stream
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||
/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.
|
||||
/// </summary>
|
||||
/// <remarks>The default value is true.</remarks>
|
||||
public bool IsStreamOwner { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a LzwInputStream
|
||||
/// </summary>
|
||||
/// <param name="baseInputStream">
|
||||
/// The stream to read compressed data from (baseInputStream LZW format)
|
||||
/// </param>
|
||||
public LzwInputStream(Stream baseInputStream)
|
||||
{
|
||||
this.baseInputStream = baseInputStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="System.IO.Stream.ReadByte"/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int ReadByte()
|
||||
{
|
||||
int b = Read(one, 0, 1);
|
||||
if (b == 1)
|
||||
return (one[0] & 0xff);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads decompressed data into the provided buffer byte array
|
||||
/// </summary>
|
||||
/// <param name ="buffer">
|
||||
/// The array to read and decompress data into
|
||||
/// </param>
|
||||
/// <param name ="offset">
|
||||
/// The offset indicating where the data should be placed
|
||||
/// </param>
|
||||
/// <param name ="count">
|
||||
/// The number of bytes to decompress
|
||||
/// </param>
|
||||
/// <returns>The number of bytes read. Zero signals the end of stream</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the unread data in the buffer to the beginning and resets
|
||||
/// the pointers.
|
||||
/// </summary>
|
||||
/// <param name="bitPosition"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current stream supports reading
|
||||
/// </summary>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return baseInputStream.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value of false indicating seeking is not supported for this stream.
|
||||
/// </summary>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value of false indicating that this stream is not writeable.
|
||||
/// </summary>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A value representing the length of the stream in bytes.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return got;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current position within the stream.
|
||||
/// Throws a NotSupportedException when attempting to set the position
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">Attempting to set the position</exception>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return baseInputStream.Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException("InflaterInputStream Position not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the baseInputStream
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
baseInputStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position within the current stream
|
||||
/// Always throws a NotSupportedException
|
||||
/// </summary>
|
||||
/// <param name="offset">The relative offset to seek to.</param>
|
||||
/// <param name="origin">The <see cref="SeekOrigin"/> defining where to seek from.</param>
|
||||
/// <returns>The new position in the stream.</returns>
|
||||
/// <exception cref="NotSupportedException">Any access</exception>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException("Seek not supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the length of the current stream
|
||||
/// Always throws a NotSupportedException
|
||||
/// </summary>
|
||||
/// <param name="value">The new length value for the stream.</param>
|
||||
/// <exception cref="NotSupportedException">Any access</exception>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException("InflaterInputStream SetLength not supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to stream and advances the current position
|
||||
/// This method always throws a NotSupportedException
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer containing data to write.</param>
|
||||
/// <param name="offset">The offset of the first byte to write.</param>
|
||||
/// <param name="count">The number of bytes to write.</param>
|
||||
/// <exception cref="NotSupportedException">Any access</exception>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException("InflaterInputStream Write not supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes one byte to the current stream and advances the current position
|
||||
/// Always throws a NotSupportedException
|
||||
/// </summary>
|
||||
/// <param name="value">The byte to write.</param>
|
||||
/// <exception cref="NotSupportedException">Any access</exception>
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
throw new NotSupportedException("InflaterInputStream WriteByte not supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the input stream. When <see cref="IsStreamOwner"></see>
|
||||
/// is true the underlying stream is also closed.
|
||||
/// </summary>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!isClosed)
|
||||
{
|
||||
isClosed = true;
|
||||
if (IsStreamOwner)
|
||||
{
|
||||
baseInputStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Stream Overrides
|
||||
|
||||
#region Instance Fields
|
||||
|
||||
private Stream baseInputStream;
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating wether this instance has been closed or not.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
@ -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<LzwInputStream>
|
||||
{
|
||||
protected override LzwInputStream CreateCompressor(Stream stream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override LzwInputStream CreateDecompressor(Stream stream)
|
||||
{
|
||||
return new LzwInputStream(stream);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MBS.Editor.Core.Compression.Modules.LZW;
|
||||
|
||||
/// <summary>
|
||||
/// LZWException represents exceptions specific to LZW classes and code.
|
||||
/// </summary>
|
||||
public class LZWException : CompressionException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="LzwException" />.
|
||||
/// </summary>
|
||||
public LZWException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="LzwException" /> with its message string.
|
||||
/// </summary>
|
||||
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||
public LZWException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="LzwException" />.
|
||||
/// </summary>
|
||||
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||
public LZWException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using System.IO.Compression;
|
||||
using MBS.Editor.Core.Compression;
|
||||
|
||||
public class ZlibBuiltinCompressionModule : SystemCompressionModule<ZLibStream>
|
||||
{
|
||||
protected override ZLibStream CreateCompressor(Stream stream)
|
||||
{
|
||||
return new ZLibStream(stream, GetSystemCompressionLevel());
|
||||
}
|
||||
protected override ZLibStream CreateDecompressor(Stream stream)
|
||||
{
|
||||
return new ZLibStream(stream, CompressionMode.Decompress);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
namespace MBS.Editor.Core.Compression;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies values that indicate whether a compression operation emphasizes speed or compression size.
|
||||
/// </summary>
|
||||
public enum SystemCompressionLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// The compression operation should optimally balance compression speed and output size.
|
||||
/// </summary>
|
||||
Optimal = 0,
|
||||
/// <summary>
|
||||
/// The compression operation should complete as quickly as possible, even if the resulting
|
||||
/// file is not optimally compressed.
|
||||
/// </summary>
|
||||
Fastest = 1,
|
||||
/// <summary>
|
||||
/// No compression should be performed on the file.
|
||||
/// </summary>
|
||||
NoCompression = 2,
|
||||
/// <summary>
|
||||
/// The compression operation should create output as small as possible, even if
|
||||
/// the operation takes a longer time to complete.
|
||||
/// </summary>
|
||||
SmallestSize = 3
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace MBS.Editor.Core.Compression;
|
||||
|
||||
public abstract class SystemCompressionModule<TStream> : 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");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<T>() 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;
|
||||
}
|
||||
}
|
||||
10
editor-dotnet/src/lib/MBS.Editor.Core/DataFormatMetadata.cs
Normal file
10
editor-dotnet/src/lib/MBS.Editor.Core/DataFormatMetadata.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace MBS.Editor.Core;
|
||||
|
||||
using MBS.Core.Settings;
|
||||
|
||||
public class DataFormatMetadata
|
||||
{
|
||||
public SettingsProvider ExportSettings { get; }
|
||||
public SettingsProvider ImportSettings { get; }
|
||||
|
||||
}
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
41
editor-dotnet/src/lib/MBS.Editor.Core/Document.cs
Normal file
41
editor-dotnet/src/lib/MBS.Editor.Core/Document.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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<HostApplicationMessage>
|
||||
{
|
||||
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; } }
|
||||
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace MBS.Editor.Core.Hosting;
|
||||
|
||||
public enum HostApplicationMessageSeverity
|
||||
{
|
||||
None = 0,
|
||||
Notice = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MBS.Editor.Core.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace MBS.Editor.Core.Hosting;
|
||||
|
||||
public interface IHostApplication
|
||||
{
|
||||
HostServices HostServices { get; }
|
||||
}
|
||||
7
editor-dotnet/src/lib/MBS.Editor.Core/IO/Endianness.cs
Normal file
7
editor-dotnet/src/lib/MBS.Editor.Core/IO/Endianness.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace MBS.Editor.Core.IO;
|
||||
|
||||
public enum Endianness
|
||||
{
|
||||
LittleEndian,
|
||||
BigEndian
|
||||
}
|
||||
11
editor-dotnet/src/lib/MBS.Editor.Core/IO/NewLineSequence.cs
Normal file
11
editor-dotnet/src/lib/MBS.Editor.Core/IO/NewLineSequence.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace MBS.Editor.Core.IO;
|
||||
|
||||
public enum NewLineSequence
|
||||
{
|
||||
Automatic,
|
||||
SystemDefault,
|
||||
CarriageReturn,
|
||||
LineFeed,
|
||||
CarriageReturnLineFeed,
|
||||
LineFeedCarriageReturn
|
||||
}
|
||||
1848
editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs
Normal file
1848
editor-dotnet/src/lib/MBS.Editor.Core/IO/Reader.cs
Normal file
File diff suppressed because it is too large
Load Diff
79
editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs
Normal file
79
editor-dotnet/src/lib/MBS.Editor.Core/IO/ReaderWriterBase.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System.Text;
|
||||
|
||||
namespace MBS.Editor.Core.IO;
|
||||
|
||||
public class ReaderWriterBase
|
||||
{
|
||||
private readonly Stream _st;
|
||||
/// <summary>
|
||||
/// Exposes access to the underlying stream of the <see cref="ReaderWriterBase" />.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The underlying stream associated with the <see cref="ReaderWriterBase" />.
|
||||
/// </value>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aligns the <see cref="Writer" /> to the specified number of bytes. If the current
|
||||
/// position of the <see cref="Writer" /> 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.
|
||||
/// </summary>
|
||||
/// <param name="alignTo">The number of bytes on which to align the <see cref="Reader"/>.</param>
|
||||
/// <param name="extraPadding">Any additional padding bytes that should be included after aligning to the specified boundary.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1023
editor-dotnet/src/lib/MBS.Editor.Core/IO/Writer.cs
Normal file
1023
editor-dotnet/src/lib/MBS.Editor.Core/IO/Writer.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@
|
||||
namespace MBS.Editor.Core;
|
||||
|
||||
public class InvalidDataFormatException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvalidDataFormatException" /> class.
|
||||
/// </summary>
|
||||
public InvalidDataFormatException() : base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvalidDataFormatException" /> class
|
||||
/// with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public InvalidDataFormatException(string? message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvalidDataFormatException" /> class
|
||||
/// with a specified error message and a reference to the inner exception that is the
|
||||
/// cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a null reference
|
||||
/// (Nothing in Visual Basic) if no inner exception is specified.
|
||||
/// </param>
|
||||
public InvalidDataFormatException(string? message, Exception? innerException) : base(message, innerException) { }
|
||||
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@ -4,4 +4,33 @@ public class ObjectModel
|
||||
{
|
||||
public PropertyCollection Properties { get; } = new PropertyCollection();
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void CopyTo(ObjectModel dest)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static T FromType<T>() 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
namespace MBS.Editor.Core;
|
||||
|
||||
public class ObjectModelMetadata
|
||||
{
|
||||
public string[] Path { get; set; }
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
namespace MBS.Editor.Core;
|
||||
|
||||
public class ObjectModelNotSupportedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectModelNotSupportedException" /> class.
|
||||
/// </summary>
|
||||
public ObjectModelNotSupportedException() : base() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectModelNotSupportedException" /> class
|
||||
/// with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public ObjectModelNotSupportedException(string? message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectModelNotSupportedException" /> class
|
||||
/// with a specified error message and a reference to the inner exception that is the
|
||||
/// cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The error message that explains the reason for the exception.</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a null reference
|
||||
/// (Nothing in Visual Basic) if no inner exception is specified.
|
||||
/// </param>
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
//
|
||||
// DatabaseField.cs - represents a field (column) in a DatabaseObjectModel
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a field (column) in a <see cref="DatabaseObjectModel" />.
|
||||
/// </summary>
|
||||
public class DatabaseField : ICloneable
|
||||
{
|
||||
|
||||
public class DatabaseFieldCollection
|
||||
: System.Collections.ObjectModel.Collection<DatabaseField>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
//
|
||||
// DatabaseObjectModel.cs - provides an ObjectModel for manipulating databases
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an <see cref="ObjectModel" /> for manipulating databases.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
//
|
||||
// DatabaseRecord.cs - represents a record (row) in a DatabaseObjectModel
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a record (row) in a <see cref="DatabaseObjectModel" />.
|
||||
/// </summary>
|
||||
public class DatabaseRecord : ICloneable
|
||||
{
|
||||
|
||||
public class DatabaseRecordCollection
|
||||
: System.Collections.ObjectModel.Collection<DatabaseRecord>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
//
|
||||
// DatabaseTable.cs - represents a table in a DatabaseObjectModel
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a table in a <see cref="DatabaseObjectModel" />.
|
||||
/// </summary>
|
||||
public class DatabaseTable : ICloneable
|
||||
{
|
||||
public class DatabaseTableCollection
|
||||
: System.Collections.ObjectModel.Collection<DatabaseTable>
|
||||
{
|
||||
public DatabaseTable this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_itemsByName.ContainsKey(name))
|
||||
return _itemsByName[name];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, DatabaseTable> _itemsByName = new Dictionary<string, DatabaseTable>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the <see cref="DatabaseTable" />.
|
||||
/// </summary>
|
||||
/// <value>The name of the <see cref="DatabaseTable" />.</value>
|
||||
public string Name { get; set; } = String.Empty;
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="DatabaseField" /> instances representing the fields (columns) in the <see cref="DatabaseTable" />.
|
||||
/// </summary>
|
||||
/// <value>The fields (columns) in the <see cref="DatabaseTable" />.</value>
|
||||
public DatabaseField.DatabaseFieldCollection Fields { get; } = new DatabaseField.DatabaseFieldCollection();
|
||||
/// <summary>
|
||||
/// Gets a collection of <see cref="DatabaseRecord" /> instances representing the records (rows) in the <see cref="DatabaseTable" />.
|
||||
/// </summary>
|
||||
/// <value>The records (rows) in the <see cref="DatabaseTable" />.</value>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
//
|
||||
// MemoryFileSource.cs - provides a FileSource for retrieving file data from a byte array
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.FileSystem.FileSources;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a <see cref="FileSource" /> for retrieving file data from a <see cref="MemoryAccessor" />.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
//
|
||||
// StreamFileSource.cs - provides a FileSource for retrieving file data from a System.IO.Stream
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.FileSystem.FileSources;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a <see cref="FileSource" /> for retrieving file data from a <see cref="MemoryAccessor" />.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
namespace MBS.Editor.Core.ObjectModels.FileSystem;
|
||||
|
||||
public class FileSystemCustomDetailCollection
|
||||
{
|
||||
private struct _item
|
||||
{
|
||||
public string id;
|
||||
public string title;
|
||||
}
|
||||
|
||||
private Dictionary<string, _item> _dict = new Dictionary<string, _item>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<string, object> CustomDetails { get; } = new Dictionary<string, object>();
|
||||
|
||||
public long Size
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Source == null)
|
||||
return 0;
|
||||
return Source.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
@ -0,0 +1,181 @@
|
||||
|
||||
using MBS.Editor.Core.ObjectModels.FileSystem.FileSources;
|
||||
|
||||
namespace MBS.Editor.Core.ObjectModels.FileSystem;
|
||||
|
||||
public class FileSystemItemCollection
|
||||
: System.Collections.ObjectModel.Collection<FileSystemItem>
|
||||
{
|
||||
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<FileSystemFile> list = new List<FileSystemFile>();
|
||||
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<FileSystemFile> list = new List<FileSystemFile>();
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (this[i] is FileSystemFile Peter)
|
||||
{
|
||||
list.Add(Peter);
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public FileSystemFolder[] GetFolders()
|
||||
{
|
||||
List<FileSystemFolder> list = new List<FileSystemFolder>();
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (this[i] is FileSystemFolder folder)
|
||||
{
|
||||
list.Add(folder);
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
@ -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() };
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace MBS.Editor.Core.ObjectModels.FileSystem;
|
||||
|
||||
public interface IFileSystemItemContainer
|
||||
{
|
||||
FileSystemObjectModel FileSystem { get; }
|
||||
FileSystemItemCollection Items { get; }
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
//
|
||||
// UTFColumnDataType.cs - CRI Middleware UTF table column data types
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Plugins.CRI.DataFormats.Database.UTF;
|
||||
|
||||
/// <summary>
|
||||
/// The data type for a column in a UTF table.
|
||||
/// </summary>
|
||||
public enum UTFColumnDataType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Mask value for combining <see cref="UTFColumnDataType" /> with <see cref="UTFColumnStorageType" />.
|
||||
/// </summary>
|
||||
Mask = 0x0f,
|
||||
/// <summary>
|
||||
/// The column represents a variable-length array of <see cref="System.Byte" /> data.
|
||||
/// </summary>
|
||||
Data = 0x0b,
|
||||
/// <summary>
|
||||
/// The column represents a variable-length <see cref="System.String" />.
|
||||
/// </summary>
|
||||
String = 0x0a,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Single" /> value.
|
||||
/// </summary>
|
||||
Float = 0x08,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Int64" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Long2 = 0x07,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Int64" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Long = 0x06,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Int32" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Int2 = 0x05,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Int32" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Int = 0x04,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Int16" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Short2 = 0x03,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Int16" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Short = 0x02,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Byte" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Byte2 = 0x01,
|
||||
/// <summary>
|
||||
/// The column represents a <see cref="System.Byte" /> value. There may or may not be a distinction between signed and unsigned types.
|
||||
/// </summary>
|
||||
Byte = 0x00
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
//
|
||||
// UTFColumnStorageType.cs - CRI Middleware UTF table column storage types
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Plugins.CRI.DataFormats.Database.UTF;
|
||||
|
||||
/// <summary>
|
||||
/// The storage type for a column in a UTF table.
|
||||
/// </summary>
|
||||
public enum UTFColumnStorageType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Mask value for combining <see cref="UTFColumnDataType" /> with <see cref="UTFColumnStorageType" />.
|
||||
/// </summary>
|
||||
Mask = 0xf0,
|
||||
/// <summary>
|
||||
/// Data in this column is stored per row, with a single value written for each ROW in the table.
|
||||
/// </summary>
|
||||
PerRow = 0x50,
|
||||
/// <summary>
|
||||
/// Data in this column is constant regardless of row, with a single value written for each COLUMN in the table.
|
||||
/// </summary>
|
||||
Constant = 0x30,
|
||||
/// <summary>
|
||||
/// Data in this column is declared NULL for all rows in the table. No data is written for this column.
|
||||
/// </summary>
|
||||
Zero = 0x10
|
||||
}
|
||||
@ -0,0 +1,644 @@
|
||||
//
|
||||
// UTFDataFormat.cs - COMPLETED - Implementation of CRI Middleware UTF table (used in CPK)
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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; // "<NULL>\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); // "<NULL>\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 "<NULL>\0" string and dt.Name + 1
|
||||
|
||||
List<string> stringTable = new List<string>();
|
||||
stringTable.Add("<NULL>");
|
||||
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<string> 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<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,293 @@
|
||||
//
|
||||
// AFSDataFormat.cs - COMPLETED - implementation of CRI Middleware AFS archive
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="DataFormat" /> for loading and saving <see cref="FileSystemObjectModel" /> archives in CRI Middleware AFS/AWB/ACB format.
|
||||
/// </summary>
|
||||
public class AFSDataFormat : DataFormat
|
||||
{
|
||||
/*
|
||||
private static DataFormatReference _dfr;
|
||||
/// <summary>
|
||||
/// Creates a <see cref="DataFormatReference" /> containing metadata about the <see cref="AFSDataFormat" />.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="DataFormatReference" /> which contains metadata about the <see cref="AFSDataFormat" />.</returns>
|
||||
protected override DataFormatReference MakeReferenceInternal()
|
||||
{
|
||||
if (_dfr == null)
|
||||
{
|
||||
_dfr = base.MakeReferenceInternal();
|
||||
_dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All);
|
||||
}
|
||||
return _dfr;
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the version of AFS archive to read or write. Defaults to <see cref="AFSFormatVersion.AFS0" /> ('AFS\0').
|
||||
/// </summary>
|
||||
/// <value>The version of AFS archive to read or write.</value>
|
||||
public AFSFormatVersion FormatVersion { get; set; } = AFSFormatVersion.AFS0;
|
||||
|
||||
/// <summary>
|
||||
/// When true, and the underlying <see cref="Stream" /> is a <see cref="FileStream" />,
|
||||
/// if the name of the file ends in ".awb", will automatically set <see cref="FormatVersion" /> to
|
||||
/// <see cref="AFSFormatVersion.AFS2" />.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public bool AutoDetectFormatVersion { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the <see cref="ObjectModel" /> data from the input <see cref="Accessor" />.
|
||||
/// </summary>
|
||||
/// <param name="objectModel">A <see cref="FileSystemObjectModel" /> into which to load archive content.</param>
|
||||
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<uint>(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\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the <see cref="ObjectModel" /> data to the output <see cref="Accessor" />.
|
||||
/// </summary>
|
||||
/// <param name="objectModel">A <see cref="FileSystemObjectModel" /> containing the archive content to write.</param>
|
||||
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<uint>(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<uint>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
//
|
||||
// AFSFileInfo.cs - internal structure representing metadata for files in an AFS archive
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS;
|
||||
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Internal structure representing metadata for files in an AFS archive.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
//
|
||||
// AFSFormatVersion.cs - the version of AFS archive being handled by an AFSDataFormat instance
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.AFS;
|
||||
|
||||
/// <summary>
|
||||
/// The version of AFS archive being handled by an <see cref="AFSDataFormat" /> instance.
|
||||
/// </summary>
|
||||
public enum AFSFormatVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// Older version of AFS, which stores file data and TOC information in the same AFS file.
|
||||
/// </summary>
|
||||
AFS0,
|
||||
/// <summary>
|
||||
/// Newer version of AFS, which stores file data in an AWB file and writes the TOC to a separate ACB file.
|
||||
/// </summary>
|
||||
AFS2
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,897 @@
|
||||
//
|
||||
// CPKDataFormat.cs - implementation of CRI Middleware CPK archive
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="DataFormat" /> for loading and saving <see cref="FileSystemObjectModel" /> archives in CRI Middleware CPK format.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <value>The version string.</value>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sector alignment, in bytes, of the CPK archive. The default value is 2048.
|
||||
/// </summary>
|
||||
/// <value>The file alignment.</value>
|
||||
public int SectorAlignment { get; set; } = 2048;
|
||||
|
||||
// these are mainly for the benefit of the CRI Extensions for FileSystemEditor
|
||||
|
||||
private byte[] _HeaderData = null;
|
||||
/// <summary>
|
||||
/// Returns the raw data from the initial "CPK " chunk of this file, or <see langword="null"/> if this chunk does not exist.
|
||||
/// </summary>
|
||||
/// <value>The raw data from the "CPK " chunk.</value>
|
||||
public byte[] HeaderData { get { return _HeaderData; } }
|
||||
/// <summary>
|
||||
/// Returns the UTF table from the initial "CPK " chunk of this file, or <see langword="null"/> if this chunk does not exist.
|
||||
/// </summary>
|
||||
/// <value>The header table.</value>
|
||||
public DatabaseTable HeaderTable { get; private set; } = null;
|
||||
|
||||
private byte[] _TocData = null;
|
||||
/// <summary>
|
||||
/// Returns the raw data from the "TOC " chunk of this file, or NULL if this chunk does not exist.
|
||||
/// </summary>
|
||||
/// <value>The raw data from the "TOC " chunk.</value>
|
||||
public byte[] TocData { get { return _TocData; } }
|
||||
|
||||
private byte[] _ITocData = null;
|
||||
/// <summary>
|
||||
/// Returns the raw data from the "ITOC" chunk of this file, or NULL if this chunk does not exist.
|
||||
/// </summary>
|
||||
/// <value>The raw data from the "ITOC" chunk.</value>
|
||||
public byte[] ITocData { get { return _ITocData; } }
|
||||
|
||||
private byte[] _GTocData = null;
|
||||
/// <summary>
|
||||
/// Returns the raw data from the "GTOC" chunk of this file, or NULL if this chunk does not exist.
|
||||
/// </summary>
|
||||
/// <value>The raw data from the "GTOC" chunk.</value>
|
||||
public byte[] GTocData { get { return _GTocData; } }
|
||||
|
||||
private byte[] _ETocData = null;
|
||||
/// <summary>
|
||||
/// Returns the raw data from the final "ETOC" chunk of this file, or NULL if this chunk does not exist.
|
||||
/// </summary>
|
||||
/// <value>The raw data from the "ETOC" chunk.</value>
|
||||
public byte[] ETocData { get { return _ETocData; } }
|
||||
|
||||
/// <summary>
|
||||
/// Loads the <see cref="ObjectModel" /> data from the input <see cref="Accessor" />.
|
||||
/// </summary>
|
||||
/// <param name="objectModel">A <see cref="FileSystemObjectModel" /> into which to load archive content.</param>
|
||||
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<string> listAttribs = new List<string>();
|
||||
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<FileSystemFile> list = new List<FileSystemFile>();
|
||||
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<FileSystemFile>((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", "<NULL>", typeof(string));
|
||||
|
||||
ulong offset = initialFileOffset;
|
||||
offset -= (ulong) SectorAlignment; // idk?
|
||||
|
||||
List<IDOFFSET> offsets = new List<IDOFFSET>(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", "<NULL>")
|
||||
}));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a simple cipher to decrypt an encrypted UTF sector.
|
||||
/// </summary>
|
||||
/// <returns>The decrypted data.</returns>
|
||||
/// <param name="input">The data to decrypt.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the <see cref="ObjectModel" /> data to the output <see cref="Accessor" />.
|
||||
/// </summary>
|
||||
/// <param name="objectModel">A <see cref="FileSystemObjectModel" /> containing the archive content to write.</param>
|
||||
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<ulong>(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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
//
|
||||
// CPKFileMode.cs
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK;
|
||||
|
||||
public enum CPKFileMode
|
||||
{
|
||||
IDOnly = 0,
|
||||
FilenameOnly = 1,
|
||||
IDFilename = 2,
|
||||
FilenameGroup = 3,
|
||||
IDGroup = 4,
|
||||
FilenameIDGroup = 5
|
||||
}
|
||||
@ -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 <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace MBS.Editor.Plugins.CRI.DataFormats.FileSystem.CPK;
|
||||
|
||||
/// <summary>
|
||||
/// Internal structure for representing an ID, offset, and size for a file in a CPK archive.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\lib\MBS.Editor.Core\MBS.Editor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,3 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyCompany("Mike Becker's Software")]
|
||||
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\lib\MBS.Editor.Core\MBS.Editor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\framework-dotnet\framework-dotnet\src\lib\MBS.Core\MBS.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,123 @@
|
||||
//
|
||||
// PositionVector2.cs - provides a tuple indicating X and Y position
|
||||
//
|
||||
// Author:
|
||||
// Michael Becker <alcexhim@gmail.com>
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
|
||||
namespace UniversalEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a tuple indicating X and Y position.
|
||||
/// </summary>
|
||||
public struct PositionVector2 : ICloneable
|
||||
{
|
||||
public bool IsEmpty { get; }
|
||||
|
||||
private PositionVector2(bool empty)
|
||||
{
|
||||
X = 0;
|
||||
Y = 0;
|
||||
IsEmpty = empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the empty <see cref="PositionVector2" />. This field is read-only.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
global using NUnit.Framework;
|
||||
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\lib\MBS.Editor.Core\MBS.Editor.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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]));
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
@ -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<string, System.IO.Stream> testDataStreams = new System.Collections.Generic.Dictionary<string, System.IO.Stream>();
|
||||
|
||||
[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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
global using NUnit.Framework;
|
||||
@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\lib\MBS.Editor.Core\MBS.Editor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\plugins\MBS.Editor.Plugins.CRI\MBS.Editor.Plugins.CRI.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\TestData\sample_data_uncompressed_unmasked.cpk" />
|
||||
<EmbeddedResource Include="Resources\TestData\sample_data_uncompressed_unmasked_idfn.cpk" />
|
||||
<EmbeddedResource Include="Resources\TestData\sample_data_uncompressed_masked.cpk" />
|
||||
<EmbeddedResource Include="Resources\TestData\sample_data.cpk" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user