(NPK): better handle segments compression.

don't compress segments when compression produces larger segments.
This commit is contained in:
morkt 2016-12-25 08:55:40 +04:00
parent 7a9a6a607f
commit 53129e43ba

View File

@ -237,13 +237,6 @@ namespace GameRes.Formats.NitroPlus
return key; return key;
} }
class NpkStoredEntry : NpkEntry
{
public byte[] RawName;
public byte[] CheckSum;
public bool IsSolid;
}
public override void Create (Stream output, IEnumerable<Entry> list, ResourceOptions options, public override void Create (Stream output, IEnumerable<Entry> list, ResourceOptions options,
EntryCallback callback) EntryCallback callback)
{ {
@ -285,8 +278,8 @@ namespace GameRes.Formats.NitroPlus
if (null != callback) if (null != callback)
callback (callback_count++, entry, arcStrings.MsgAddingFile); callback (callback_count++, entry, arcStrings.MsgAddingFile);
using (var file = File.OpenRead (entry.Name)) using (var writer = new NpkWriter (entry, output, aes))
CopyFile (file, entry, output, aes); writer.Write (DefaultSegmentSize);
} }
output.Position = 0; output.Position = 0;
var buffer = new byte[] { (byte)'N', (byte)'P', (byte)'K', (byte)'2', 1, 0, 0, 0 }; var buffer = new byte[] { (byte)'N', (byte)'P', (byte)'K', (byte)'2', 1, 0, 0, 0 };
@ -333,82 +326,153 @@ namespace GameRes.Formats.NitroPlus
} }
} }
void CopyFile (FileStream file, NpkStoredEntry entry, Stream archive, Aes aes) static readonly HashSet<string> SolidFiles = new HashSet<string> { ".png", ".jpg" };
{ static readonly HashSet<string> DisableCompression = new HashSet<string> { ".png", ".jpg", ".ogg" };
if (file.Length > uint.MaxValue)
throw new FileSizeException();
long input_size = file.Length;
entry.Offset = archive.Position;
entry.Size = entry.UnpackedSize = (uint)input_size;
if (0 == entry.Size)
{
entry.CheckSum = EmptyFileHash;
return;
} }
uint segment_size = DefaultSegmentSize; internal class NpkStoredEntry : NpkEntry
byte[] buffer = null; {
if (!entry.IsSolid) public byte[] RawName;
buffer = new byte[segment_size]; public byte[] CheckSum;
else public bool IsSolid;
}
internal sealed class NpkWriter : IDisposable
{
NpkStoredEntry m_entry;
FileStream m_input;
Stream m_archive;
CryptoStream m_checksum_stream;
Aes m_aes;
long m_remaining;
byte[] m_buffer;
public NpkWriter (NpkStoredEntry entry, Stream archive, Aes aes)
{
m_input = File.OpenRead (entry.Name);
m_archive = archive;
m_entry = entry;
m_aes = aes;
m_buffer = null;
}
static readonly byte[] EmptyFileHash = GetDefaultHash();
public void Write (uint segment_size)
{
long input_size = m_input.Length;
if (input_size > uint.MaxValue)
throw new FileSizeException();
m_entry.Offset = m_archive.Position;
m_entry.Size = m_entry.UnpackedSize = (uint)input_size;
if (0 == m_entry.Size)
{
m_entry.CheckSum = EmptyFileHash;
return;
}
if (!m_entry.IsSolid)
m_buffer = new byte[segment_size];
else if (input_size > segment_size)
segment_size = (uint)input_size; segment_size = (uint)input_size;
entry.Segments.Clear();
using (var sha256 = SHA256.Create()) using (var sha256 = SHA256.Create())
using (var checksum_stream = new CryptoStream (Stream.Null, sha256, CryptoStreamMode.Write)) using (m_checksum_stream = new CryptoStream (Stream.Null, sha256, CryptoStreamMode.Write))
{ {
long remaining = input_size; m_remaining = input_size;
while (remaining > 0) int segment_count = (int)((input_size + segment_size - 1) / segment_size);
m_entry.Segments.Clear();
m_entry.Segments.Capacity = segment_count;
for (int i = 0; i < segment_count; ++i)
{ {
int chunk_size = (int)Math.Min (remaining, segment_size); int chunk_size = (int)Math.Min (m_remaining, segment_size);
var segment = new NpkSegment var file_pos = m_input.Position;
var segment = WriteSegment (chunk_size, m_entry.IsPacked);
if (m_entry.IsPacked && !segment.IsCompressed)
{ {
Offset = archive.Position, // compressed segment is larger than uncompressed, rewrite
UnpackedSize = (uint)chunk_size, m_input.Position = file_pos;
}; m_archive.Position = segment.Offset;
using (var proxy = new ProxyStream (archive, true)) RewriteSegment (segment, chunk_size);
using (var encryptor = aes.CreateEncryptor()) m_archive.SetLength (m_archive.Position);
}
m_entry.Segments.Add (segment);
m_remaining -= segment.UnpackedSize;
}
m_checksum_stream.FlushFinalBlock();
m_entry.CheckSum = sha256.Hash;
}
}
NpkSegment WriteSegment (int chunk_size, bool compress)
{
var segment = new NpkSegment { Offset = m_archive.Position };
using (var proxy = new ProxyStream (m_archive, true))
using (var encryptor = m_aes.CreateEncryptor())
{ {
Stream output = new CryptoStream (proxy, encryptor, CryptoStreamMode.Write); Stream output = new CryptoStream (proxy, encryptor, CryptoStreamMode.Write);
var measure = new CountedStream (output); var measure = new CountedStream (output);
output = measure; output = measure;
if (entry.IsPacked) if (compress)
output = new DeflateStream (output, CompressionLevel.Optimal); output = new DeflateStream (output, CompressionLevel.Optimal);
using (output) using (output)
{ {
if (remaining == chunk_size) if (m_remaining == chunk_size)
{ {
var pos = file.Position; var file_pos = m_input.Position;
file.CopyTo (output); m_input.CopyTo (output);
file.Position = pos; m_input.Position = file_pos;
file.CopyTo (checksum_stream); m_input.CopyTo (m_checksum_stream);
} }
else else
{ {
chunk_size = file.Read (buffer, 0, chunk_size); chunk_size = m_input.Read (m_buffer, 0, chunk_size);
output.Write (buffer, 0, chunk_size); output.Write (m_buffer, 0, chunk_size);
checksum_stream.Write (buffer, 0, chunk_size); m_checksum_stream.Write (m_buffer, 0, chunk_size);
} }
} }
segment.UnpackedSize = (uint)chunk_size;
segment.Size = (uint)measure.Count; segment.Size = (uint)measure.Count;
segment.AlignedSize = (uint)(archive.Position - segment.Offset); segment.AlignedSize = (uint)(m_archive.Position - segment.Offset);
entry.Segments.Add (segment); return segment;
}
remaining -= chunk_size;
}
checksum_stream.FlushFinalBlock();
entry.CheckSum = sha256.Hash;
} }
} }
static readonly HashSet<string> SolidFiles = new HashSet<string> { ".png", ".jpg" }; void RewriteSegment (NpkSegment segment, int chunk_size)
static readonly HashSet<string> DisableCompression = new HashSet<string> { ".png", ".jpg", ".ogg" }; {
static readonly byte[] EmptyFileHash = GetDefaultHash(); using (var proxy = new ProxyStream (m_archive, true))
using (var encryptor = m_aes.CreateEncryptor())
using (var output = new CryptoStream (proxy, encryptor, CryptoStreamMode.Write))
{
if (m_remaining == chunk_size)
{
m_input.CopyTo (output);
}
else
{
chunk_size = m_input.Read (m_buffer, 0, chunk_size);
output.Write (m_buffer, 0, chunk_size);
}
}
segment.UnpackedSize = segment.Size = (uint)chunk_size;
segment.AlignedSize = (uint)(m_archive.Position - segment.Offset);
}
static byte[] GetDefaultHash () static byte[] GetDefaultHash ()
{ {
using (var sha256 = SHA256.Create()) using (var sha256 = SHA256.Create())
return sha256.ComputeHash (new byte[0]); return sha256.ComputeHash (new byte[0]);
} }
bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
m_input.Dispose();
_disposed = true;
}
GC.SuppressFinalize (this);
}
} }
internal class NpkStream : Stream internal class NpkStream : Stream