diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj
index bde0d065b..035121934 100644
--- a/ArcFormats/ArcFormats.csproj
+++ b/ArcFormats/ArcFormats.csproj
@@ -161,6 +161,7 @@
+
@@ -225,6 +226,7 @@
+
diff --git a/ArcFormats/Guyzware/ArcDat.cs b/ArcFormats/Guyzware/ArcDat.cs
new file mode 100644
index 000000000..85c1c8efe
--- /dev/null
+++ b/ArcFormats/Guyzware/ArcDat.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.IO;
+using GameRes.Utility;
+
+namespace GameRes.Formats.Guyzware
+{
+ [Export(typeof(ArchiveFormat))]
+ public class GdpOpener : ArchiveFormat
+ {
+ public override string Tag => "DAT/GDP";
+ public override string Description => "Guyzware engine";
+ public override uint Signature => 0;
+ public override bool IsHierarchic => false;
+ public override bool CanWrite => false;
+
+ public override ArcFile TryOpen(ArcView file)
+ {
+ int count = file.View.ReadInt32(0);
+ if (!IsSaneCount(count))
+ return null;
+
+ var dir = new List(count);
+ long index_offset = 4;
+
+ // heuristic: small first byte = variable
+ byte checkByte = file.View.ReadByte(index_offset);
+ bool isVariableLength = checkByte < 64;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (index_offset >= file.MaxOffset)
+ break;
+
+ string name;
+ uint offset, size;
+
+ if (isVariableLength)
+ {
+ // Variable length:
+ // [1 byte length (ignored)] [Shift-JIS string] [00]
+
+ index_offset++; // skip length byte
+
+ var nameBytes = new List();
+
+ while (index_offset < file.MaxOffset)
+ {
+ byte b = file.View.ReadByte(index_offset++);
+ if (b == 0)
+ break;
+
+ nameBytes.Add(b);
+ }
+
+ name = Encodings.cp932.GetString(nameBytes.ToArray());
+ }
+ else
+ {
+ // Fixed 32 bytes
+
+ name = file.View.ReadString(index_offset, 32, Encodings.cp932);
+ name = name.TrimEnd('\0', ' ');
+ index_offset += 32;
+ }
+
+ offset = file.View.ReadUInt32(index_offset);
+ size = file.View.ReadUInt32(index_offset + 4);
+ index_offset += 8;
+
+ var entry = new Entry
+ {
+ Name = name,
+ Offset = offset,
+ Size = size,
+ };
+
+ if (!entry.CheckPlacement(file.MaxOffset))
+ return null;
+
+ dir.Add(entry);
+ }
+
+ return new ArcFile(file, this, dir);
+ }
+
+ public override Stream OpenEntry(ArcFile arc, Entry entry)
+ {
+ return arc.File.CreateStream(entry.Offset, entry.Size);
+ }
+ }
+}
diff --git a/ArcFormats/Psp/ArcGim.cs b/ArcFormats/Psp/ArcGim.cs
new file mode 100644
index 000000000..8e693261a
--- /dev/null
+++ b/ArcFormats/Psp/ArcGim.cs
@@ -0,0 +1,374 @@
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using GameRes.Utility;
+
+namespace GameRes.Formats.Psp
+{
+ internal class GimMetaData : ImageMetaData
+ {
+ public int ImageInfoOffset;
+ public int PaletteInfoOffset;
+ public int PaletteBlockEnd;
+ public bool IsLittleEndian;
+ public int Format;
+ public int Order; // 0=Linear, 1=Swizzled
+ public int ImgDataRelOffset;
+ public int PalDataRelOffset;
+ public int BufferWidth; // Aligned width
+ }
+
+ [Export(typeof(ImageFormat))]
+ public class GimFormat : ImageFormat
+ {
+ public override string Tag { get { return "GIM"; } }
+ public override string Description { get { return "Sony GIM Image"; } }
+ public override uint Signature { get { return 0; } }
+
+ public override ImageMetaData ReadMetaData(IBinaryStream file)
+ {
+ var header = file.ReadBytes(16);
+ if (header.Length < 16) return null;
+
+ // Header check for Endianness detection
+ bool littleEndian = true;
+ if (header[0] == 'M' && header[1] == 'I' && header[2] == 'G') littleEndian = true;
+ else if (header[0] == 'G' && header[1] == 'I' && header[2] == 'M') littleEndian = false;
+ else return null;
+
+ // Read the entire stream into an array to facilitate navigation
+ // GIMs are small, so this is safe and fast.
+ byte[] data = new byte[file.Length];
+ file.Position = 0;
+ file.Read(data, 0, (int)file.Length);
+
+ int imageInfoOffset = -1;
+ int paletteInfoOffset = -1;
+ int paletteBlockEnd = -1;
+ int offset = 0x10;
+ int loop = 0;
+
+ // Block scanning loop (Verviewer style)
+ while (offset + 0x10 <= data.Length && loop < 128)
+ {
+ ushort id = ReadUInt16(data, offset, littleEndian);
+ // 0x02=EndFile, 0x03=EndImage (Skip), 0x04=Image, 0x05=Palette, 0xFF=FileInfo
+ if (id == 0xFF) break;
+
+ uint size = ReadUInt32(data, offset + 4, littleEndian);
+ uint next = ReadUInt32(data, offset + 8, littleEndian);
+ uint headerSize = ReadUInt32(data, offset + 0xC, littleEndian);
+
+ if (size < headerSize || headerSize == 0) break; // Invalid data
+
+ // "Next" logic: If 0, usually the next block is sequential (size)
+ int nextOffset = (next != 0) ? (int)next : (int)size;
+
+ int blockStart = offset;
+ int blockEnd = blockStart + (int)size;
+ int subHeader = blockStart + (int)headerSize;
+
+ if (blockEnd > data.Length) break;
+
+ if (id == 4) // Image Section
+ {
+ if (imageInfoOffset < 0) imageInfoOffset = subHeader;
+ }
+ else if (id == 5) // Palette Section
+ {
+ if (paletteInfoOffset < 0)
+ {
+ paletteInfoOffset = subHeader;
+ paletteBlockEnd = blockEnd;
+ }
+ }
+
+ offset = blockStart + nextOffset;
+ loop++;
+ }
+
+ if (imageInfoOffset < 0) return null;
+
+ ushort imgFormat = ReadUInt16(data, imageInfoOffset + 4, littleEndian);
+ ushort pixelOrder = ReadUInt16(data, imageInfoOffset + 6, littleEndian);
+ ushort width = ReadUInt16(data, imageInfoOffset + 8, littleEndian);
+ ushort height = ReadUInt16(data, imageInfoOffset + 0xA, littleEndian);
+ ushort bpp = ReadUInt16(data, imageInfoOffset + 0xC, littleEndian);
+ uint imgRel = ReadUInt32(data, imageInfoOffset + 0x1C, littleEndian);
+ uint palRel = 0;
+
+ if (paletteInfoOffset > 0)
+ palRel = ReadUInt32(data, paletteInfoOffset + 0x1C, littleEndian);
+
+ // Texture Alignment (Buffer Width)
+ // PSP aligns in blocks of 16 bytes (128 bits)
+ int align = 16;
+ int pixelsPerBlock = (align * 8) / Math.Max(1, (int)bpp);
+ int bufferWidth = (width + pixelsPerBlock - 1) & ~(pixelsPerBlock - 1);
+
+ return new GimMetaData
+ {
+ Width = width,
+ Height = height,
+ BPP = bpp,
+ Format = imgFormat,
+ Order = pixelOrder,
+ BufferWidth = bufferWidth,
+ ImageInfoOffset = imageInfoOffset,
+ PaletteInfoOffset = paletteInfoOffset,
+ PaletteBlockEnd = paletteBlockEnd,
+ ImgDataRelOffset = (int)imgRel,
+ PalDataRelOffset = (int)palRel,
+ IsLittleEndian = littleEndian
+ };
+ }
+
+ public override ImageData Read(IBinaryStream file, ImageMetaData info)
+ {
+ var meta = (GimMetaData)info;
+
+ byte[] data = new byte[file.Length];
+ file.Position = 0;
+ file.Read(data, 0, (int)file.Length);
+
+ int imgDataOffset = meta.ImageInfoOffset + meta.ImgDataRelOffset;
+
+ // Safe pixel buffer reading
+ int width = (int)meta.Width;
+ int height = (int)meta.Height;
+ int bpp = meta.BPP;
+ int bufferWidth = meta.BufferWidth;
+
+ // Internal PSP Stride (can be larger than image width)
+ int bufferStride = (bufferWidth * bpp) / 8;
+ int totalBytes = bufferStride * height;
+
+ if (imgDataOffset + totalBytes > data.Length)
+ totalBytes = Math.Max(0, data.Length - imgDataOffset);
+
+ byte[] pixels = new byte[totalBytes];
+ Buffer.BlockCopy(data, imgDataOffset, pixels, 0, totalBytes);
+
+ // --- UNSWIZZLE ---
+ if (meta.Order == 1)
+ {
+ pixels = UnswizzlePSP(pixels, width, height, bufferWidth, bpp);
+ }
+ else
+ {
+ // If linear but with lateral padding, remove it
+ if (bufferWidth != width)
+ pixels = RemovePadding(pixels, width, height, bufferWidth, bpp);
+
+ // If 4bpp Linear, we still need to fix Nibbles
+ if (bpp == 4)
+ SwapNibbles(pixels);
+ }
+
+ // --- PALETTE ---
+ BitmapPalette palette = null;
+ if (meta.Format == 0x04 || meta.Format == 0x05)
+ {
+ if (meta.PaletteInfoOffset > 0 && meta.PaletteBlockEnd > 0)
+ {
+ int palDataOffset = meta.PaletteInfoOffset + meta.PalDataRelOffset;
+ ushort palFmt = ReadUInt16(data, meta.PaletteInfoOffset + 4, meta.IsLittleEndian);
+ int entrySize = (palFmt == 3) ? 4 : 2;
+
+ int palBytes = meta.PaletteBlockEnd - palDataOffset;
+ int colorCount = palBytes / entrySize;
+
+ // Limit colors by BPP
+ if (meta.Format == 0x04) colorCount = Math.Min(colorCount, 16);
+ else if (meta.Format == 0x05) colorCount = Math.Min(colorCount, 256);
+
+ if (palDataOffset + (colorCount * entrySize) <= data.Length)
+ {
+ Color[] colors = new Color[colorCount];
+ for(int i=0; i= data.Length) return 0;
+ return littleEndian
+ ? (ushort)(data[offset] | (data[offset + 1] << 8))
+ : (ushort)((data[offset] << 8) | data[offset + 1]);
+ }
+
+ static uint ReadUInt32(byte[] data, int offset, bool littleEndian)
+ {
+ if (offset + 3 >= data.Length) return 0;
+ return littleEndian
+ ? (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24))
+ : (uint)((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]);
+ }
+
+ static byte[] UnswizzlePSP(byte[] src, int width, int height, int bufferWidth, int bpp)
+ {
+ // 4BPP TRICK: Treat 4BPP as 8BPP with half width for coordinate calculation.
+ // This moves bytes correctly. THEN we swap nibbles.
+ int procBpp = bpp;
+ int procWidth = width;
+ int procBufferWidth = bufferWidth;
+
+ if (bpp == 4)
+ {
+ procBpp = 8;
+ procWidth = width / 2;
+ procBufferWidth = bufferWidth / 2;
+ }
+
+ int blockWidth = 16;
+ int blockHeight = 8;
+
+ if (procBpp == 16) { blockWidth = 16; blockHeight = 4; }
+ else if (procBpp == 32) { blockWidth = 8; blockHeight = 4; }
+
+ int dstStride = (procWidth * procBpp) / 8;
+ byte[] dst = new byte[dstStride * height];
+ int bppByte = procBpp / 8;
+
+ for (int y = 0; y < height; y++)
+ {
+ for (int x = 0; x < procWidth; x++)
+ {
+ int bx = x / blockWidth;
+ int by = y / blockHeight;
+ int mx = x % blockWidth;
+ int my = y % blockHeight;
+
+ int blocksPerRow = procBufferWidth / blockWidth;
+ int blockSize = blockWidth * blockHeight * bppByte;
+ int blockIdx = (by * blocksPerRow) + bx;
+ int srcOffset = (blockIdx * blockSize) + ((my * blockWidth + mx) * bppByte);
+ int dstOffset = (y * dstStride) + (x * bppByte);
+
+ if (srcOffset + bppByte <= src.Length && dstOffset + bppByte <= dst.Length)
+ {
+ // For 4bpp, bppByte is 1 (because we simulate 8bpp).
+ byte val = src[srcOffset];
+
+ // 4BPP LINES FIX:
+ // Unswizzle moved the correct Byte to the correct place.
+ // Now we need to invert nibbles (High/Low) because WPF reads differently from PSP.
+ if (bpp == 4)
+ {
+ val = (byte)(((val & 0x0F) << 4) | ((val & 0xF0) >> 4));
+ }
+
+ dst[dstOffset] = val;
+
+ // For other formats (>8bpp), normal copy
+ if (bpp > 8)
+ {
+ for(int k=1; k> 4));
+ }
+ }
+
+ Color DecodePspColor(ushort v, int fmt)
+ {
+ int r=0, g=0, b=0, a=255;
+ if (fmt == 0) { // 5650
+ r=(v&0x1F)<<3; g=((v>>5)&0x3F)<<2; b=((v>>11)&0x1F)<<3;
+ } else if (fmt == 1) { // 5551
+ r=(v&0x1F)<<3; g=((v>>5)&0x1F)<<3; b=((v>>10)&0x1F)<<3; a=(v>>15)!=0?255:0;
+ } else if (fmt == 2) { // 4444
+ r=(v&0xF)<<4; g=((v>>4)&0xF)<<4; b=((v>>8)&0xF)<<4; a=((v>>12)&0xF)<<4;
+ }
+ // Swap R and B to fix inverted colors
+ return Color.FromArgb((byte)a, (byte)b, (byte)g, (byte)r);
+ }
+
+ byte[] Convert16to32(byte[] inp, int w, int h, int fmt, bool le)
+ {
+ byte[] outp = new byte[w * h * 4];
+ for(int i=0; i=inp.Length) break;
+ ushort v = le
+ ? (ushort)(inp[i*2] | (inp[i*2+1] << 8))
+ : (ushort)((inp[i*2] << 8) | inp[i*2+1]);
+ Color c = DecodePspColor(v, fmt);
+ int o = i*4;
+ outp[o]=c.B; outp[o+1]=c.G; outp[o+2]=c.R; outp[o+3]=c.A;
+ }
+ return outp;
+ }
+ }
+}
\ No newline at end of file