diff --git a/ParLibrary/Converter/ParArchiveReader.cs b/ParLibrary/Converter/ParArchiveReader.cs
index 8a3967c..27d3a0c 100644
--- a/ParLibrary/Converter/ParArchiveReader.cs
+++ b/ParLibrary/Converter/ParArchiveReader.cs
@@ -1,11 +1,12 @@
// -------------------------------------------------------
-// © Kaplas. Licensed under MIT. See LICENSE for details.
+// © Kaplas, Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
// -------------------------------------------------------
namespace ParLibrary.Converter
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+ using System.IO;
using System.Text;
using Yarhl.FileFormat;
using Yarhl.FileSystem;
@@ -43,6 +44,18 @@ public NodeContainerFormat Convert(BinaryFormat source)
var result = new NodeContainerFormat();
+ if (source.Stream.Length == 0)
+ {
+ if (this.parameters.AllowZeroLengthPars)
+ {
+ return result;
+ }
+ else
+ {
+ throw new InvalidDataException("PAR stream is zero bytes long and cannot be read.");
+ }
+ }
+
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var reader = new DataReader(source.Stream)
diff --git a/ParLibrary/Converter/ParArchiveReaderParameters.cs b/ParLibrary/Converter/ParArchiveReaderParameters.cs
index ea881ef..4c51a72 100644
--- a/ParLibrary/Converter/ParArchiveReaderParameters.cs
+++ b/ParLibrary/Converter/ParArchiveReaderParameters.cs
@@ -1,5 +1,5 @@
// -------------------------------------------------------
-// © Kaplas. Licensed under MIT. See LICENSE for details.
+// © Kaplas, Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
// -------------------------------------------------------
namespace ParLibrary.Converter
{
@@ -12,5 +12,10 @@ public class ParArchiveReaderParameters
/// Gets or sets a value indicating whether the reading is recursive.
///
public bool Recursive { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether zero-length PARs cause an error or not.
+ ///
+ public bool AllowZeroLengthPars { get; set; }
}
}
diff --git a/ParTool/Program.Add.cs b/ParTool/Program.Add.cs
index e9f14b3..53c4cc4 100644
--- a/ParTool/Program.Add.cs
+++ b/ParTool/Program.Add.cs
@@ -1,5 +1,5 @@
// -------------------------------------------------------
-// © Kaplas. Licensed under MIT. See LICENSE for details.
+// © Kaplas, Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
// -------------------------------------------------------
namespace ParTool
{
@@ -46,6 +46,9 @@ private static void Add(Options.Add opts)
var readerParameters = new ParArchiveReaderParameters
{
Recursive = true,
+
+ // If we encounter a zero-length PAR at any point below the top level, we treat it as an empty directory.
+ AllowZeroLengthPars = true,
};
var writerParameters = new ParArchiveWriterParameters
@@ -56,8 +59,17 @@ private static void Add(Options.Add opts)
Console.Write("Reading PAR file... ");
Node par = NodeFactory.FromFile(opts.InputParArchivePath, Yarhl.IO.FileOpenMode.Read);
+
+ // Warn the user if the top-level PAR they're using is a zero-length file.
+ // If it is, we can't infer the IncludeDots parameter.
+ if (par.Stream.Length == 0)
+ {
+ Console.WriteLine($"ERROR: \"{opts.InputParArchivePath}\" is an empty file, and contains no data. Use `ParTool.exe create` instead.");
+ return;
+ }
+
par.TransformWith(readerParameters);
- writerParameters.IncludeDots = par.Children[0].Name == ".";
+ writerParameters.IncludeDots = (par.Children.Count > 0) && par.Children[0].Name == ".";
Console.WriteLine("DONE!");
Console.Write("Reading input directory... ");
diff --git a/ParTool/Program.Extract.cs b/ParTool/Program.Extract.cs
index 561a538..367dfaf 100644
--- a/ParTool/Program.Extract.cs
+++ b/ParTool/Program.Extract.cs
@@ -1,5 +1,5 @@
// -------------------------------------------------------
-// © Kaplas. Licensed under MIT. See LICENSE for details.
+// © Kaplas, Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
// -------------------------------------------------------
namespace ParTool
{
@@ -42,9 +42,20 @@ private static void Extract(Options.Extract opts)
var parameters = new ParArchiveReaderParameters
{
Recursive = opts.Recursive,
+
+ // If we encounter a zero-length PAR at any point, we treat it as an empty directory.
+ AllowZeroLengthPars = true,
};
using Node par = NodeFactory.FromFile(opts.ParArchivePath, Yarhl.IO.FileOpenMode.Read);
+
+ // For convenience, warn the user if the top-level PAR they're using is a zero-length file.
+ // We still use the AllowZeroLengthPARs parameter, in case a non-zero-length PAR contains a zero-length PAR and we're reading in recursive mode.
+ if (par.Stream.Length == 0)
+ {
+ Console.WriteLine($"WARNING: \"{opts.ParArchivePath}\" is an empty file, and contains no data.");
+ }
+
par.TransformWith(parameters);
Extract(par, opts.OutputDirectory);
diff --git a/ParTool/Program.List.cs b/ParTool/Program.List.cs
index 551df0a..0c442db 100644
--- a/ParTool/Program.List.cs
+++ b/ParTool/Program.List.cs
@@ -1,5 +1,5 @@
// -------------------------------------------------------
-// © Kaplas. Licensed under MIT. See LICENSE for details.
+// © Kaplas, Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
// -------------------------------------------------------
namespace ParTool
{
@@ -27,9 +27,20 @@ private static void List(Options.List opts)
var parameters = new ParArchiveReaderParameters
{
Recursive = opts.Recursive,
+
+ // If we encounter a zero-length PAR at any point, we treat it as an empty directory.
+ AllowZeroLengthPars = true,
};
using Node par = NodeFactory.FromFile(opts.ParArchivePath, Yarhl.IO.FileOpenMode.Read);
+
+ // For convenience, warn the user if the top-level PAR they're using is a zero-length file.
+ // We still use the AllowZeroLengthPARs parameter, in case a non-zero-length PAR contains a zero-length PAR and we're reading in recursive mode.
+ if (par.Stream.Length == 0)
+ {
+ Console.WriteLine($"WARNING: \"{opts.ParArchivePath}\" is an empty file, and contains no data.");
+ }
+
par.TransformWith(parameters);
foreach (Node node in Navigator.IterateNodes(par))
diff --git a/ParTool/Program.Remove.cs b/ParTool/Program.Remove.cs
index 0b10edf..4924dc5 100644
--- a/ParTool/Program.Remove.cs
+++ b/ParTool/Program.Remove.cs
@@ -1,5 +1,5 @@
// -------------------------------------------------------
-// © Kaplas. Licensed under MIT. See LICENSE for details.
+// © Kaplas, Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
// -------------------------------------------------------
namespace ParTool
{
@@ -40,6 +40,9 @@ private static void Remove(Options.Remove opts)
var readerParameters = new ParArchiveReaderParameters
{
Recursive = true,
+
+ // If we encounter a zero-length PAR at any point, we treat it as an empty directory.
+ AllowZeroLengthPars = true,
};
using Node par = NodeFactory.FromFile(opts.InputParArchivePath, Yarhl.IO.FileOpenMode.Read);
diff --git a/Tests/ParLib.UnitTests/ParLib.UnitTests.csproj b/Tests/ParLib.UnitTests/ParLib.UnitTests.csproj
index 6210552..fdce2c7 100644
--- a/Tests/ParLib.UnitTests/ParLib.UnitTests.csproj
+++ b/Tests/ParLib.UnitTests/ParLib.UnitTests.csproj
@@ -19,4 +19,10 @@
+
+
+ Always
+
+
+
diff --git a/Tests/ParLib.UnitTests/ZeroLengthPar.cs b/Tests/ParLib.UnitTests/ZeroLengthPar.cs
new file mode 100644
index 0000000..3ca209c
--- /dev/null
+++ b/Tests/ParLib.UnitTests/ZeroLengthPar.cs
@@ -0,0 +1,115 @@
+// -------------------------------------------------------
+// © Samuel W. Stark (TheTurboTurnip). Licensed under MIT. See LICENSE for details.
+// -------------------------------------------------------
+namespace ParLib.UnitTests
+{
+ using System.IO;
+ using NUnit.Framework;
+ using ParLibrary.Converter;
+ using Yarhl.FileSystem;
+
+ public class ZeroLengthPar
+ {
+ ///
+ /// Test that reading a 0B PAR file returns an empty node when using AllowZeroLengthPars = true.
+ ///
+ [Test]
+ public void ZeroLengthParIsEmptyNodeWhenAllowed()
+ {
+ var readerParameters = new ParArchiveReaderParameters
+ {
+ Recursive = true,
+
+ // If we encounter a zero-length PAR at any point, we treat it as an empty directory.
+ AllowZeroLengthPars = true,
+ };
+
+ // This creates an empty BinaryStream for the Node, so it's a 0b file
+ Node test_0b_par = NodeFactory.FromMemory("test_0b_par.par");
+ test_0b_par.TransformWith(readerParameters);
+
+ Assert.AreEqual(0, test_0b_par.Children.Count);
+ }
+
+ ///
+ /// Test that reading a 0B PAR file throws an exception when using AllowZeroLengthPars = false.
+ ///
+ [Test]
+ public void ZeroLengthParThrowsWhenNotAllowed()
+ {
+ var readerParameters = new ParArchiveReaderParameters
+ {
+ Recursive = true,
+
+ AllowZeroLengthPars = false,
+ };
+
+ // This creates an empty BinaryStream for the Node, so it's a 0b file
+ Node test_0b_par = NodeFactory.FromMemory("test_0b_par.par");
+
+ Assert.Throws(() => test_0b_par.TransformWith(readerParameters));
+ }
+
+ ///
+ /// Test that when recursively reading a PAR that *contains* a 0B PAR file, that the 0B par is treated as an empty directory with the correct name.
+ ///
+ [Test]
+ public void ParContainingZeroLengthParHasNodeWhenAllowed()
+ {
+ var readerParameters = new ParArchiveReaderParameters
+ {
+ Recursive = true,
+
+ AllowZeroLengthPars = true,
+ };
+
+ // This loads the file stored in Tests/ParLib.UnitTests
+ Node test_par_containing_0b_par = NodeFactory.FromFile("test_par_containing_0b_par.par", Yarhl.IO.FileOpenMode.Read);
+ test_par_containing_0b_par.TransformWith(readerParameters);
+
+ // The toplevel par should have one child (it's a directory)
+ Assert.AreEqual(1, test_par_containing_0b_par.Children.Count);
+
+ // That child should be '.', which should *also* have one child.
+ // (I exported this test file with IncludeDots = true).
+ Assert.AreEqual(".", test_par_containing_0b_par.Children[0].Name);
+ Assert.AreEqual(1, test_par_containing_0b_par.Children[0].Children.Count);
+
+ // That child should be test_0kb_par.par
+ var test_0kb_par = test_par_containing_0b_par.Children[0].Children[0];
+ Assert.AreEqual("test_0kb_par.par", test_0kb_par.Name);
+
+ // test_0kb_par.par should have no children
+ Assert.AreEqual(0, test_0kb_par.Children.Count);
+
+ // Overall the listing is
+ // /test_par_containing_0b_par.par
+ // /test_par_containing_0b_par.par/./
+ // /test_par_containing_0b_par.par/./test_0kb_par/
+ }
+
+ ///
+ /// Test that reading a PAR *containing* 0B PAR file throws an exception when using AllowZeroLengthPars = false.
+ ///
+ /// This is not necessarily desirable behaviour, and at time of writing there isn't a nice way to surface the error to the user.
+ /// "test_par_containing_0b_par.par is fine, but it contains test_0b_par.par and that is bad!"
+ ///
+ /// This test is meant to document the current behaviour, and if the behaviour is improved then it should be changed or removed.
+ ///
+ [Test]
+ public void ParContainingZeroLengthParThrowsWhenNotAllowed()
+ {
+ var readerParameters = new ParArchiveReaderParameters
+ {
+ Recursive = true,
+
+ AllowZeroLengthPars = false,
+ };
+
+ // This loads the file stored in Tests/ParLib.UnitTests
+ Node test_par_containing_0b_par = NodeFactory.FromFile("test_par_containing_0b_par.par", Yarhl.IO.FileOpenMode.Read);
+
+ Assert.Throws(() => test_par_containing_0b_par.TransformWith(readerParameters));
+ }
+ }
+}
diff --git a/Tests/ParLib.UnitTests/test_par_containing_0b_par.par b/Tests/ParLib.UnitTests/test_par_containing_0b_par.par
new file mode 100644
index 0000000..548fac1
Binary files /dev/null and b/Tests/ParLib.UnitTests/test_par_containing_0b_par.par differ