mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-06 05:38:48 +08:00
Merge pull request #158 from gopicolo/feature/psp-gim-support
Add PSP GIM support and support for guyzware Inc psp visual novels
This commit is contained in:
@@ -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" />
|
||||
|
||||
93
ArcFormats/Guyzware/ArcDat.cs
Normal file
93
ArcFormats/Guyzware/ArcDat.cs
Normal 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
374
ArcFormats/Psp/ArcGim.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user