diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index 62839f43..40710671 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -68,6 +68,7 @@
+
diff --git a/ArcFormats/ImagePSD.cs b/ArcFormats/ImagePSD.cs
new file mode 100644
index 00000000..c1399cb2
--- /dev/null
+++ b/ArcFormats/ImagePSD.cs
@@ -0,0 +1,250 @@
+//! \file ImagePSD.cs
+//! \date Tue Nov 17 18:22:36 2015
+//! \brief Adobe Photoshop file format implementation.
+//
+// Copyright (C) 2015 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 GameRes.Utility;
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Text;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace GameRes.Formats.Adobe
+{
+ internal class PsdMetaData : ImageMetaData
+ {
+ public int Channels;
+ public int Mode;
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class PsdFormat : ImageFormat
+ {
+ public override string Tag { get { return "PSD"; } }
+ public override string Description { get { return "Adobe Photoshop image format"; } }
+ public override uint Signature { get { return 0x53504238; } } // '8BPS'
+
+ public override ImageMetaData ReadMetaData (Stream stream)
+ {
+ var header = new byte[26];
+ if (header.Length != stream.Read (header, 0, header.Length))
+ return null;
+ int version = BigEndian.ToInt16 (header, 4);
+ if (1 != version)
+ return null;
+ int channels = BigEndian.ToInt16 (header, 0x0C);
+ if (channels < 1 || channels > 56)
+ return null;
+ uint height = BigEndian.ToUInt32 (header, 0x0E);
+ uint width = BigEndian.ToUInt32 (header, 0x12);
+ if (width < 1 || width > 30000 || height < 1 || height > 30000)
+ return null;
+ int bpc = BigEndian.ToInt16 (header, 0x16);
+ return new PsdMetaData
+ {
+ Width = width,
+ Height = height,
+ Channels = channels,
+ BPP = channels * bpc,
+ Mode = BigEndian.ToInt16 (header, 0x18),
+ };
+ }
+
+ public override ImageData Read (Stream stream, ImageMetaData info)
+ {
+ var meta = (PsdMetaData)info;
+ using (var reader = new PsdReader (stream, meta))
+ {
+ reader.Unpack();
+ return ImageData.Create (info, reader.Format, reader.Palette, reader.Data);
+ }
+ }
+
+ public override void Write (Stream file, ImageData image)
+ {
+ throw new System.NotImplementedException ("PsdFormat.Write not implemented");
+ }
+ }
+
+ internal class PsdReader : IDisposable
+ {
+ BinaryReader m_input;
+ PsdMetaData m_info;
+ byte[] m_output;
+ int m_channel_size;
+
+ public PixelFormat Format { get; private set; }
+ public BitmapPalette Palette { get; private set; }
+ public byte[] Data { get { return m_output; } }
+
+ public PsdReader (Stream input, PsdMetaData info)
+ {
+ m_info = info;
+ m_input = new BinaryReader (input, Encoding.Unicode, true);
+ int bpc = m_info.BPP / m_info.Channels;
+ switch (m_info.Mode)
+ {
+ case 3:
+ if (8 != bpc)
+ throw new NotSupportedException();
+ if (3 == m_info.Channels)
+ Format = PixelFormats.Bgr24;
+ else if (4 == m_info.Channels)
+ Format = PixelFormats.Bgra32;
+ else
+ throw new NotSupportedException();
+ break;
+// case 2: Format = PixelFormats.Indexed8; break;
+ case 1:
+ if (8 == bpc)
+ Format = PixelFormats.Gray8;
+ else if (16 == bpc)
+ Format = PixelFormats.Gray16;
+ else
+ throw new NotSupportedException();
+ break;
+ case 0: Format = PixelFormats.BlackWhite; break;
+ default:
+ throw new NotImplementedException ("Not supported PSD color mode");
+ }
+ m_channel_size = (int)m_info.Height * (int)m_info.Width * bpc / 8;
+ }
+
+ public void Unpack ()
+ {
+ m_input.BaseStream.Position = 0x1A;
+ int color_data_length = Binary.BigEndian (m_input.ReadInt32());
+ long next_pos = m_input.BaseStream.Position + color_data_length;
+ if (0 != color_data_length)
+ {
+ if (8 == m_info.BPP)
+ ReadPalette (color_data_length);
+ m_input.BaseStream.Position = next_pos;
+ }
+ next_pos += 4 + Binary.BigEndian (m_input.ReadInt32());
+ m_input.BaseStream.Position = next_pos; // skip Image Resources
+ next_pos += 4 + Binary.BigEndian (m_input.ReadInt32());
+ m_input.BaseStream.Position = next_pos; // skip Layer and Mask Information
+
+ int compression = Binary.BigEndian (m_input.ReadInt16());
+ int remaining = checked((int)(m_input.BaseStream.Length - m_input.BaseStream.Position));
+ byte[] pixels;
+ if (0 == compression)
+ pixels = m_input.ReadBytes (remaining);
+ else if (1 == compression)
+ pixels = UnpackRLE();
+ else
+ throw new NotSupportedException ("PSD files with ZIP compression not supported");
+
+ if (1 == m_info.Channels)
+ {
+ m_output = pixels;
+ return;
+ }
+ int channels = Math.Min (4, m_info.Channels);
+ m_output = new byte[channels * m_channel_size];
+ int src = 0;
+ for (int ch = 0; ch < channels; ++ch)
+ {
+ int dst = ChannelMap[ch];
+ for (int i = 0; i < m_channel_size; ++i)
+ {
+ m_output[dst] = pixels[src++];
+ dst += channels;
+ }
+ }
+ }
+
+ static readonly byte[] ChannelMap = { 2, 1, 0, 3 };
+
+ void ReadPalette (int palette_size)
+ {
+ var palette_data = m_input.ReadBytes (palette_size);
+ if (palette_data.Length != palette_size)
+ throw new EndOfStreamException();
+ int colors = Math.Min (256, palette_size/3);
+ var palette = new Color[colors];
+ for (int i = 0; i < colors; ++i)
+ {
+ int c = i * 3;
+ palette[i] = Color.FromRgb (palette_data[c], palette_data[c+1], palette_data[c+2]);
+ }
+ Palette = new BitmapPalette (palette);
+ }
+
+ byte[] UnpackRLE ()
+ {
+ var scanlines = new int[m_info.Channels, (int)m_info.Height];
+ for (int ch = 0; ch < m_info.Channels; ++ch)
+ {
+ for (uint row = 0; row < m_info.Height; ++row)
+ scanlines[ch,row] = Binary.BigEndian (m_input.ReadInt16());
+ }
+ var pixels = new byte[m_info.Channels * m_channel_size];
+ int dst = 0;
+ for (int ch = 0; ch < m_info.Channels; ++ch)
+ {
+ for (uint row = 0; row < m_info.Height; ++row)
+ {
+ int line_count = scanlines[ch,row];
+ int n = 0;
+ while (n < line_count)
+ {
+ int count = m_input.ReadSByte();
+ ++n;
+ if (count >= 0)
+ {
+ ++count;
+ m_input.Read (pixels, dst, count);
+ dst += count;
+ n += count;
+ }
+ else if (count > -128)
+ {
+ count = 1 - count;
+ byte color = m_input.ReadByte();
+ ++n;
+ for (int i = 0; i < count; ++i)
+ pixels[dst++] = color;
+ }
+ }
+ }
+ }
+ return pixels;
+ }
+
+ #region IDisposable Members
+ bool _disposed = false;
+ public void Dispose ()
+ {
+ if (!_disposed)
+ {
+ m_input.Dispose();
+ _disposed = true;
+ }
+ }
+ #endregion
+ }
+}