diff --git a/Mara.Generator/Mara.Generator.csproj b/Mara.Generator/Mara.Generator.csproj
index 8b0ca65..18c2438 100644
--- a/Mara.Generator/Mara.Generator.csproj
+++ b/Mara.Generator/Mara.Generator.csproj
@@ -2,7 +2,7 @@
WinExe
- net5.0-windows
+ net6.0-windows
true
diff --git a/Mara.Lib/Common/Xdelta.cs b/Mara.Lib/Common/Xdelta.cs
index bf36abe..77bd7fa 100644
--- a/Mara.Lib/Common/Xdelta.cs
+++ b/Mara.Lib/Common/Xdelta.cs
@@ -1,5 +1,5 @@
using System.IO;
-using Pleosoft.XdeltaSharp;
+using PleOps.XdeltaSharp.Decoder;
namespace Mara.Lib.Common
{
@@ -15,5 +15,18 @@ public static void Apply(FileStream file, byte[] xdeltaFile, string outfile)
outStream.Close();
file.Close();
}
+
+ public static void ApplyFileStream(string file, string xdeltaFile, string outfile)
+ {
+ using var fileStream = new FileStream(file, FileMode.Open);
+ using var xdeltaStream = new FileStream(xdeltaFile, FileMode.Open);
+ using var outStream = new FileStream(outfile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
+
+ var decoder = new Decoder(fileStream, xdeltaStream, outStream);
+ decoder.Run();
+ fileStream.Close();
+ xdeltaStream.Close();
+ outStream.Close();
+ }
}
}
diff --git a/Mara.Lib/Dll/Pleosoft.XdeltaSharp.deps.json b/Mara.Lib/Dll/Pleosoft.XdeltaSharp.deps.json
deleted file mode 100644
index 8413375..0000000
--- a/Mara.Lib/Dll/Pleosoft.XdeltaSharp.deps.json
+++ /dev/null
@@ -1,120 +0,0 @@
-{
- "runtimeTarget": {
- "name": ".NETStandard,Version=v2.0/",
- "signature": ""
- },
- "compilationOptions": {},
- "targets": {
- ".NETStandard,Version=v2.0": {},
- ".NETStandard,Version=v2.0/": {
- "Pleosoft.XdeltaSharp/1.0.0": {
- "dependencies": {
- "Microsoft.SourceLink.GitHub": "1.0.0",
- "NETStandard.Library": "2.0.3",
- "Roslynator.Analyzers": "3.0.0",
- "SonarAnalyzer.CSharp": "8.15.0.24505",
- "StyleCop.Analyzers": "1.1.118",
- "System.Buffers": "4.5.1"
- },
- "runtime": {
- "Pleosoft.XdeltaSharp.dll": {}
- }
- },
- "Microsoft.Build.Tasks.Git/1.0.0": {},
- "Microsoft.NETCore.Platforms/1.1.0": {},
- "Microsoft.SourceLink.Common/1.0.0": {},
- "Microsoft.SourceLink.GitHub/1.0.0": {
- "dependencies": {
- "Microsoft.Build.Tasks.Git": "1.0.0",
- "Microsoft.SourceLink.Common": "1.0.0"
- }
- },
- "NETStandard.Library/2.0.3": {
- "dependencies": {
- "Microsoft.NETCore.Platforms": "1.1.0"
- }
- },
- "Roslynator.Analyzers/3.0.0": {},
- "SonarAnalyzer.CSharp/8.15.0.24505": {},
- "StyleCop.Analyzers/1.1.118": {},
- "System.Buffers/4.5.1": {
- "runtime": {
- "lib/netstandard2.0/System.Buffers.dll": {
- "assemblyVersion": "4.0.3.0",
- "fileVersion": "4.6.28619.1"
- }
- }
- }
- }
- },
- "libraries": {
- "Pleosoft.XdeltaSharp/1.0.0": {
- "type": "project",
- "serviceable": false,
- "sha512": ""
- },
- "Microsoft.Build.Tasks.Git/1.0.0": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-z2fpmmt+1Jfl+ZnBki9nSP08S1/tbEOxFdsK1rSR+LBehIJz1Xv9/6qOOoGNqlwnAGGVGis1Oj6S8Kt9COEYlQ==",
- "path": "microsoft.build.tasks.git/1.0.0",
- "hashPath": "microsoft.build.tasks.git.1.0.0.nupkg.sha512"
- },
- "Microsoft.NETCore.Platforms/1.1.0": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
- "path": "microsoft.netcore.platforms/1.1.0",
- "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
- },
- "Microsoft.SourceLink.Common/1.0.0": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-G8DuQY8/DK5NN+3jm5wcMcd9QYD90UV7MiLmdljSJixi3U/vNaeBKmmXUqI4DJCOeWizIUEh4ALhSt58mR+5eg==",
- "path": "microsoft.sourcelink.common/1.0.0",
- "hashPath": "microsoft.sourcelink.common.1.0.0.nupkg.sha512"
- },
- "Microsoft.SourceLink.GitHub/1.0.0": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-aZyGyGg2nFSxix+xMkPmlmZSsnGQ3w+mIG23LTxJZHN+GPwTQ5FpPgDo7RMOq+Kcf5D4hFWfXkGhoGstawX13Q==",
- "path": "microsoft.sourcelink.github/1.0.0",
- "hashPath": "microsoft.sourcelink.github.1.0.0.nupkg.sha512"
- },
- "NETStandard.Library/2.0.3": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
- "path": "netstandard.library/2.0.3",
- "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
- },
- "Roslynator.Analyzers/3.0.0": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-UKaYNtbXGGkR83rMA42DbA43fIUebO+WfHvPk29WMZfXzzF9kTKEB0HL6dUmcukgeHwXyTBCj6946DSaoow02A==",
- "path": "roslynator.analyzers/3.0.0",
- "hashPath": "roslynator.analyzers.3.0.0.nupkg.sha512"
- },
- "SonarAnalyzer.CSharp/8.15.0.24505": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-r4Pt4c/FV1m//NuvVP5k0PmGBceJTyV9hv0sGXSd2bpLb2I0yX3IShuGcCekkS2E82Gay5feKlfOK9b+G3vr1Q==",
- "path": "sonaranalyzer.csharp/8.15.0.24505",
- "hashPath": "sonaranalyzer.csharp.8.15.0.24505.nupkg.sha512"
- },
- "StyleCop.Analyzers/1.1.118": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-Onx6ovGSqXSK07n/0eM3ZusiNdB6cIlJdabQhWGgJp3Vooy9AaLS/tigeybOJAobqbtggTamoWndz72JscZBvw==",
- "path": "stylecop.analyzers/1.1.118",
- "hashPath": "stylecop.analyzers.1.1.118.nupkg.sha512"
- },
- "System.Buffers/4.5.1": {
- "type": "package",
- "serviceable": true,
- "sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
- "path": "system.buffers/4.5.1",
- "hashPath": "system.buffers.4.5.1.nupkg.sha512"
- }
- }
-}
\ No newline at end of file
diff --git a/Mara.Lib/Dll/Pleosoft.XdeltaSharp.dll b/Mara.Lib/Dll/Pleosoft.XdeltaSharp.dll
deleted file mode 100644
index f4e101e..0000000
Binary files a/Mara.Lib/Dll/Pleosoft.XdeltaSharp.dll and /dev/null differ
diff --git a/Mara.Lib/Dll/Pleosoft.XdeltaSharp.xml b/Mara.Lib/Dll/Pleosoft.XdeltaSharp.xml
deleted file mode 100644
index 6bfb571..0000000
--- a/Mara.Lib/Dll/Pleosoft.XdeltaSharp.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- Pleosoft.XdeltaSharp
-
-
-
-
- Adler-32 checksum algorithm.
-
-
- For more information: https://en.wikipedia.org/wiki/Adler-32.
-
-
-
-
- Computes the checksum Adler-32 from a stream.
-
- The stream to read data. It starts at the current position.
- The amount of bytes to read from the stream for the checksum.
- The start value of the checksum.
- The Adler-32 checksum of the data.
-
-
-
- Decodes an integer of variable length.
- Defined in RCF-3284, the VCDIFF format uses a variable-length integer.
- Each byte contains 7-bits of data and the last bit indicates if there
- more bytes to decode.
-
- The integer.
-
-
-
diff --git a/Mara.Lib/Mara.Lib.csproj b/Mara.Lib/Mara.Lib.csproj
index e1cc009..4ea488d 100644
--- a/Mara.Lib/Mara.Lib.csproj
+++ b/Mara.Lib/Mara.Lib.csproj
@@ -1,14 +1,15 @@
- net5.0
+ net6.0
-
+
+
-
+
@@ -19,9 +20,6 @@
Dll\LibHac.dll
-
- Dll\Pleosoft.XdeltaSharp.dll
-
diff --git a/Mara.Lib/Platforms/3ds/Main.cs b/Mara.Lib/Platforms/3ds/Main.cs
index 593acdc..a88e12c 100644
--- a/Mara.Lib/Platforms/3ds/Main.cs
+++ b/Mara.Lib/Platforms/3ds/Main.cs
@@ -1,12 +1,246 @@
-using System;
+using SceneGate.Lemon.Containers.Converters;
+using SceneGate.Lemon.Titles;
+using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Yarhl.FileFormat;
+using Yarhl.FileSystem;
+using Yarhl.IO;
namespace Mara.Lib.Platforms._3ds
{
- class Main
+ public class Main : PatchProcess
{
+ public PatchMode PatchMode { get; set; }
+ bool UseFileStream { get; set; }
+ Node Cia { get; set; }
+ Node ContentNode { get; set; }
+ Dictionary> ContentToPatch = new Dictionary>();
+
+ public Main(string oriFile, string outFile, string patchPath, PatchMode patchMode, bool useFileStream = false) : base(oriFile, outFile, patchPath)
+ {
+ PatchMode = patchMode;
+ UseFileStream = useFileStream;
+
+ try
+ {
+ Cia = NodeFactory.FromFile(oriFile, "root").TransformWith();
+ }
+ catch (Exception ex)
+ {
+ if (ex is FormatException)
+ {
+ throw new FormatException($"{Path.GetFileName(oriFile)} is an invalid CIA file.", ex);
+ }
+ else
+ {
+ throw new Exception($"An error has ocurred while reading the file.", ex);
+ }
+ }
+
+ ContentNode = Cia.Children["content"];
+
+ foreach (var content in ContentNode.Children)
+ {
+ if (content.Tags.ContainsKey("LEMON_NCCH_ENCRYPTED"))
+ {
+ throw new Exception("Encrypted (legit) CIA not supported");
+ }
+
+ for (int i = 0; i < maraConfig.FilesInfo.ListOriFiles.Length; i++)
+ {
+ var splitLine = maraConfig.FilesInfo.ListOriFiles[i].Split('\\');
+
+ if (splitLine[0] == content.Name)
+ {
+ if (ContentToPatch.ContainsKey(splitLine[0]))
+ {
+ if (!ContentToPatch[splitLine[0]].Contains(splitLine[1]))
+ {
+ ContentToPatch[splitLine[0]].Add(splitLine[1]);
+ }
+ }
+ else
+ {
+ ContentToPatch.Add(splitLine[0], new List { splitLine[1] });
+ }
+
+ if (patchMode == PatchMode.Specific)
+ {
+ switch(splitLine[1])
+ {
+ case "rom":
+ break;
+ case "system":
+ break;
+ default:
+ throw new FormatException($"Unsupported specific patching for {splitLine[1]}");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public override (int, string) ApplyTranslation()
+ {
+ var count = maraConfig.FilesInfo.ListOriFiles.Length;
+ var files = maraConfig.FilesInfo;
+
+ switch (PatchMode)
+ {
+ case PatchMode.General:
+ foreach (var contentToPatch in ContentToPatch)
+ {
+ var contentChildNode = ContentNode.Children[contentToPatch.Key];
+ contentChildNode.TransformWith();
+
+ foreach (var node in Navigator.IterateNodes(contentChildNode))
+ {
+ node.Stream.WriteTo(tempFolder + Path.DirectorySeparatorChar + contentToPatch.Key + Path.DirectorySeparatorChar + node.Name);
+ }
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ var file = $"{tempFolder}{Path.DirectorySeparatorChar}{files.ListOriFiles[i]}";
+ var xdelta = $"{tempFolder}{Path.DirectorySeparatorChar}{files.ListXdeltaFiles[i]}";
+
+ var result = ApplyXdelta(file, xdelta, file, files.ListMd5Files[i], UseFileStream);
+
+ if (result.Item1 != 0)
+ return result;
+
+ var content = files.ListOriFiles[i].Split('\\')[0];
+ ContentNode.Children[content].Add(NodeFactory.FromFile(file, Path.GetFileName(files.ListOriFiles[i])));
+ }
+
+ foreach (var contentToPatch in ContentToPatch)
+ {
+ var contentChildNode = ContentNode.Children[contentToPatch.Key];
+ var contentChildPath = $"{tempFolder}{Path.DirectorySeparatorChar}{contentToPatch.Key}";
+ contentChildNode.TransformWith();
+
+ // Doing this reduces the amount of memory used.
+ Directory.Delete(contentChildPath, true);
+ contentChildNode.Stream.WriteTo(contentChildPath);
+
+ ContentNode.Add(NodeFactory.FromFile(contentChildPath, contentToPatch.Key));
+ }
+ break;
+ case PatchMode.Specific:
+ foreach (var contentToPatch in ContentToPatch)
+ {
+ var contentNode = ContentNode.Children[contentToPatch.Key];
+ contentNode.TransformWith();
+
+ foreach (var node in Navigator.IterateNodes(contentNode))
+ {
+ if (contentToPatch.Value.Contains(node.Name))
+ {
+ switch (node.Name)
+ {
+ case "rom":
+ node.TransformWith();
+ break;
+ case "system":
+ node.TransformWith();
+ break;
+ default:
+ continue;
+ }
+
+ foreach (var nodeChild in Navigator.IterateNodes(node))
+ {
+ var path = nodeChild.Path.Replace($"/root/content/{contentToPatch.Key}/", "").Replace('/', Path.DirectorySeparatorChar);
+
+ // Using node.Stream will return null for some reason
+ var nodeChildFromRoot = Navigator.SearchNode(Cia, nodeChild.Path);
+ if (!nodeChildFromRoot.IsContainer)
+ {
+ nodeChildFromRoot.Stream.WriteTo(tempFolder + Path.DirectorySeparatorChar + contentToPatch.Key + Path.DirectorySeparatorChar + path);
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ var file = $"{tempFolder}{Path.DirectorySeparatorChar}{files.ListOriFiles[i]}";
+ var xdelta = $"{tempFolder}{Path.DirectorySeparatorChar}{files.ListXdeltaFiles[i]}";
+
+ var result = ApplyXdelta(file, xdelta, file, files.ListMd5Files[i], UseFileStream);
+
+ if (result.Item1 != 0)
+ return result;
+
+ // We need to delete these since later we're going to make a new Node container with this folder
+ if (File.Exists($"{file}_ori"))
+ {
+ File.Delete($"{file}_ori");
+ }
+
+ if (File.Exists(xdelta))
+ {
+ File.Delete(xdelta);
+ }
+ }
+
+ foreach (var contentToPatch in ContentToPatch)
+ {
+ var contentPath = $"{tempFolder}{Path.DirectorySeparatorChar}{contentToPatch.Key}";
+
+ foreach (var contentChild in ContentToPatch[contentToPatch.Key])
+ {
+ var contentChildPath = $"{contentPath}{Path.DirectorySeparatorChar}{contentChild}";
+ ContentNode.Children[contentToPatch.Key].Add(NodeFactory.FromDirectory(contentChildPath, "*", contentChild, true));
+
+ switch (contentChild)
+ {
+ case "rom":
+ ContentNode.Children[contentToPatch.Key].Children[contentChild].TransformWith();
+ break;
+ case "system":
+ IConverter binaryConverter = new BinaryExeFs2NodeContainer();
+ var convertedNode = binaryConverter.Convert(ContentNode.Children[contentToPatch.Key].Children[contentChild].GetFormatAs());
+ ContentNode.Children[contentToPatch.Key].Children[contentChild].ChangeFormat(convertedNode);
+ break;
+ }
+ }
+
+ ContentNode.Children[contentToPatch.Key].TransformWith();
+
+ // Doing this reduces the amount of memory used.
+ Directory.Delete(contentPath, true);
+ ContentNode.Children[contentToPatch.Key].Stream.WriteTo(contentPath);
+ ContentNode.Children[contentToPatch.Key].Dispose();
+
+ ContentNode.Add(NodeFactory.FromFile(contentPath, contentToPatch.Key));
+ }
+ break;
+ }
+
+ Cia.TransformWith();
+
+ if (File.Exists(outFolder))
+ {
+ File.Delete(outFolder);
+ }
+
+ Cia.Stream.WriteTo(outFolder);
+ Cia.Stream?.Dispose();
+
+ return base.ApplyTranslation();
+ }
+ }
+
+ public enum PatchMode
+ {
+ General,
+ Specific
}
}
diff --git a/Mara.Lib/Platforms/PatchProcess.cs b/Mara.Lib/Platforms/PatchProcess.cs
index 5fa00e6..7a14faa 100644
--- a/Mara.Lib/Platforms/PatchProcess.cs
+++ b/Mara.Lib/Platforms/PatchProcess.cs
@@ -30,7 +30,7 @@ public virtual (int, string) ApplyTranslation()
}
- protected (int, string) ApplyXdelta(string file, string xdelta, string result, string md5)
+ protected (int, string) ApplyXdelta(string file, string xdelta, string result, string md5, bool deltaFileStream = false)
{
if (!File.Exists(file + "_ori"))
File.Move(file, file + "_ori");
@@ -50,7 +50,10 @@ public virtual (int, string) ApplyTranslation()
try
{
- Common.Xdelta.Apply(File.Open(file, FileMode.Open), File.ReadAllBytes(xdelta), result);
+ if (!deltaFileStream)
+ Common.Xdelta.Apply(File.Open(file, FileMode.Open), File.ReadAllBytes(xdelta), result);
+ else
+ Common.Xdelta.ApplyFileStream(file, xdelta, result);
}
catch (Exception e)
{
diff --git a/Mara.Patcher/Mara.Patcher.csproj b/Mara.Patcher/Mara.Patcher.csproj
index 80c7c77..eaee386 100644
--- a/Mara.Patcher/Mara.Patcher.csproj
+++ b/Mara.Patcher/Mara.Patcher.csproj
@@ -1,6 +1,6 @@
- net5.0
+ net6.0
diff --git a/Mara.Tester/Mara.Tester.csproj b/Mara.Tester/Mara.Tester.csproj
index 40d934e..4b60bcb 100644
--- a/Mara.Tester/Mara.Tester.csproj
+++ b/Mara.Tester/Mara.Tester.csproj
@@ -2,7 +2,7 @@
Exe
- net5.0
+ net6.0
diff --git a/Mara.Tester/Program.cs b/Mara.Tester/Program.cs
index 9b69850..4db6cc1 100644
--- a/Mara.Tester/Program.cs
+++ b/Mara.Tester/Program.cs
@@ -11,17 +11,7 @@ static void Main(string[] args)
if (args.Length != 4)
PrintInfo();
- // Check ori folder
- if (!CheckDirectoryOrFile(args[1], true))
- return;
-
- // Check result folder
- if (!CheckDirectoryOrFile(args[2], true))
- return;
-
- // Check zip file
- if (!CheckDirectoryOrFile(args[3], false))
- return;
+ CheckPaths(args[1], args[2], args[3], args[0]);
switch (args[0].ToUpper())
{
@@ -31,6 +21,12 @@ static void Main(string[] args)
case "PSVITA":
ImportVita(args[1], args[2], args[3]);
break;
+ case "3DS-GENERAL":
+ Import3dsGeneral(args[1], args[2], args[3]);
+ break;
+ case "3DS-SPECIFIC":
+ Import3dsSpecific(args[1], args[2], args[3]);
+ break;
default:
PrintInfo();
break;
@@ -50,6 +46,18 @@ private static void ImportPc(string oriFolder, string outFolder, string zipPatch
PrintResult(mainVita.ApplyTranslation());
}
+ private static void Import3dsGeneral(string oriFile, string outFile, string zipPatch)
+ {
+ var main3DS = new Lib.Platforms._3ds.Main(oriFile, outFile, zipPatch, Lib.Platforms._3ds.PatchMode.General);
+ PrintResult(main3DS.ApplyTranslation());
+ }
+
+ private static void Import3dsSpecific(string oriFile, string outFile, string zipPatch)
+ {
+ var main3DS = new Lib.Platforms._3ds.Main(oriFile, outFile, zipPatch, Lib.Platforms._3ds.PatchMode.Specific);
+ PrintResult(main3DS.ApplyTranslation());
+ }
+
private static void PrintResult((int, string) result)
{
Console.WriteLine($"RESULT CODE: {result.Item1}\n" +
@@ -64,9 +72,38 @@ private static void PrintInfo()
Console.WriteLine("Mara.Tester PSVITA \"ORIGINAL FILES FOLDER\" \"OUT PATCHED FILES FOLDER\" \"ZIP DATA PATH\"");
}
- private static bool CheckDirectoryOrFile(string path, bool isDirectory)
+ static void CheckPaths(string oriPath, string outPath, string zipPath, string patchPlatform)
{
- if (!isDirectory)
+ bool isDirectory = true;
+
+ switch (patchPlatform.ToUpper())
+ {
+ case "3DS-GENERAL":
+ isDirectory = false;
+ outPath = Path.GetDirectoryName(outPath);
+ break;
+ case "3DS-SPECIFIC":
+ isDirectory = false;
+ outPath = Path.GetDirectoryName(outPath);
+ break;
+ }
+
+ // Check ori folder/file
+ if (!CheckDirectoryOrFile(oriPath, isDirectory))
+ return;
+
+ // Check result folder
+ if (!CheckDirectoryOrFile(outPath, true, true))
+ return;
+
+ // Check zip file
+ if (!CheckDirectoryOrFile(zipPath, false))
+ return;
+ }
+
+ private static bool CheckDirectoryOrFile(string path, bool isDirectory, bool isOut = false)
+ {
+ if (!isDirectory && !isOut)
{
if (File.Exists(path))
return true;
@@ -75,6 +112,10 @@ private static bool CheckDirectoryOrFile(string path, bool isDirectory)
return false;
}
+ else if (!isDirectory && isOut)
+ {
+ path = Path.GetDirectoryName(path);
+ }
if (Directory.Exists(path))
return true;