diff --git a/ArcFormats/ArcDRS.cs b/ArcFormats/ArcDRS.cs new file mode 100644 index 00000000..b84bc968 --- /dev/null +++ b/ArcFormats/ArcDRS.cs @@ -0,0 +1,92 @@ +//! \file ArcDRS.cs +//! \date Thu Aug 21 06:11:09 2014 +//! \brief Digital Romance System archive implementation. +// +// Copyright (C) 2014 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.IO; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using GameRes.Formats.Strings; + +namespace GameRes.Formats.DRS +{ + [Export(typeof(ArchiveFormat))] + public class DrsOpener : ArchiveFormat + { + public override string Tag { get { return "DRS"; } } + public override string Description { get { return "Digital Romance System resource archive"; } } + public override uint Signature { get { return 0; } } + public override bool IsHierarchic { get { return false; } } + public override bool CanCreate { get { return false; } } + + public DrsOpener () + { + Extensions = new string[0]; // DRS archives have no extensions + } + + public override ArcFile TryOpen (ArcView file) + { + if (file.MaxOffset > uint.MaxValue) + return null; + int dir_size = file.View.ReadUInt16 (0); + if (dir_size < 0x20 || 0 != (dir_size & 0xf) || dir_size + 2 >= file.MaxOffset) + return null; + byte first = file.View.ReadByte (2); + if (0 == first) + return null; + file.View.Reserve (0, (uint)dir_size + 2); + int dir_offset = 2; + + uint next_offset = file.View.ReadUInt32 (dir_offset+12); + if (next_offset > file.MaxOffset) + return null; + var encoding = Encodings.cp932.WithFatalFallback(); + byte[] name_raw = new byte[12]; + + int count = dir_size / 0x10 - 1; + var dir = new List (count); + for (int i = 0; i < count; ++i) + { + file.View.Read (dir_offset, name_raw, 0, 12); + int name_length = name_raw.Length; + while (name_length > 0 && 0 == name_raw[name_length-1]) + --name_length; + if (0 == name_length) + return null; + uint offset = next_offset; + dir_offset += 0x10; + next_offset = file.View.ReadUInt32 (dir_offset+12); + if (next_offset > file.MaxOffset) + return null; + string name = encoding.GetString (name_raw, 0, name_length).ToLowerInvariant(); + var entry = FormatCatalog.Instance.CreateEntry (name); + entry.Offset = offset; + entry.Size = next_offset - offset; + dir.Add (entry); + } + return new ArcFile (file, this, dir); + } + } +} diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index e50c5517..07fc84d9 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -57,6 +57,7 @@ + @@ -95,6 +96,7 @@ CreateYPFWidget.xaml + diff --git a/ArcFormats/ImageDRG.cs b/ArcFormats/ImageDRG.cs new file mode 100644 index 00000000..20abdfb7 --- /dev/null +++ b/ArcFormats/ImageDRG.cs @@ -0,0 +1,467 @@ +//! \file ImageDRG.cs +//! \date Fri Aug 22 05:58:40 2014 +//! \brief Digital Romance System image format implementation. +// +// Copyright (C) 2014 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Text; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace GameRes.Formats.DRS +{ + [Export(typeof(ImageFormat))] + public class DrgFormat : ImageFormat + { + public override string Tag { get { return "DRG"; } } + public override string Description { get { return "Digital Romance System image format"; } } + public override uint Signature { get { return ~0x4c4c5546u; } } // 'FULL' + + public DrgFormat () + { + Signatures = new uint[] { ~0x4c4c5546u, ~0x45555254u, ~0x48474948u, ~0x47363532u }; + } + + public override void Write (Stream file, ImageData image) + { + using (var writer = new Writer (file)) + { + writer.Pack (image.Bitmap); + } + } + + public override ImageMetaData ReadMetaData (Stream stream) + { + uint signature = ~FormatCatalog.ReadSignature (stream); + int bpp; + switch (signature) + { + case 0x4c4c5546: /* fall through */ + case 0x45555254: bpp = 24; break; + case 0x48474948: bpp = 16; break; + case 0x47363532: bpp = 8; break; + default: return null; + } + using (var input = new BinaryReader (stream, Encoding.ASCII, true)) + { + uint width, height; + if (8 != bpp) + { + width = input.ReadUInt16(); + height = input.ReadUInt16(); + } + else + { + width = input.ReadUInt32(); + height = input.ReadUInt32(); + } + return new ImageMetaData { + Width = width, + Height = height, + BPP = bpp, + }; + } + } + + public override ImageData Read (Stream file, ImageMetaData info) + { + PixelFormat format; + BitmapPalette bitmap_palette = null; + int stride = (int)info.Width*((info.BPP+7)/8); + if (8 == info.BPP) + { + format = PixelFormats.Indexed8; + var palette_data = new byte[0x400]; + if (palette_data.Length != file.Read (palette_data, 0, palette_data.Length)) + throw new InvalidFormatException(); + var palette = new Color[256]; + file.Position = 44; + for (int i = 0; i < 256; ++i) + { + palette[i] = Color.FromRgb (palette_data[i*4+2], palette_data[i*4+1], palette_data[i*4]); + } + bitmap_palette = new BitmapPalette (palette); + } + else + { + file.Position = 8; + if (24 == info.BPP) + format = PixelFormats.Bgr24; + else + format = PixelFormats.Bgr565; + } + var pixel_data = DecodeStream (file, stride*(int)info.Height); + if (null == pixel_data) + throw new InvalidFormatException(); + var bitmap = BitmapSource.Create ((int)info.Width, (int)info.Height, 96, 96, + format, bitmap_palette, pixel_data, (int)stride); + bitmap.Freeze(); + return new ImageData (bitmap, info); + } + + byte[] DecodeStream (Stream input, int pixel_count) + { + byte[] output = new byte[pixel_count]; + for (int out_pos = 0; pixel_count > 0; ) + { + int opcode = input.ReadByte (); + if (-1 == opcode) + break; + int count, src_offset; + switch (opcode) + { + case 0: + count = input.ReadByte (); + src_offset = out_pos - 3; + if (count < 0 || count * 3 > pixel_count || src_offset < 0) + return null; + for (int i = 0; i < count; ++i) + { + Array.Copy (output, src_offset, output, out_pos, 3); + out_pos += 3; + } + pixel_count -= count * 3; + break; + case 1: + count = 3 * input.ReadByte (); + src_offset = out_pos - 3 * input.ReadByte (); + if (count < 0 || count > pixel_count || src_offset < 0 || src_offset == out_pos) + return null; + CopyOverlapped (output, src_offset, out_pos, count); + out_pos += count; + pixel_count -= count; + break; + case 2: + { + count = 3 * input.ReadByte (); + int off_lo = input.ReadByte (); + int off_hi = input.ReadByte (); + src_offset = out_pos - 3 * (off_hi << 8 | off_lo); + if (count < 0 || count > pixel_count || src_offset < 0 || src_offset == out_pos) + return null; + CopyOverlapped (output, src_offset, out_pos, count); + out_pos += count; + pixel_count -= count; + break; + } + case 3: + count = 3; + src_offset = out_pos - 3 * input.ReadByte (); + if (count > pixel_count || src_offset < 0 || src_offset == out_pos) + return null; + Array.Copy (output, src_offset, output, out_pos, count); + out_pos += count; + pixel_count -= count; + break; + case 4: + { + count = 3; + int off_lo = input.ReadByte (); + int off_hi = input.ReadByte (); + src_offset = out_pos - 3 * (off_hi << 8 | off_lo); + if (count > pixel_count || src_offset < 0 || src_offset == out_pos) + return null; + Array.Copy (output, src_offset, output, out_pos, count); + out_pos += count; + pixel_count -= count; + break; + } + default: + count = 3*(opcode - 4); + if (count > pixel_count) + return null; + for (int i = 0; i < count; ++i) + { + output[out_pos++] = (byte)input.ReadByte (); + } + pixel_count -= count; + break; + } + } + return output; + } + + static internal void CopyOverlapped (byte[] data, int src, int dst, int count) + { + int preceding = dst-src; + while (count > 0) + { + if (preceding > count) + preceding = count; + Array.Copy (data, src, data, dst, preceding); + src = dst; + dst += preceding; + count -= preceding; + } + } + + internal class Writer : IDisposable + { + BinaryWriter m_out; + uint[] m_input; + + const int MaxWindowSize = 0xfffe; + const int MaxMatchSize = 0xff; + + struct WindowPosition + { + public ushort Offset; + public ushort Length; + } + + public Writer (Stream output) + { + m_out = new BinaryWriter (output, Encoding.ASCII, true); + } + + void WriteHeader (int width, int height) + { + m_out.Write (~0x4c4c5546u); + m_out.Write ((ushort)width); + m_out.Write ((ushort)height); + } + + void PrepareInput (BitmapSource bitmap) + { + int width = bitmap.PixelWidth; + int height = bitmap.PixelHeight; + int pixels = width*height; + m_input = new uint[pixels]; + if (bitmap.Format != PixelFormats.Bgr32) + { + var converted_bitmap = new FormatConvertedBitmap(); + converted_bitmap.BeginInit(); + converted_bitmap.Source = bitmap; + converted_bitmap.DestinationFormat = PixelFormats.Bgr32; + converted_bitmap.EndInit(); + bitmap = converted_bitmap; + } + unsafe + { + fixed (uint* buffer = m_input) + { + bitmap.CopyPixels (Int32Rect.Empty, (IntPtr)buffer, pixels*4, width*4); + } + } + WriteHeader (width, height); + } + + List m_buffer = new List(); + int m_buffer_size; + + Dictionary> m_dict = new Dictionary> (MaxWindowSize); + + public void Pack (BitmapSource bitmap) + { + PrepareInput (bitmap); + m_dict.Clear(); + m_buffer.Clear(); + m_buffer_size = 0; + int last = m_input.Length; + int current = 0; + int win_begin = current; + int win_end = current; + while (current != last) + { + int new_win_end = current; + int window_size = Math.Min (new_win_end - win_begin, MaxWindowSize); + int new_win_begin = new_win_end - window_size; + AdjustWindow (ref win_begin, ref win_end, new_win_begin, new_win_end); + var win_pos = FindLongest (win_begin, win_end, current, last); + if (win_pos.Length > 0) + { + Flush(); + WritePos (win_pos, current - win_pos.Offset); + current += win_pos.Length; + } + else + { + WritePixel (m_input[current++]); + } + } + Flush(); + } + + void AdjustWindow (ref int win_begin, ref int win_end, int new_begin, int new_end) + { + while (win_begin != new_begin) + { + var pixel = m_input[win_begin]; + SortedSet pos = m_dict[pixel]; + pos.Remove (win_begin); + if (0 == pos.Count) + m_dict.Remove (pixel); + ++win_begin; + } + while (win_end != new_end) + { + var pixel = m_input[win_end]; + SortedSet pos; + if (!m_dict.TryGetValue (pixel, out pos)) + { + pos = new SortedSet(); + m_dict[pixel] = pos; + } + pos.Add (win_end); + ++win_end; + } + } + + void WritePixel (uint pixel) + { + if (0xff-4 == m_buffer_size) + Flush(); + m_buffer.Add ((byte)pixel); + m_buffer.Add ((byte)(pixel >> 8)); + m_buffer.Add ((byte)(pixel >> 16)); + ++m_buffer_size; + } + + void Flush () + { + if (0 != m_buffer.Count) + { + m_out.Write ((byte)(m_buffer_size+4)); + foreach (var b in m_buffer) + m_out.Write (b); + m_buffer.Clear(); + m_buffer_size = 0; + } + } + + int Mismatch (int first1, int last1, int first2) + { + while (first1 != last1 && m_input[first1] == m_input[first2]) + { + ++first1; + ++first2; + } + return first2; + } + + WindowPosition FindLongest (int win_begin, int win_end, int buf_begin, int buf_end) + { + buf_end = Math.Min (buf_begin + MaxMatchSize, buf_end); + WindowPosition pos = new WindowPosition { Offset = 0, Length = 0 }; + if (win_begin == win_end) + return pos; + if (m_input[win_end-1] == m_input[buf_begin]) + { + pos.Offset = 1; + var match_end = Mismatch (buf_begin+1, buf_end, win_end); + pos.Length = (ushort)(match_end - (win_end-1)); + if (MaxMatchSize == pos.Length) + return pos; + } + SortedSet found; + if (m_dict.TryGetValue (m_input[buf_begin], out found)) + { + foreach (var win_pos in found) + { + var match_end = Mismatch (buf_begin+1, buf_end, win_pos+1); + int weight = match_end - win_pos; + int distance = buf_begin - win_pos; + if (weight > pos.Length || (weight == pos.Length && distance < pos.Offset)) + { + pos.Offset = (ushort)distance; + pos.Length = (ushort)weight; + if (MaxMatchSize == weight && distance < 0x100) + break; + } + } + } + return pos; + } + + void WritePos (WindowPosition pos, int buf) + { + if (1 == pos.Offset) + { + uint pixel = m_input[buf]; + if (buf+1 == m_input.Length || -1 == Array.FindIndex (m_input, buf+1, x => x != pixel)) + { + m_out.Write ((byte)0); + m_out.Write ((byte)pos.Length); + } + else + { + m_out.Write ((byte)1); + m_out.Write ((byte)pos.Length); + m_out.Write ((byte)1); + } + } + else if (1 == pos.Length) + { + if (pos.Offset < 0x100) + { + m_out.Write ((byte)3); + m_out.Write ((byte)pos.Offset); + } + else + { + m_out.Write ((byte)4); + m_out.Write (pos.Offset); + } + } + else if (pos.Offset < 0x100) + { + m_out.Write ((byte)1); + m_out.Write ((byte)pos.Length); + m_out.Write ((byte)pos.Offset); + } + else + { + m_out.Write ((byte)2); + m_out.Write ((byte)pos.Length); + m_out.Write (pos.Offset); + } + } + + #region IDisposable Members + bool disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + m_out.Dispose(); + } + disposed = true; + } + } + #endregion + } + } +}