diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index e4a716ef..f8e37d86 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -204,6 +204,10 @@ + + + + diff --git a/ArcFormats/VnMaker/ArcZIP.cs b/ArcFormats/VnMaker/ArcZIP.cs new file mode 100644 index 00000000..e8ac81f5 --- /dev/null +++ b/ArcFormats/VnMaker/ArcZIP.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; + +using SharpZip = ICSharpCode.SharpZipLib.Zip; + +namespace GameRes.Formats.VnMaker +{ + internal class ZipEntry : PackedEntry + { + public readonly SharpZip.ZipEntry NativeEntry; + + public ZipEntry (SharpZip.ZipEntry zip_entry) + { + NativeEntry = zip_entry; + Name = zip_entry.Name; + Type = FormatCatalog.Instance.GetTypeFromName (zip_entry.Name); + IsPacked = true; + // design decision of having 32bit entry sizes was made early during GameRes + // library development. nevertheless, large files will be extracted correctly + // despite the fact that size is reported as uint.MaxValue, because extraction is + // performed by .Net framework based on real size value. + Size = (uint)Math.Min (zip_entry.CompressedSize, uint.MaxValue); + UnpackedSize = (uint)Math.Min (zip_entry.Size, uint.MaxValue); + Offset = zip_entry.Offset; + } + } + + internal class PkZipArchive : ArcFile + { + readonly SharpZip.ZipFile m_zip; + + public SharpZip.ZipFile Native { get { return m_zip; } } + + public PkZipArchive (ArcView arc, ArchiveFormat impl, ICollection dir, SharpZip.ZipFile native) + : base (arc, impl, dir) + { + m_zip = native; + } + + #region IDisposable implementation + bool _zip_disposed = false; + protected override void Dispose (bool disposing) + { + if (!_zip_disposed) + { + if (disposing) + m_zip.Close(); + _zip_disposed = true; + } + base.Dispose (disposing); + } + #endregion + } + + [Export(typeof(ArchiveFormat))] + public class ZipOpener : ArchiveFormat + { + public override string Tag { get { return "ZIP/VnMaker Encrypted ZIP Archive"; } } + public override string Description { get { return "VnMaker Encrypted ZIP Archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return true; } } + public override bool CanWrite { get { return false; } } + + public ZipOpener () + { + Settings = new[] { ZipEncoding }; + Extensions = new string[] { "zip" }; + } + + readonly EncodingSetting ZipEncoding = new EncodingSetting ("ZIPEncodingCP", "DefaultEncoding"); + + public override ArcFile TryOpen (ArcView file) + { + var input = file.CreateStream (); + try + { + var zip = DeobfuscateStream (input, GuessEncryptionKey (input)); + if ((zip.Signature & 0xFFFF) != 0x4B50) // 'PK' + throw new InvalidFormatException (); + return OpenZipArchive (file, zip.AsStream); + } + catch + { + input.Dispose (); + throw; + } + } + + byte[] GuessEncryptionKey (IBinaryStream file) + { + return new byte[] { 0x0A, 0x2B, 0x36, 0x6F, 0x0B }; + } + + IBinaryStream DeobfuscateStream (IBinaryStream file, byte[] key) + { + var zip = new ByteStringEncryptedStream (file.AsStream, key, true); + return new BinaryStream (zip, file.Name); + } + + internal ArcFile OpenZipArchive (ArcView file, Stream input) + { + SharpZip.ZipStrings.CodePage = Properties.Settings.Default.ZIPEncodingCP; + var zip = new SharpZip.ZipFile (input); + try + { + var files = zip.Cast().Where (z => !z.IsDirectory); + var dir = files.Select (z => new ZipEntry (z) as Entry).ToList(); + return new PkZipArchive (file, this, dir, zip); + } + catch + { + zip.Close(); + throw; + } + } + + public override Stream OpenEntry (ArcFile arc, Entry entry) + { + var zarc = (PkZipArchive)arc; + var zent = (ZipEntry)entry; + return zarc.Native.GetInputStream (zent.NativeEntry); + } + } +} diff --git a/ArcFormats/VnMaker/AudioMP3.cs b/ArcFormats/VnMaker/AudioMP3.cs new file mode 100644 index 00000000..4f194a0c --- /dev/null +++ b/ArcFormats/VnMaker/AudioMP3.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.Composition; + +namespace GameRes.Formats.VnMaker +{ + [Export(typeof(AudioFormat))] + public class AudioMP3 : Mp3Audio + { + public override string Tag { get { return "MP3/VnMaker Encrypted MP3 Audio"; } } + public override string Description { get { return "VnMaker Encrypted MP3 Audio"; } } + public override uint Signature { get { return 0; } } + public override bool CanWrite { get { return false; } } + + public AudioMP3() : base() + { + Signatures = new uint[] { 0 }; + Extensions = new[] { "mp3" }; + } + + public override SoundInput TryOpen (IBinaryStream file) + { + using (var input = DeobfuscateStream (file, GuessEncryptionKey (file))) + { + return base.TryOpen (input); + } + } + + byte[] GuessEncryptionKey (IBinaryStream file) + { + return new byte[] { 0x0A, 0x2B, 0x36, 0x6F, 0x0B }; + } + + IBinaryStream DeobfuscateStream (IBinaryStream file, byte[] key) + { + var mp3 = new ByteStringEncryptedStream (file.AsStream, key, true); + return new BinaryStream (mp3, file.Name); + } + } +} diff --git a/ArcFormats/VnMaker/AudioOGG.cs b/ArcFormats/VnMaker/AudioOGG.cs new file mode 100644 index 00000000..eb374030 --- /dev/null +++ b/ArcFormats/VnMaker/AudioOGG.cs @@ -0,0 +1,40 @@ +using System.ComponentModel.Composition; + +namespace GameRes.Formats.VnMaker +{ + [Export(typeof(AudioFormat))] + public class AudioOGG : AudioFormat + { + public override string Tag { get { return "OGG/VnMaker Encrypted OGG Audio"; } } + public override string Description { get { return "VnMaker Encrypted OGG Audio"; } } + public override uint Signature { get { return 0; } } + public override bool CanWrite { get { return false; } } + + public AudioOGG() : base() + { + Signatures = new uint[] { 0 }; + Extensions = new[] { "ogg" }; + } + + public override SoundInput TryOpen (IBinaryStream file) + { + using (var input = DeobfuscateStream (file, GuessEncryptionKey (file))) + { + if (input.Signature != 0x5367674F) + throw new InvalidFormatException (); + return new OggInput (input.AsStream); + } + } + + byte[] GuessEncryptionKey (IBinaryStream file) + { + return new byte[] { 0x0A, 0x2B, 0x36, 0x6F, 0x0B }; + } + + IBinaryStream DeobfuscateStream (IBinaryStream file, byte[] key) + { + var ogg = new ByteStringEncryptedStream (file.AsStream, key, true); + return new BinaryStream (ogg, file.Name); + } + } +} diff --git a/ArcFormats/VnMaker/ImagePNG.cs b/ArcFormats/VnMaker/ImagePNG.cs new file mode 100644 index 00000000..6e517f57 --- /dev/null +++ b/ArcFormats/VnMaker/ImagePNG.cs @@ -0,0 +1,61 @@ +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes.Formats.VnMaker +{ + [Export(typeof(ImageFormat))] + public class PngFormat : GameRes.PngFormat + { + public override string Tag { get { return "PNG/VnMaker Encrypted PNG Image"; } } + public override string Description { get { return "VnMaker Encrypted PNG Image"; } } + public override uint Signature { get { return 0; } } + public override bool CanWrite { get { return true; } } + + public PngFormat() : base() + { + Signatures = new uint[] { 0 }; + Extensions = new[] { "png" }; + } + + public override ImageMetaData ReadMetaData (IBinaryStream file) + { + using (var input = DeobfuscateStream (file, GuessEncryptionKey (file))) + { + if (input.Signature != 0x474E5089) + throw new InvalidFormatException (); + return base.ReadMetaData (input); + } + } + + public override ImageData Read (IBinaryStream file, ImageMetaData info) + { + using (var input = DeobfuscateStream (file, GuessEncryptionKey (file))) + { + if (input.Signature != 0x474E5089) + throw new InvalidFormatException (); + return base.Read (input, info); + } + } + + byte[] GuessEncryptionKey (IBinaryStream file) + { + return new byte[] { 0x0A, 0x2B, 0x36, 0x6F, 0x0B }; + } + + IBinaryStream DeobfuscateStream (IBinaryStream file, byte[] key) + { + var png = new ByteStringEncryptedStream (file.AsStream, key, true); + return new BinaryStream (png, file.Name); + } + + public override void Write (Stream file, ImageData image) + { + var ms = new MemoryStream (); + base.Write (ms, image); + ms.Position = 0; + var es = new ByteStringEncryptedStream (ms, GuessEncryptionKey (null)); + es.CopyTo (file); + file.Flush (); + } + } +}