Add PSP GIM support and support for guyzware Inc psp visual novels

This commit is contained in:
gopicolo
2026-01-27 01:07:48 -03:00
parent 9647b68729
commit 31a1a4f42f
3 changed files with 469 additions and 0 deletions

View File

@@ -161,6 +161,7 @@
<Compile Include="FC01\BdtTables.cs" />
<Compile Include="Fog\ArcDAT.cs" />
<Compile Include="GScripter\ArcDATA.cs" />
<Compile Include="Guyzware\ArcDat.cs" />
<Compile Include="Ism\ImagePNG.cs" />
<Compile Include="Kid\ArcDATRAW.cs" />
<Compile Include="Kid\ArcKLZ.cs" />
@@ -225,6 +226,7 @@
<Compile Include="NipponIchi\ImageNMT.cs" />
<Compile Include="NScripter\Script.cs" />
<Compile Include="Ps1\ImageTIM.cs" />
<Compile Include="Psp\ArcGim.cs" />
<Compile Include="Psp\ArcQPK.cs" />
<Compile Include="ScrPlayer\ImageIMG.cs" />
<Compile Include="SingleFileArchive.cs" />

View File

@@ -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<Entry>(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<byte>();
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);
}
}
}

374
ArcFormats/Psp/ArcGim.cs Normal file
View File

@@ -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<colorCount; i++)
{
int pOff = palDataOffset + (i * entrySize);
if (entrySize == 4) // RGBA8888
{
byte r = data[pOff];
byte g = data[pOff+1];
byte b = data[pOff+2];
byte a = data[pOff+3];
// IMPORTANT: Swap R and B to fix "blue face"
colors[i] = Color.FromArgb(a, r, g, b);
}
else // 16 bit
{
ushort p = ReadUInt16(data, pOff, meta.IsLittleEndian);
colors[i] = DecodePspColor(p, palFmt);
}
}
palette = new BitmapPalette(colors);
}
}
}
PixelFormat format = PixelFormats.Bgra32;
int outputStride = (width * bpp + 7) / 8;
byte[] outputPixels = pixels;
if (bpp == 8) format = PixelFormats.Indexed8;
else if (bpp == 4) format = PixelFormats.Indexed4;
else if (bpp == 16)
{
format = PixelFormats.Bgra32;
outputPixels = Convert16to32(pixels, width, height, meta.Format, meta.IsLittleEndian);
outputStride = width * 4;
}
else if (bpp == 32) format = PixelFormats.Bgra32;
return ImageData.Create(info, format, palette, outputPixels, outputStride);
}
public override void Write(Stream file, ImageData image)
{
throw new NotImplementedException();
}
// --- HELPERS ---
static ushort ReadUInt16(byte[] data, int offset, bool littleEndian)
{
if (offset + 1 >= 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<bppByte; k++) dst[dstOffset+k] = src[srcOffset+k];
}
}
}
}
return dst;
}
static byte[] RemovePadding(byte[] src, int width, int height, int bufferWidth, int bpp)
{
int srcStride = (bufferWidth * bpp + 7) / 8;
int dstStride = (width * bpp + 7) / 8;
byte[] dst = new byte[dstStride * height];
for (int y = 0; y < height; y++)
{
int copyLen = Math.Min(srcStride, dstStride);
int sOff = y * srcStride;
int dOff = y * dstStride;
if (sOff + copyLen <= src.Length)
Array.Copy(src, sOff, dst, dOff, copyLen);
}
return dst;
}
static void SwapNibbles(byte[] data)
{
for (int i = 0; i < data.Length; i++)
{
byte b = data[i];
data[i] = (byte)(((b & 0x0F) << 4) | ((b & 0xF0) >> 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<w*h; i++) {
if(i*2+1>=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;
}
}
}