Add mesh reading and conversion support
This commit is contained in:
parent
65dc96c4d6
commit
aa1277a4e1
25 changed files with 2212 additions and 9 deletions
395
LibDgf.Graphics/Mesh/ObjConverter.cs
Normal file
395
LibDgf.Graphics/Mesh/ObjConverter.cs
Normal file
|
@ -0,0 +1,395 @@
|
|||
using LibDgf.Dat;
|
||||
using LibDgf.Mesh;
|
||||
using LibDgf.Ps2.Gs;
|
||||
using LibDgf.Ps2.Gs.Registers;
|
||||
using LibDgf.Txm;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Graphics.Mesh
|
||||
{
|
||||
public class ObjConverter : IDisposable
|
||||
{
|
||||
Dictionary<ulong, TxmHeader> textureCache = new Dictionary<ulong, TxmHeader>();
|
||||
DatReader textureDat;
|
||||
private bool disposedValue;
|
||||
int numWrittenTextures = 0;
|
||||
|
||||
public ObjConverter(DatReader textureDat)
|
||||
{
|
||||
this.textureDat = textureDat ?? throw new ArgumentNullException(nameof(textureDat));
|
||||
}
|
||||
|
||||
public void ConvertObj(Pdb pdb, StreamWriter sw)
|
||||
{
|
||||
int startVert = 1;
|
||||
if (pdb.Specular != null)
|
||||
{
|
||||
sw.WriteLine("o specular");
|
||||
startVert = WriteObj(pdb.Specular, sw, startVert);
|
||||
}
|
||||
if (pdb.Diffuse != null)
|
||||
{
|
||||
sw.WriteLine("o diffuse");
|
||||
startVert = WriteObj(pdb.Diffuse, sw, startVert);
|
||||
}
|
||||
if (pdb.Metallic != null)
|
||||
{
|
||||
sw.WriteLine("o metallic");
|
||||
startVert = WriteObj(pdb.Metallic, sw, startVert);
|
||||
}
|
||||
}
|
||||
|
||||
TxmHeader CreateTexture(Tdb tdb, int index, out ulong textureId)
|
||||
{
|
||||
var tdbTexture = tdb.Textures[index];
|
||||
textureId = ((ulong)tdbTexture.DatIndex << 32) | ((ulong)tdbTexture.ImageBufferBase << 16) | tdbTexture.ClutBufferBase;
|
||||
if (!textureCache.ContainsKey(textureId))
|
||||
{
|
||||
TxmHeader txm = new TxmHeader();
|
||||
txm.ImageBufferBase = tdbTexture.ImageBufferBase;
|
||||
txm.ClutBufferBase = tdbTexture.ClutBufferBase;
|
||||
textureCache.Add(textureId, txm);
|
||||
return txm;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void FillTxm(Db2Element elem, TxmHeader txm)
|
||||
{
|
||||
Tex0 tex0 = new Tex0 { Packed = BitConverter.ToUInt64(elem.GsRegs, 0x10) };
|
||||
txm.ImageSourcePixelFormat = (TxmPixelFormat)tex0.Psm;
|
||||
txm.ImageVideoPixelFormat = txm.ImageSourcePixelFormat;
|
||||
txm.ImageWidth = (short)(1 << tex0.Tw);
|
||||
txm.ImageHeight = (short)(1 << tex0.Th);
|
||||
txm.Misc = tex0.Tbw;
|
||||
if (txm.ImageSourcePixelFormat == TxmPixelFormat.PSMT4 || txm.ImageSourcePixelFormat == TxmPixelFormat.PSMT8)
|
||||
{
|
||||
txm.ClutPixelFormat = (TxmPixelFormat)tex0.Cpsm;
|
||||
if (txm.ImageSourcePixelFormat == TxmPixelFormat.PSMT4)
|
||||
{
|
||||
txm.ClutWidth = 8;
|
||||
txm.ClutHeight = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
txm.ClutWidth = 16;
|
||||
txm.ClutHeight = 16;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
txm.ClutPixelFormat = TxmPixelFormat.None;
|
||||
}
|
||||
}
|
||||
|
||||
int WriteObj(Tdb tdb, StreamWriter sw, int startVert)
|
||||
{
|
||||
for (int i = 0; i < tdb.Mesh.Elements.Count; ++i)
|
||||
{
|
||||
var elem = tdb.Mesh.Elements[i];
|
||||
sw.WriteLine($"g elem_{i}");
|
||||
|
||||
TxmHeader txm = CreateTexture(tdb, elem.TextureIndex, out var textureId);
|
||||
sw.WriteLine($"usemtl tex_{textureId:x12}");
|
||||
if (txm != null) FillTxm(elem, txm);
|
||||
|
||||
// Write vertices
|
||||
foreach (var vert in elem.Vertices)
|
||||
{
|
||||
sw.WriteLine($"v {(double)vert.X} {(double)vert.Y} {(double)vert.Z}");
|
||||
}
|
||||
|
||||
foreach (var norm in elem.VertexNormals)
|
||||
{
|
||||
sw.WriteLine($"vn {norm.Item1} {norm.Item2} {norm.Item3}");
|
||||
}
|
||||
|
||||
foreach (var uv in elem.STCoordinates)
|
||||
{
|
||||
sw.WriteLine($"vt {uv.Item1} {1 - uv.Item2}");
|
||||
}
|
||||
|
||||
// Write faces
|
||||
int[] initVerts = new int[2];
|
||||
bool clockwise = true;
|
||||
if (elem.GifTagIndex == 3) // Triangle fans
|
||||
{
|
||||
int initVertPos = 0;
|
||||
for (int j = 0; j < elem.Vertices.Count; ++j)
|
||||
{
|
||||
if ((elem.Vertices[j].W.Packed & 0x00008000) != 0)
|
||||
{
|
||||
initVerts[initVertPos++] = startVert + j;
|
||||
}
|
||||
else
|
||||
{
|
||||
int currVert = startVert + j;
|
||||
if (clockwise)
|
||||
{
|
||||
sw.WriteLine($"f {initVerts[0]}/{initVerts[0]}/{initVerts[0]} {initVerts[1]}/{initVerts[1]}/{initVerts[1]} {currVert}/{currVert}/{currVert}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sw.WriteLine($"f {currVert}/{currVert}/{currVert} {initVerts[1]}/{initVerts[1]}/{initVerts[1]} {initVerts[0]}/{initVerts[0]}/{initVerts[0]}");
|
||||
}
|
||||
initVerts[1] = currVert;
|
||||
initVertPos = 0;
|
||||
}
|
||||
clockwise = !clockwise;
|
||||
}
|
||||
}
|
||||
else if (elem.GifTagIndex == 4) // Triangle strips
|
||||
{
|
||||
for (int j = 0; j < elem.Vertices.Count; ++j)
|
||||
{
|
||||
int currVert = startVert + j;
|
||||
if ((elem.Vertices[j].W.Packed & 0x00008000) == 0)
|
||||
{
|
||||
if (clockwise)
|
||||
{
|
||||
sw.WriteLine($"f {initVerts[0]}/{initVerts[0]}/{initVerts[0]} {initVerts[1]}/{initVerts[1]}/{initVerts[1]} {currVert}/{currVert}/{currVert}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sw.WriteLine($"f {currVert}/{currVert}/{currVert} {initVerts[1]}/{initVerts[1]}/{initVerts[1]} {initVerts[0]}/{initVerts[0]}/{initVerts[0]}");
|
||||
}
|
||||
}
|
||||
initVerts[0] = initVerts[1];
|
||||
initVerts[1] = currVert;
|
||||
clockwise = !clockwise;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unknown face construction type");
|
||||
}
|
||||
startVert += elem.Vertices.Count;
|
||||
sw.WriteLine();
|
||||
}
|
||||
|
||||
return startVert;
|
||||
}
|
||||
|
||||
void CopyTexelsClut(BinaryReader br, BinaryWriter bw, TxmHeader pakTxm, TxmHeader textureTxm)
|
||||
{
|
||||
if (pakTxm.ClutPixelFormat != TxmPixelFormat.None)
|
||||
throw new ArgumentException("Cannot operate on source TXM with CLUT.", nameof(pakTxm));
|
||||
if (textureTxm.ClutPixelFormat == TxmPixelFormat.None) return;
|
||||
|
||||
var destColumnParams = GsMemoryUtils.GetColumnParams(textureTxm.ClutPixelFormat);
|
||||
int copyLength = textureTxm.GetClutByteSize();
|
||||
int baseBlockNumber = textureTxm.ClutBufferBase - pakTxm.ImageBufferBase;
|
||||
int srcBase = 0x10 + pakTxm.GetClutByteSize();
|
||||
var destBase = 0x10;
|
||||
int bytesPerSrcLine = pakTxm.GetImageByteSize() / pakTxm.ImageHeight;
|
||||
int bytesPerDestLine = textureTxm.GetClutByteSize() / textureTxm.ClutHeight;
|
||||
|
||||
bw.Write(new byte[copyLength]);
|
||||
int numXBlocks = textureTxm.ClutWidth / destColumnParams.Width;
|
||||
if (numXBlocks == 0) numXBlocks = 1;
|
||||
int numYBlocks = textureTxm.ClutHeight / (destColumnParams.Height * GsMemoryUtils.COLUMNS_PER_BLOCK);
|
||||
if (numYBlocks == 0) numYBlocks = 1;
|
||||
int destBlock = 0;
|
||||
for (int blockY = 0; blockY < numYBlocks; ++blockY)
|
||||
{
|
||||
for (int blockX = 0; blockX < numXBlocks; ++blockX)
|
||||
{
|
||||
int blockNumber = baseBlockNumber + GsMemoryUtils.CalcBlockNumber(textureTxm.ClutPixelFormat, blockX, blockY, 1);
|
||||
br.BaseStream.Seek(srcBase + GsMemoryUtils.CalcBlockMemoryOffset(pakTxm.ImageSourcePixelFormat, blockNumber),
|
||||
SeekOrigin.Begin);
|
||||
bw.BaseStream.Seek(destBase + GsMemoryUtils.CalcTxmImageOffset(destColumnParams, destBlock, textureTxm.ClutWidth),
|
||||
SeekOrigin.Begin);
|
||||
for (int i = 0; i < GsMemoryUtils.COLUMNS_PER_BLOCK; ++i)
|
||||
{
|
||||
byte[] col = GsMemoryUtils.ReadColumn(br, pakTxm.ImageSourcePixelFormat, bytesPerSrcLine);
|
||||
GsMemoryUtils.WriteColumn(bw, textureTxm.ClutPixelFormat, bytesPerDestLine, col);
|
||||
}
|
||||
++destBlock;
|
||||
}
|
||||
}
|
||||
|
||||
// Dump palette
|
||||
//bw.BaseStream.Seek(destBase, SeekOrigin.Begin);
|
||||
//BinaryReader palBr = new BinaryReader(bw.BaseStream);
|
||||
//using (var palette = TxmConversion.ConvertTxmRgba32(palBr, textureTxm.ClutWidth, textureTxm.ClutHeight))
|
||||
//{
|
||||
// palette.SaveAsPng($"palette_{numWrittenTextures}.png");
|
||||
//}
|
||||
}
|
||||
|
||||
void CopyTexels(BinaryReader br, BinaryWriter bw, TxmHeader pakTxm, TxmHeader textureTxm)
|
||||
{
|
||||
if (pakTxm.ClutPixelFormat != TxmPixelFormat.None)
|
||||
throw new ArgumentException("Cannot operate on source TXM with CLUT.", nameof(pakTxm));
|
||||
|
||||
var destColumnParams = GsMemoryUtils.GetColumnParams(textureTxm.ImageSourcePixelFormat);
|
||||
int copyLength = textureTxm.GetImageByteSize();
|
||||
int srcBase = 0x10 + pakTxm.GetClutByteSize();
|
||||
int baseBlockNumber = textureTxm.ImageBufferBase - pakTxm.ImageBufferBase;
|
||||
int destBase = 0x10 + textureTxm.GetClutByteSize();
|
||||
int bytesPerSrcLine = pakTxm.GetImageByteSize() / pakTxm.ImageHeight;
|
||||
int bytesPerDestLine = copyLength / textureTxm.ImageHeight;
|
||||
|
||||
bw.Write(new byte[copyLength]);
|
||||
int numXBlocks = textureTxm.ImageWidth / destColumnParams.Width;
|
||||
if (numXBlocks == 0) numXBlocks = 1;
|
||||
int numYBlocks = textureTxm.ImageHeight / (destColumnParams.Height * GsMemoryUtils.COLUMNS_PER_BLOCK);
|
||||
if (numYBlocks == 0) numYBlocks = 1;
|
||||
int destBlock = 0;
|
||||
for (int blockY = 0; blockY < numYBlocks; ++blockY)
|
||||
{
|
||||
for (int blockX = 0; blockX < numXBlocks; ++blockX)
|
||||
{
|
||||
int blockNumber = baseBlockNumber + GsMemoryUtils.CalcBlockNumber(textureTxm.ImageSourcePixelFormat, blockX, blockY, textureTxm.Misc);
|
||||
br.BaseStream.Seek(srcBase + GsMemoryUtils.CalcBlockMemoryOffset(pakTxm.ImageSourcePixelFormat, blockNumber),
|
||||
SeekOrigin.Begin);
|
||||
bw.BaseStream.Seek(destBase + GsMemoryUtils.CalcTxmImageOffset(destColumnParams, destBlock, textureTxm.ImageWidth),
|
||||
SeekOrigin.Begin);
|
||||
for (int i = 0; i < GsMemoryUtils.COLUMNS_PER_BLOCK; ++i)
|
||||
{
|
||||
byte[] col = GsMemoryUtils.ReadColumn(br, pakTxm.ImageSourcePixelFormat, bytesPerSrcLine);
|
||||
if (pakTxm.ImageSourcePixelFormat != textureTxm.ImageSourcePixelFormat)
|
||||
{
|
||||
col = PsmtMixer.MixColumn(col, pakTxm.ImageSourcePixelFormat, textureTxm.ImageSourcePixelFormat, i % 2 != 0);
|
||||
}
|
||||
GsMemoryUtils.WriteColumn(bw, textureTxm.ImageSourcePixelFormat, bytesPerDestLine, col);
|
||||
}
|
||||
|
||||
++destBlock;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportTextures(StreamWriter mtlWriter, string outputPath)
|
||||
{
|
||||
if (disposedValue) throw new ObjectDisposedException(GetType().FullName);
|
||||
|
||||
int i = 0;
|
||||
numWrittenTextures = 0;
|
||||
foreach (var pair in textureCache.OrderBy(p => p.Key))
|
||||
{
|
||||
string pngPath = $"{outputPath}{i}.png";
|
||||
string alphaPath = $"{outputPath}{i}_alpha.png";
|
||||
TxmHeader textureTxm = pair.Value;
|
||||
|
||||
int txmIndex = (int)(pair.Key >> 32);
|
||||
using (var txmMs = new MemoryStream(textureDat.GetData(txmIndex)))
|
||||
{
|
||||
BinaryReader txmBr = new BinaryReader(txmMs);
|
||||
TxmHeader pakTxm = new TxmHeader();
|
||||
pakTxm.Read(txmBr);
|
||||
|
||||
Image<Rgba32> img = null;
|
||||
try
|
||||
{
|
||||
// Check if TXM is already suitable
|
||||
if (/*pakTxm.ImageSourcePixelFormat == textureTxm.ImageSourcePixelFormat &&*/
|
||||
pakTxm.ImageBufferBase == textureTxm.ImageBufferBase &&
|
||||
pakTxm.ClutPixelFormat == textureTxm.ClutPixelFormat &&
|
||||
pakTxm.ClutBufferBase == textureTxm.ClutBufferBase)
|
||||
{
|
||||
// Use TXM as-is
|
||||
txmMs.Seek(0, SeekOrigin.Begin);
|
||||
img = TxmConversion.ConvertTxmToImage(txmMs);
|
||||
|
||||
// Dump palette
|
||||
//if (pakTxm.ClutPixelFormat != TxmPixelFormat.None)
|
||||
//{
|
||||
// txmMs.Seek(0x10, SeekOrigin.Begin);
|
||||
// using (var palette = TxmConversion.ConvertTxmRgba32(txmBr, pakTxm.ClutWidth, pakTxm.ClutHeight))
|
||||
// {
|
||||
// palette.SaveAsPng($"palette_{numWrittenTextures}.png");
|
||||
// }
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate new TXM
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
BinaryWriter bw = new BinaryWriter(ms);
|
||||
textureTxm.Write(bw);
|
||||
CopyTexelsClut(txmBr, bw, pakTxm, textureTxm);
|
||||
CopyTexels(txmBr, bw, pakTxm, textureTxm);
|
||||
bw.Flush();
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
img = TxmConversion.ConvertTxmToImage(ms);
|
||||
}
|
||||
}
|
||||
|
||||
// Save out color texture
|
||||
using (var img24bpp = img.CloneAs<Rgb24>())
|
||||
{
|
||||
img24bpp.SaveAsPng(pngPath);
|
||||
}
|
||||
|
||||
// Extract alpha channel as a separate image
|
||||
using (var alphaImg = new Image<L8>(img.Width, img.Height))
|
||||
{
|
||||
for (int y = 0; y < alphaImg.Height; ++y)
|
||||
{
|
||||
var srcSpan = img.GetPixelRowSpan(y);
|
||||
var destSpan = alphaImg.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < alphaImg.Width; ++x)
|
||||
{
|
||||
var srcAlpha = srcSpan[x].A;
|
||||
destSpan[x] = new L8(srcAlpha);
|
||||
}
|
||||
}
|
||||
alphaImg.SaveAsPng(alphaPath);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (img != null) img.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
mtlWriter.WriteLine($"newmtl tex_{pair.Key:x12}");
|
||||
mtlWriter.WriteLine("Kd 0.80000000 0.80000000 0.80000000");
|
||||
mtlWriter.WriteLine("Ka 0 0 0");
|
||||
mtlWriter.WriteLine("Ke 0 0 0");
|
||||
mtlWriter.WriteLine("Ks 0 0 0");
|
||||
mtlWriter.WriteLine("d 1");
|
||||
mtlWriter.WriteLine("illum 2");
|
||||
mtlWriter.WriteLine($"map_Kd {Path.GetFileName(pngPath)}");
|
||||
mtlWriter.WriteLine($"map_d {Path.GetFileName(alphaPath)}");
|
||||
mtlWriter.WriteLine();
|
||||
|
||||
++i;
|
||||
++numWrittenTextures;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
textureCache.Clear();
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
textureDat.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ namespace LibDgf.Graphics
|
|||
{
|
||||
public static class TxmConversion
|
||||
{
|
||||
public static void ConvertImageToTxm(string inPath, Stream outStream, byte level = 1, short bufferBase = 0, short paletteBufferBase = 0)
|
||||
public static void ConvertImageToTxm(string inPath, Stream outStream, byte level = 1, ushort bufferBase = 0, ushort paletteBufferBase = 0)
|
||||
{
|
||||
using (var image = Image.Load<Rgba32>(inPath))
|
||||
{
|
||||
|
@ -161,14 +161,22 @@ namespace LibDgf.Graphics
|
|||
}
|
||||
|
||||
public static void ConvertTxmToPng(Stream stream, string outPath)
|
||||
{
|
||||
using (var image = ConvertTxmToImage(stream))
|
||||
{
|
||||
image.SaveAsPng(outPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static Image<Rgba32> ConvertTxmToImage(Stream stream)
|
||||
{
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
TxmHeader imageHeader = new TxmHeader();
|
||||
imageHeader.Read(br);
|
||||
|
||||
Console.WriteLine(imageHeader);
|
||||
if (imageHeader.Misc != 1)
|
||||
Console.WriteLine("Different level!");
|
||||
//if (imageHeader.Misc != 1)
|
||||
// Console.WriteLine("Different level!");
|
||||
|
||||
Image<Rgba32> image;
|
||||
if (imageHeader.ImageSourcePixelFormat == TxmPixelFormat.PSMT8 || imageHeader.ImageSourcePixelFormat == TxmPixelFormat.PSMT4)
|
||||
|
@ -209,8 +217,7 @@ namespace LibDgf.Graphics
|
|||
throw new NotSupportedException("Unsupported pixel format");
|
||||
}
|
||||
|
||||
image.SaveAsPng(outPath);
|
||||
image.Dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
public static Image<Rgba32> ConvertTxmIndexed8bpp(BinaryReader br, int width, int height, Rgba32[] palette)
|
||||
|
|
157
LibDgf/Mesh/Db2.cs
Normal file
157
LibDgf/Mesh/Db2.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
using LibDgf.Ps2.Vif;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Mesh
|
||||
{
|
||||
public class Db2
|
||||
{
|
||||
BinaryReader br;
|
||||
|
||||
public List<Db2Element> Elements { get; set; } = new List<Db2Element>();
|
||||
|
||||
public void Read(BinaryReader br)
|
||||
{
|
||||
uint vifStreamLength = br.ReadUInt32();
|
||||
uint version = br.ReadUInt32();
|
||||
if (version != 2) throw new InvalidDataException("DB version is not 2.");
|
||||
uint reserved1 = br.ReadUInt32();
|
||||
uint reserved2 = br.ReadUInt32();
|
||||
//if (reserved1 != 0 || reserved2 != 0)
|
||||
// System.Diagnostics.Debugger.Break();
|
||||
|
||||
Elements.Clear();
|
||||
var startPos = br.BaseStream.Position;
|
||||
var endPos = startPos + vifStreamLength;
|
||||
this.br = br;
|
||||
try
|
||||
{
|
||||
ExpectNop();
|
||||
ExpectNop();
|
||||
ExpectStCycl();
|
||||
ExpectUnpackV4_32();
|
||||
|
||||
while (br.BaseStream.Position < endPos)
|
||||
{
|
||||
Db2Element element = new Db2Element();
|
||||
element.ElementLength = br.ReadInt32();
|
||||
element.Flags = br.ReadUInt32();
|
||||
//if ((element.Flags & 0xFFFFE0E0) != 0)
|
||||
// System.Diagnostics.Debugger.Break();
|
||||
element.TextureIndex = br.ReadInt32();
|
||||
element.Reserved = br.ReadUInt32();
|
||||
//if (element.Reserved != 0)
|
||||
// System.Diagnostics.Debugger.Break();
|
||||
|
||||
int numReglistWords = (int)(element.Flags & 0xf);
|
||||
if (numReglistWords != 0)
|
||||
{
|
||||
element.GsRegs = br.ReadBytes((numReglistWords + 1) * 16);
|
||||
}
|
||||
|
||||
element.GifTagFan = br.ReadBytes(16);
|
||||
element.GifTagStrip = br.ReadBytes(16);
|
||||
|
||||
// Retrieve number of vertices from GIFtag NLOOP
|
||||
int numVertices = element.VertexCount;
|
||||
for (int i = 0; i < numVertices; ++i)
|
||||
{
|
||||
element.Vertices.Add(br.ReadV4_32());
|
||||
}
|
||||
|
||||
ExpectUnpackV3_16((byte)numVertices);
|
||||
for (int i = 0; i < numVertices; ++i)
|
||||
{
|
||||
element.VertexNormals.Add(new Tuple<double, double, double>(
|
||||
Utils.Convert12BitFixedToDouble(br.ReadInt16()),
|
||||
Utils.Convert12BitFixedToDouble(br.ReadInt16()),
|
||||
Utils.Convert12BitFixedToDouble(br.ReadInt16())
|
||||
));
|
||||
}
|
||||
ReadAlign(startPos, 4);
|
||||
|
||||
// Do STs exist if no texture?
|
||||
ExpectUnpackV2_16((byte)numVertices);
|
||||
for (int i = 0; i < numVertices; ++i)
|
||||
{
|
||||
element.STCoordinates.Add(new Tuple<double, double>(
|
||||
Utils.Convert12BitFixedToDouble(br.ReadInt16()),
|
||||
Utils.Convert12BitFixedToDouble(br.ReadInt16())
|
||||
));
|
||||
}
|
||||
ReadAlign(startPos, 16); // Skipping over some NOPs in the process, assume that's what they are
|
||||
|
||||
ExpectMsCnt();
|
||||
|
||||
Elements.Add(element);
|
||||
|
||||
ExpectNop();
|
||||
if (br.BaseStream.Position == endPos - 8)
|
||||
{
|
||||
ExpectNop();
|
||||
ExpectNop();
|
||||
}
|
||||
else
|
||||
{
|
||||
ExpectStCycl();
|
||||
ExpectUnpackV4_32();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.br = null;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadAlign(long startPos, int bytes)
|
||||
{
|
||||
var aligned = (br.BaseStream.Position - startPos + bytes - 1) / bytes * bytes;
|
||||
br.BaseStream.Seek(startPos + aligned - br.BaseStream.Position, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
#region Sanity checking functions
|
||||
|
||||
VifCode ExpectNop()
|
||||
{
|
||||
return ExpectVifCode(0x00000000, 0x7f000000);
|
||||
}
|
||||
|
||||
VifCode ExpectStCycl()
|
||||
{
|
||||
return ExpectVifCode(0x01000404, 0x7f00ffff);
|
||||
}
|
||||
|
||||
VifCodeUnpack ExpectUnpackV4_32()
|
||||
{
|
||||
return (VifCodeUnpack)ExpectVifCode(0x6c008000, 0x6f00c000);
|
||||
}
|
||||
|
||||
VifCodeUnpack ExpectUnpackV3_16(byte num)
|
||||
{
|
||||
return (VifCodeUnpack)ExpectVifCode(0x69008000 | ((uint)num << 16), 0x6fffc000);
|
||||
}
|
||||
|
||||
VifCodeUnpack ExpectUnpackV2_16(byte num)
|
||||
{
|
||||
return (VifCodeUnpack)ExpectVifCode(0x65008000 | ((uint)num << 16), 0x6fffc000);
|
||||
}
|
||||
|
||||
VifCode ExpectMsCnt()
|
||||
{
|
||||
return ExpectVifCode(0x17000000, 0x7f000000);
|
||||
}
|
||||
|
||||
VifCode ExpectVifCode(uint value, uint mask)
|
||||
{
|
||||
uint read = br.ReadUInt32();
|
||||
if ((read & mask) != value)
|
||||
throw new InvalidDataException($"VIFcode expectation failed at {br.BaseStream.Position - 4:x8}.");
|
||||
return new VifCode { Value = read };
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
89
LibDgf/Mesh/Db2Element.cs
Normal file
89
LibDgf/Mesh/Db2Element.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using LibDgf.Ps2.Vif;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Mesh
|
||||
{
|
||||
public class Db2Element
|
||||
{
|
||||
// Size of element in 128-bit words, including header, excluding surrounding VIFcodes
|
||||
public int ElementLength { get; set; }
|
||||
// 0x000f: number of REGLIST words (excluding leading GIFtag)
|
||||
// 0x0010: has texture
|
||||
// 0x00e0: reserved
|
||||
// 0x0100: has vertex color (deprecated, probably)
|
||||
// 0x0e00: GIFtag index - with standard REGLIST primitive: 3 = triangle fan, 4 = triangle strip
|
||||
// 0x1000: disable lighting
|
||||
// 0x80000: textures enabled (only set at runtime)
|
||||
public uint Flags { get; set; }
|
||||
public int TextureIndex { get; set; } // Actually byte
|
||||
public uint Reserved { get; set; }
|
||||
|
||||
public byte[] GsRegs { get; set; } // One GS primitive, CLAMP_1 and TEX0_1
|
||||
public byte[] GifTagFan { get; set; }
|
||||
public byte[] GifTagStrip { get; set; }
|
||||
|
||||
public List<VuVector> Vertices { get; } = new List<VuVector>();
|
||||
public List<Tuple<double, double, double>> VertexNormals { get; } = new List<Tuple<double, double, double>>();
|
||||
public List<Tuple<double, double>> STCoordinates { get; } = new List<Tuple<double, double>>();
|
||||
|
||||
// Flags broken out
|
||||
|
||||
public bool HasTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Flags & 0x10) != 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
Flags = (uint)((Flags & ~0x10) | ((value ? 1u : 0) << 4));
|
||||
}
|
||||
}
|
||||
|
||||
public int GifTagIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return (int)((Flags >> 9) & 7);
|
||||
}
|
||||
set
|
||||
{
|
||||
Flags = (uint)((Flags & ~0xe00) | (((uint)value & 7) << 9));
|
||||
}
|
||||
}
|
||||
|
||||
public bool DisableLighting
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Flags & 0x1000) != 0;
|
||||
}
|
||||
set
|
||||
{
|
||||
Flags = (uint)((Flags & ~0x1000) | ((value ? 1u : 0) << 12));
|
||||
}
|
||||
}
|
||||
|
||||
public int VertexCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return BitConverter.ToInt32(GifTagStrip, 0) & 0x7ff;
|
||||
}
|
||||
set
|
||||
{
|
||||
int num = BitConverter.ToInt32(GifTagStrip, 0);
|
||||
num = (num & ~0x7ff) | (value & 0x7ff);
|
||||
byte[] bytes = BitConverter.GetBytes(num);
|
||||
Buffer.BlockCopy(bytes, 0, GifTagStrip, 0, bytes.Length);
|
||||
|
||||
num = BitConverter.ToInt32(GifTagFan, 0);
|
||||
num = (num & ~0x7ff) | (value & 0x7ff);
|
||||
bytes = BitConverter.GetBytes(num);
|
||||
Buffer.BlockCopy(bytes, 0, GifTagFan, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
LibDgf/Mesh/Pdb.cs
Normal file
92
LibDgf/Mesh/Pdb.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using LibDgf.Ps2.Vif;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Mesh
|
||||
{
|
||||
public class Pdb
|
||||
{
|
||||
VuVector[] boundingBox;
|
||||
|
||||
public int BoundingBoxType { get; set; }
|
||||
public VuFloat BoundingBallSize { get; set; }
|
||||
public VuVector[] BoundingBox
|
||||
{
|
||||
get
|
||||
{
|
||||
return boundingBox;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null && value.Length != 8) throw new ArgumentException("Wrong number of vertices for bounding box.", nameof(value));
|
||||
boundingBox = value;
|
||||
}
|
||||
}
|
||||
public Tdb Specular { get; set; }
|
||||
public Tdb Diffuse { get; set; }
|
||||
public Tdb Metallic { get; set; }
|
||||
|
||||
public void Read(BinaryReader br)
|
||||
{
|
||||
var startOffset = br.BaseStream.Position;
|
||||
BoundingBoxType = br.ReadInt32();
|
||||
BoundingBallSize = br.ReadPs2Float();
|
||||
uint boundingBoxOffset = br.ReadUInt32();
|
||||
uint boundingBoxLength = br.ReadUInt32();
|
||||
uint[] tdbOffsets = new uint[3];
|
||||
uint[] tdbLengths = new uint[tdbOffsets.Length];
|
||||
for (int i = 0; i < tdbOffsets.Length; ++i)
|
||||
{
|
||||
tdbOffsets[i] = br.ReadUInt32();
|
||||
tdbLengths[i] = br.ReadUInt32();
|
||||
}
|
||||
|
||||
// Junk bytes 0xc8 to 0xcf here
|
||||
|
||||
if (boundingBoxLength != 0)
|
||||
{
|
||||
br.BaseStream.Seek(startOffset + boundingBoxOffset, SeekOrigin.Begin);
|
||||
BoundingBox = br.ReadBoundingBox();
|
||||
}
|
||||
else
|
||||
{
|
||||
BoundingBox = null;
|
||||
}
|
||||
|
||||
if (tdbLengths[0] != 0)
|
||||
{
|
||||
br.BaseStream.Seek(startOffset + tdbOffsets[0], SeekOrigin.Begin);
|
||||
Specular = new Tdb();
|
||||
Specular.Read(br);
|
||||
}
|
||||
else
|
||||
{
|
||||
Specular = null;
|
||||
}
|
||||
|
||||
if (tdbLengths[1] != 0)
|
||||
{
|
||||
br.BaseStream.Seek(startOffset + tdbOffsets[1], SeekOrigin.Begin);
|
||||
Diffuse = new Tdb();
|
||||
Diffuse.Read(br);
|
||||
}
|
||||
else
|
||||
{
|
||||
Diffuse = null;
|
||||
}
|
||||
|
||||
if (tdbLengths[2] != 0)
|
||||
{
|
||||
br.BaseStream.Seek(startOffset + tdbOffsets[2], SeekOrigin.Begin);
|
||||
Metallic = new Tdb();
|
||||
Metallic.Read(br);
|
||||
}
|
||||
else
|
||||
{
|
||||
Metallic = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
LibDgf/Mesh/Tdb.cs
Normal file
54
LibDgf/Mesh/Tdb.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using LibDgf.Ps2.Vif;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Mesh
|
||||
{
|
||||
public class Tdb
|
||||
{
|
||||
VuVector[] boundingBox;
|
||||
|
||||
public TdbFlags Flags { get; set; }
|
||||
public List<TdbTexture> Textures { get; } = new List<TdbTexture>();
|
||||
public VuVector[] BoundingBox
|
||||
{
|
||||
get
|
||||
{
|
||||
return boundingBox;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != null && value.Length != 8) throw new ArgumentException("Wrong number of vertices for bounding box.", nameof(value));
|
||||
boundingBox = value;
|
||||
}
|
||||
}
|
||||
public Db2 Mesh { get; set; }
|
||||
|
||||
public void Read(BinaryReader br)
|
||||
{
|
||||
var startPos = br.BaseStream.Position;
|
||||
Flags = (TdbFlags)br.ReadByte();
|
||||
Textures.Clear();
|
||||
byte numTextures = br.ReadByte();
|
||||
for (int i = 0; i < numTextures; ++i)
|
||||
{
|
||||
var tex = new TdbTexture();
|
||||
tex.Read(br);
|
||||
Textures.Add(tex);
|
||||
}
|
||||
var read = br.BaseStream.Position - startPos;
|
||||
var aligned = (read + 15) & ~15;
|
||||
br.BaseStream.Seek(aligned - read, SeekOrigin.Current);
|
||||
|
||||
if ((Flags & TdbFlags.SkipBoundingBox) == 0)
|
||||
{
|
||||
BoundingBox = br.ReadBoundingBox();
|
||||
}
|
||||
|
||||
Mesh = new Db2();
|
||||
Mesh.Read(br);
|
||||
}
|
||||
}
|
||||
}
|
13
LibDgf/Mesh/TdbFlags.cs
Normal file
13
LibDgf/Mesh/TdbFlags.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Mesh
|
||||
{
|
||||
[Flags]
|
||||
public enum TdbFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
SkipBoundingBox = 1,
|
||||
}
|
||||
}
|
21
LibDgf/Mesh/TdbTexture.cs
Normal file
21
LibDgf/Mesh/TdbTexture.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Mesh
|
||||
{
|
||||
public class TdbTexture
|
||||
{
|
||||
public ushort DatIndex { get; set; }
|
||||
public ushort ImageBufferBase { get; set; }
|
||||
public ushort ClutBufferBase { get; set; }
|
||||
|
||||
public void Read(BinaryReader br)
|
||||
{
|
||||
DatIndex = br.ReadUInt16();
|
||||
ImageBufferBase = br.ReadUInt16();
|
||||
ClutBufferBase = br.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
207
LibDgf/Ps2/Gs/GsMemoryUtils.cs
Normal file
207
LibDgf/Ps2/Gs/GsMemoryUtils.cs
Normal file
|
@ -0,0 +1,207 @@
|
|||
using LibDgf.Txm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Gs
|
||||
{
|
||||
public static class GsMemoryUtils
|
||||
{
|
||||
public const int BYTES_PER_COLUMN = 64;
|
||||
public const int COLUMNS_PER_BLOCK = 4;
|
||||
public const int BYTES_PER_BLOCK = BYTES_PER_COLUMN * COLUMNS_PER_BLOCK;
|
||||
public const int BLOCKS_PER_PAGE = 32;
|
||||
public const int BYTES_PER_PAGE = BYTES_PER_BLOCK * BLOCKS_PER_PAGE;
|
||||
public const int TOTAL_PAGES = 512;
|
||||
public const int TOTAL_BYTES = BYTES_PER_PAGE * TOTAL_PAGES;
|
||||
|
||||
public class ColumnParams
|
||||
{
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int BitsPerPixel { get; }
|
||||
public int ColsPerPage { get; }
|
||||
public int RowsPerPage { get; }
|
||||
|
||||
internal ColumnParams(int width, int height, int bitsPerPixel, int colsPerPage, int rowsPerPage)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
BitsPerPixel = bitsPerPixel;
|
||||
ColsPerPage = colsPerPage;
|
||||
RowsPerPage = rowsPerPage;
|
||||
}
|
||||
}
|
||||
|
||||
static readonly ColumnParams PSMCT32_COLUMN_PARAMS = new ColumnParams(8, 2, 32, 8, 4);
|
||||
static readonly ColumnParams PSMT8_COLUMN_PARAMS = new ColumnParams(16, 4, 8, 8, 4);
|
||||
static readonly ColumnParams PSMT4_COLUMN_PARAMS = new ColumnParams(32, 4, 4, 4, 8);
|
||||
|
||||
public static ColumnParams GetColumnParams(TxmPixelFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TxmPixelFormat.PSMCT32:
|
||||
case TxmPixelFormat.PSMCT24:
|
||||
return PSMCT32_COLUMN_PARAMS;
|
||||
case TxmPixelFormat.PSMT8:
|
||||
return PSMT8_COLUMN_PARAMS;
|
||||
case TxmPixelFormat.PSMT4:
|
||||
return PSMT4_COLUMN_PARAMS;
|
||||
default:
|
||||
throw new NotSupportedException($"{format} not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] ReadColumn(BinaryReader br, TxmPixelFormat format, int bytesPerLine)
|
||||
{
|
||||
ColumnParams colParams = GetColumnParams(format);
|
||||
byte[] data = new byte[colParams.Width * colParams.Height * colParams.BitsPerPixel / 8];
|
||||
int bytesPerDestLine = colParams.Width * colParams.BitsPerPixel / 8;
|
||||
int skipLength = bytesPerLine - bytesPerDestLine;
|
||||
for (int i = 0; i < colParams.Height; ++i)
|
||||
{
|
||||
byte[] lineData = br.ReadBytes(bytesPerDestLine);
|
||||
br.BaseStream.Seek(skipLength, SeekOrigin.Current);
|
||||
Buffer.BlockCopy(lineData, 0, data, i * bytesPerDestLine, lineData.Length);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void WriteColumn(BinaryWriter bw, TxmPixelFormat format, int bytesPerLine, byte[] data)
|
||||
{
|
||||
ColumnParams colParams = GetColumnParams(format);
|
||||
int bytesPerDestLine = colParams.Width * colParams.BitsPerPixel / 8;
|
||||
int skipLength = bytesPerLine - bytesPerDestLine;
|
||||
for (int i = 0; i < colParams.Height; ++i)
|
||||
{
|
||||
bw.Write(data, i * bytesPerDestLine, bytesPerDestLine);
|
||||
bw.Seek(skipLength, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// Block address to linear offset lookup for PSMCT32
|
||||
static readonly int[,] PSMCT32_BLOCK_LOOKUP = new[,]
|
||||
{
|
||||
{ 0, 1, 4, 5, 16, 17, 20, 21 },
|
||||
{ 2, 3, 6, 7, 18, 19, 22, 23 },
|
||||
{ 8, 9, 12, 13, 24, 25, 28, 29 },
|
||||
{ 10, 11, 14, 15, 26, 27, 30, 31 }
|
||||
};
|
||||
|
||||
static readonly int[,] PSMT8_BLOCK_LOOKUP = new[,]
|
||||
{
|
||||
{ 0, 1, 4, 5, 16, 17, 20, 21 },
|
||||
{ 2, 3, 6, 7, 18, 19, 22, 23 },
|
||||
{ 8, 9, 12, 13, 24, 25, 28, 29 },
|
||||
{ 10, 11, 14, 15, 26, 27, 30, 31 }
|
||||
};
|
||||
|
||||
static readonly int[,] PSMT4_BLOCK_LOOKUP = new[,]
|
||||
{
|
||||
{ 0, 2, 8, 10 },
|
||||
{ 1, 3, 9, 11 },
|
||||
{ 4, 6, 12, 14 },
|
||||
{ 5, 7, 13, 15 },
|
||||
{ 16, 18, 24, 26 },
|
||||
{ 17, 19, 25, 27 },
|
||||
{ 20, 22, 28, 30 },
|
||||
{ 21, 23, 29, 31 }
|
||||
};
|
||||
|
||||
static readonly int[] PSMCT32_BLOCK_REVERSE_LOOKUP;
|
||||
static readonly int[] PSMT8_BLOCK_REVERSE_LOOKUP;
|
||||
static readonly int[] PSMT4_BLOCK_REVERSE_LOOKUP;
|
||||
|
||||
static GsMemoryUtils()
|
||||
{
|
||||
PSMCT32_BLOCK_REVERSE_LOOKUP = MakeReverseLookup(PSMCT32_BLOCK_LOOKUP);
|
||||
PSMT8_BLOCK_REVERSE_LOOKUP = MakeReverseLookup(PSMT8_BLOCK_LOOKUP);
|
||||
PSMT4_BLOCK_REVERSE_LOOKUP = MakeReverseLookup(PSMT4_BLOCK_LOOKUP);
|
||||
}
|
||||
|
||||
static int[] MakeReverseLookup(int[,] lut)
|
||||
{
|
||||
int[] reverse = new int[lut.Length];
|
||||
int i = 0;
|
||||
foreach (var n in lut)
|
||||
{
|
||||
reverse[n] = i++;
|
||||
}
|
||||
return reverse;
|
||||
}
|
||||
|
||||
public static int CalcBlockNumber(TxmPixelFormat format, int blockX, int blockY, int texBufWidth)
|
||||
{
|
||||
ColumnParams colParams = GetColumnParams(format);
|
||||
switch (format)
|
||||
{
|
||||
case TxmPixelFormat.PSMCT32:
|
||||
return CalcBlockNumber(colParams, PSMCT32_BLOCK_LOOKUP,blockX, blockY, texBufWidth);
|
||||
case TxmPixelFormat.PSMT8:
|
||||
return CalcBlockNumber(colParams, PSMT8_BLOCK_LOOKUP, blockX, blockY, texBufWidth);
|
||||
case TxmPixelFormat.PSMT4:
|
||||
return CalcBlockNumber(colParams, PSMT4_BLOCK_LOOKUP, blockX, blockY, texBufWidth);
|
||||
default:
|
||||
throw new NotSupportedException($"{format} not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public static int CalcBlockMemoryOffset(TxmPixelFormat format, int index)
|
||||
{
|
||||
ColumnParams colParams = GetColumnParams(format);
|
||||
switch (format)
|
||||
{
|
||||
case TxmPixelFormat.PSMCT32:
|
||||
return CalcBlockMemoryOffset(colParams, PSMCT32_BLOCK_REVERSE_LOOKUP, index);
|
||||
case TxmPixelFormat.PSMT8:
|
||||
return CalcBlockMemoryOffset(colParams, PSMT8_BLOCK_REVERSE_LOOKUP, index);
|
||||
case TxmPixelFormat.PSMT4:
|
||||
return CalcBlockMemoryOffset(colParams, PSMT4_BLOCK_REVERSE_LOOKUP, index);
|
||||
default:
|
||||
throw new NotSupportedException($"{format} not supported");
|
||||
}
|
||||
}
|
||||
|
||||
static int CalcBlockNumber(ColumnParams colParams, int[,] lut, int blockX, int blockY, int texBufWidth)
|
||||
{
|
||||
int pageX = blockX / colParams.ColsPerPage;
|
||||
int pageY = blockY / colParams.RowsPerPage;
|
||||
int blockXInPage = blockX % colParams.ColsPerPage;
|
||||
int blockYInPage = blockY % colParams.RowsPerPage;
|
||||
|
||||
return (pageY * texBufWidth + pageX) * BLOCKS_PER_PAGE + lut[blockYInPage, blockXInPage];
|
||||
}
|
||||
|
||||
static int CalcBlockMemoryOffset(ColumnParams colParams, int[] lut, int index)
|
||||
{
|
||||
int pageIndex = index / BLOCKS_PER_PAGE;
|
||||
int rem = index % BLOCKS_PER_PAGE;
|
||||
int pixelsPerBlock = colParams.Width * colParams.Height * COLUMNS_PER_BLOCK;
|
||||
int memBlockNum = lut[rem];
|
||||
int fullBlockRows = memBlockNum / colParams.ColsPerPage;
|
||||
int partialBlocks = memBlockNum % colParams.ColsPerPage;
|
||||
|
||||
return (
|
||||
pageIndex * BLOCKS_PER_PAGE * pixelsPerBlock + // Full pages
|
||||
fullBlockRows * pixelsPerBlock * colParams.ColsPerPage + // Full row of blocks
|
||||
partialBlocks * colParams.Width // Partial row of blocks
|
||||
) * colParams.BitsPerPixel / 8;
|
||||
}
|
||||
|
||||
public static int CalcTxmImageOffset(ColumnParams colParams, int blockIndex, int imageWidth)
|
||||
{
|
||||
int blocksPerRow = imageWidth / colParams.Width;
|
||||
if (blocksPerRow == 0) blocksPerRow = 1;
|
||||
int fullRows = blockIndex / blocksPerRow;
|
||||
int rowBlockIndex = blockIndex % blocksPerRow;
|
||||
return (
|
||||
fullRows * imageWidth * colParams.Height * 4 +
|
||||
rowBlockIndex * colParams.Width
|
||||
) * colParams.BitsPerPixel / 8;
|
||||
}
|
||||
}
|
||||
}
|
141
LibDgf/Ps2/Gs/PsmtMixer.cs
Normal file
141
LibDgf/Ps2/Gs/PsmtMixer.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
using LibDgf.Txm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Gs
|
||||
{
|
||||
public static class PsmtMixer
|
||||
{
|
||||
static int[,] PSMCT32_TO_PSMT4_COL02_LOOKUP = new int[16, 8];
|
||||
static int[,] PSMCT32_TO_PSMT4_COL13_LOOKUP = new int[16, 8];
|
||||
static int[,] PSMCT32_TO_PSMT8_COL02_LOOKUP = new int[16, 4];
|
||||
static int[,] PSMCT32_TO_PSMT8_COL13_LOOKUP = new int[16, 4];
|
||||
|
||||
static PsmtMixer()
|
||||
{
|
||||
FillLookup(PSMCT32_TO_PSMT4_COL02_LOOKUP, false);
|
||||
FillOddLookup(PSMCT32_TO_PSMT4_COL02_LOOKUP, PSMCT32_TO_PSMT4_COL13_LOOKUP);
|
||||
FillLookup(PSMCT32_TO_PSMT8_COL02_LOOKUP, false);
|
||||
FillOddLookup(PSMCT32_TO_PSMT8_COL02_LOOKUP, PSMCT32_TO_PSMT8_COL13_LOOKUP);
|
||||
|
||||
//PrintLookup(PSMCT32_TO_PSMT4_COL02_LOOKUP, nameof(PSMCT32_TO_PSMT4_COL02_LOOKUP));
|
||||
//PrintLookup(PSMCT32_TO_PSMT4_COL13_LOOKUP, nameof(PSMCT32_TO_PSMT4_COL13_LOOKUP));
|
||||
//PrintLookup(PSMCT32_TO_PSMT8_COL02_LOOKUP, nameof(PSMCT32_TO_PSMT8_COL02_LOOKUP));
|
||||
//PrintLookup(PSMCT32_TO_PSMT8_COL13_LOOKUP, nameof(PSMCT32_TO_PSMT8_COL13_LOOKUP));
|
||||
}
|
||||
|
||||
static void PrintLookup(int[,] lookup, string name)
|
||||
{
|
||||
Console.WriteLine(name);
|
||||
int rowNum = lookup.GetLength(0);
|
||||
int colNum = lookup.GetLength(1);
|
||||
for (int row = 0; row < rowNum; ++row)
|
||||
{
|
||||
for (int col = colNum - 1; col >= 0; --col)
|
||||
{
|
||||
Console.Write("{0,3} ", lookup[row, col]);
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
static void FillLookup(int[,] lookup, bool skipNonConsec)
|
||||
{
|
||||
int numCols = lookup.GetLength(1);
|
||||
int num = 0;
|
||||
|
||||
// Phase 1: consecutive numbers
|
||||
// Fill every second column
|
||||
// Top half then bottom half
|
||||
for (int half = 0; half < 2; ++half)
|
||||
{
|
||||
for (int col = 0; col < numCols; col += skipNonConsec ? 1 : 2)
|
||||
{
|
||||
for (int row = 0; row < 8; ++row)
|
||||
{
|
||||
lookup[half * 8 + row, col] = num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: wrapped numbers
|
||||
if (!skipNonConsec)
|
||||
{
|
||||
for (int half = 0; half < 2; ++half)
|
||||
{
|
||||
for (int col = 1; col < numCols; col += 2)
|
||||
{
|
||||
for (int row = 4; row < 12; ++row)
|
||||
{
|
||||
lookup[half * 8 + (row % 8), col] = num++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FillOddLookup(int[,] evenLookup, int[,] oddLookup)
|
||||
{
|
||||
int numCols = evenLookup.GetLength(1);
|
||||
for (int half = 0; half < 2; ++half)
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
for (int j = 0; j < numCols; ++j)
|
||||
{
|
||||
oddLookup[half * 8 + i, j] = evenLookup[half * 8 + ((i + 4) % 8), j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] MixColumn(byte[] column, TxmPixelFormat srcFormat, TxmPixelFormat destFormat, bool isOdd)
|
||||
{
|
||||
if (srcFormat != TxmPixelFormat.PSMCT32)
|
||||
throw new NotSupportedException("Only PSMCT32 supported as source format.");
|
||||
switch (destFormat)
|
||||
{
|
||||
case TxmPixelFormat.PSMT4:
|
||||
return MixColumn32To4(column, isOdd);
|
||||
case TxmPixelFormat.PSMT8:
|
||||
return MixColumn32To8(column, isOdd);
|
||||
default:
|
||||
throw new NotSupportedException($"{destFormat} not supported as destination format.");
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] MixColumn32To4(byte[] column, bool isOdd)
|
||||
{
|
||||
int[,] lookup = isOdd ? PSMCT32_TO_PSMT4_COL13_LOOKUP : PSMCT32_TO_PSMT4_COL02_LOOKUP;
|
||||
int numCol = lookup.GetLength(1);
|
||||
byte[] dest = new byte[column.Length];
|
||||
byte b = 0;
|
||||
for (int i = 0; i < column.Length * 2; ++i)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
b = column[i / 2];
|
||||
else
|
||||
b >>= 4;
|
||||
|
||||
int index = lookup[i / numCol, i % numCol];
|
||||
dest[index / 2] |= (byte)((b & 0x0f) << (index % 2 * 4));
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
static byte[] MixColumn32To8(byte[] column, bool isOdd)
|
||||
{
|
||||
int[,] lookup = isOdd ? PSMCT32_TO_PSMT8_COL13_LOOKUP : PSMCT32_TO_PSMT8_COL02_LOOKUP;
|
||||
int numCol = lookup.GetLength(1);
|
||||
byte[] dest = new byte[column.Length];
|
||||
for (int i = 0; i < column.Length; ++i)
|
||||
{
|
||||
int index = lookup[i / numCol, i % numCol];
|
||||
dest[index] = column[i];
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
}
|
107
LibDgf/Ps2/Gs/Registers/Tex0.cs
Normal file
107
LibDgf/Ps2/Gs/Registers/Tex0.cs
Normal file
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Gs.Registers
|
||||
{
|
||||
public struct Tex0
|
||||
{
|
||||
public ulong Packed;
|
||||
|
||||
public ushort Tbp0
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ushort)(Packed & 0x3fff);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Tbw
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 14) & 0x3f);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Psm
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 20) & 0x3f);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Tw
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 26) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Th
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 30) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Tcc
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 34) & 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Tfx
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 35) & 0x3);
|
||||
}
|
||||
}
|
||||
|
||||
public ushort Cbp
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 37) & 0x3ff);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Cpsm
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 51) & 0xf);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Csm
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 55) & 0x1);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Csa
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 56) & 0x1f);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Cld
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)((Packed >> 61) & 0x7);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
332
LibDgf/Ps2/Vif/BinaryReaderVifExtensions.cs
Normal file
332
LibDgf/Ps2/Vif/BinaryReaderVifExtensions.cs
Normal file
|
@ -0,0 +1,332 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public static class BinaryReaderVifExtensions
|
||||
{
|
||||
public static VuFloat ReadPs2Float(this BinaryReader br)
|
||||
{
|
||||
return new VuFloat { Packed = br.ReadUInt32() };
|
||||
}
|
||||
|
||||
public static VuVector[] ReadBoundingBox(this BinaryReader br)
|
||||
{
|
||||
var box = new VuVector[8];
|
||||
for (int i = 0; i < box.Length; ++i)
|
||||
{
|
||||
box[i] = br.ReadV4_32();
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
public static VuVector ReadOneVifCodeUnpack(this BinaryReader br, VifCodeUnpack u)
|
||||
{
|
||||
switch (u.Vn)
|
||||
{
|
||||
case VifUnpackVnType.S:
|
||||
switch (u.Vl)
|
||||
{
|
||||
case VifUnpackVlType.L_32:
|
||||
return br.ReadS_32();
|
||||
case VifUnpackVlType.L_16:
|
||||
return u.Unsigned ? br.ReadS_16U() : br.ReadS_16S();
|
||||
case VifUnpackVlType.L_8:
|
||||
return u.Unsigned ? br.ReadS_8U() : br.ReadS_8S();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case VifUnpackVnType.V2:
|
||||
switch (u.Vl)
|
||||
{
|
||||
case VifUnpackVlType.L_32:
|
||||
return br.ReadV2_32();
|
||||
case VifUnpackVlType.L_16:
|
||||
return u.Unsigned ? br.ReadV2_16U() : br.ReadV2_16S();
|
||||
case VifUnpackVlType.L_8:
|
||||
return u.Unsigned ? br.ReadV2_8U() : br.ReadV2_8S();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case VifUnpackVnType.V3:
|
||||
switch (u.Vl)
|
||||
{
|
||||
case VifUnpackVlType.L_32:
|
||||
return br.ReadV3_32();
|
||||
case VifUnpackVlType.L_16:
|
||||
return u.Unsigned ? br.ReadV3_16U() : br.ReadV3_16S();
|
||||
case VifUnpackVlType.L_8:
|
||||
return u.Unsigned ? br.ReadV3_8U() : br.ReadV3_8S();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case VifUnpackVnType.V4:
|
||||
switch (u.Vl)
|
||||
{
|
||||
case VifUnpackVlType.L_32:
|
||||
return br.ReadV4_32();
|
||||
case VifUnpackVlType.L_16:
|
||||
return u.Unsigned ? br.ReadV4_16U() : br.ReadV4_16S();
|
||||
case VifUnpackVlType.L_8:
|
||||
return u.Unsigned ? br.ReadV4_8U() : br.ReadV4_8S();
|
||||
case VifUnpackVlType.L_5:
|
||||
return br.ReadV4_5();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid vn/vl combination", nameof(u));
|
||||
}
|
||||
|
||||
public static VuVector ReadV4_32(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = br.ReadUInt32() },
|
||||
Y = new VuFloat { Packed = br.ReadUInt32() },
|
||||
Z = new VuFloat { Packed = br.ReadUInt32() },
|
||||
W = new VuFloat { Packed = br.ReadUInt32() }
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV4_16U(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = br.ReadUInt16() },
|
||||
Y = new VuFloat { Packed = br.ReadUInt16() },
|
||||
Z = new VuFloat { Packed = br.ReadUInt16() },
|
||||
W = new VuFloat { Packed = br.ReadUInt16() },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV4_16S(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
Y = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
Z = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
W = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV4_8U(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = br.ReadByte() },
|
||||
Y = new VuFloat { Packed = br.ReadByte() },
|
||||
Z = new VuFloat { Packed = br.ReadByte() },
|
||||
W = new VuFloat { Packed = br.ReadByte() },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV4_8S(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
Y = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
Z = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
W = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV3_32(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = br.ReadUInt32() },
|
||||
Y = new VuFloat { Packed = br.ReadUInt32() },
|
||||
Z = new VuFloat { Packed = br.ReadUInt32() },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV3_16U(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = br.ReadUInt16() },
|
||||
Y = new VuFloat { Packed = br.ReadUInt16() },
|
||||
Z = new VuFloat { Packed = br.ReadUInt16() },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV3_16S(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
Y = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
Z = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV3_8U(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = br.ReadByte() },
|
||||
Y = new VuFloat { Packed = br.ReadByte() },
|
||||
Z = new VuFloat { Packed = br.ReadByte() },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV3_8S(this BinaryReader br)
|
||||
{
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
Y = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
Z = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) },
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV2_32(this BinaryReader br)
|
||||
{
|
||||
var x = new VuFloat { Packed = br.ReadUInt32() };
|
||||
var y = new VuFloat { Packed = br.ReadUInt32() };
|
||||
return new VuVector
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = x,
|
||||
W = y
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV2_16U(this BinaryReader br)
|
||||
{
|
||||
var x = new VuFloat { Packed = br.ReadUInt16() };
|
||||
var y = new VuFloat { Packed = br.ReadUInt16() };
|
||||
return new VuVector
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = x,
|
||||
W = y
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV2_16S(this BinaryReader br)
|
||||
{
|
||||
var x = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) };
|
||||
var y = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) };
|
||||
return new VuVector
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = x,
|
||||
W = y
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV2_8U(this BinaryReader br)
|
||||
{
|
||||
var x = new VuFloat { Packed = (uint)br.ReadByte() };
|
||||
var y = new VuFloat { Packed = (uint)br.ReadByte() };
|
||||
return new VuVector
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = x,
|
||||
W = y
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV2_8S(this BinaryReader br)
|
||||
{
|
||||
var x = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) };
|
||||
var y = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) };
|
||||
return new VuVector
|
||||
{
|
||||
X = x,
|
||||
Y = y,
|
||||
Z = x,
|
||||
W = y
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadS_32(this BinaryReader br)
|
||||
{
|
||||
var s = new VuFloat { Packed = br.ReadUInt32() };
|
||||
return new VuVector
|
||||
{
|
||||
X = s,
|
||||
Y = s,
|
||||
Z = s,
|
||||
W = s
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadS_16U(this BinaryReader br)
|
||||
{
|
||||
var s = new VuFloat { Packed = (uint)br.ReadUInt16() };
|
||||
return new VuVector
|
||||
{
|
||||
X = s,
|
||||
Y = s,
|
||||
Z = s,
|
||||
W = s
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadS_16S(this BinaryReader br)
|
||||
{
|
||||
var s = new VuFloat { Packed = unchecked((uint)br.ReadInt16()) };
|
||||
return new VuVector
|
||||
{
|
||||
X = s,
|
||||
Y = s,
|
||||
Z = s,
|
||||
W = s
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadS_8U(this BinaryReader br)
|
||||
{
|
||||
var s = new VuFloat { Packed = (uint)br.ReadByte() };
|
||||
return new VuVector
|
||||
{
|
||||
X = s,
|
||||
Y = s,
|
||||
Z = s,
|
||||
W = s
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadS_8S(this BinaryReader br)
|
||||
{
|
||||
var s = new VuFloat { Packed = unchecked((uint)br.ReadSByte()) };
|
||||
return new VuVector
|
||||
{
|
||||
X = s,
|
||||
Y = s,
|
||||
Z = s,
|
||||
W = s
|
||||
};
|
||||
}
|
||||
|
||||
public static VuVector ReadV4_5(this BinaryReader br)
|
||||
{
|
||||
uint rgba = br.ReadUInt16();
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = (rgba << 3) & 0xf8 },
|
||||
Y = new VuFloat { Packed = (rgba >> 2) & 0xf8 },
|
||||
Z = new VuFloat { Packed = (rgba >> 7) & 0xf8 },
|
||||
W = new VuFloat { Packed = (rgba >> 8) & 0x80 },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
53
LibDgf/Ps2/Vif/VifCode.cs
Normal file
53
LibDgf/Ps2/Vif/VifCode.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public struct VifCode
|
||||
{
|
||||
public uint Value;
|
||||
|
||||
public bool Interrupt
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Cmd & VifCodeCmd.Interrupt) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public VifCodeCmd Cmd
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VifCodeCmd)(byte)(Value >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
public VifCodeCmd CmdWithoutInterrupt
|
||||
{
|
||||
get
|
||||
{
|
||||
return Cmd & ~VifCodeCmd.Interrupt;
|
||||
}
|
||||
}
|
||||
|
||||
public byte Num
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)(Value >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
public ushort Immediate
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ushort)Value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsUnpack => (Cmd & VifCodeCmd.Unpack) == VifCodeCmd.Unpack;
|
||||
}
|
||||
}
|
32
LibDgf/Ps2/Vif/VifCodeCmd.cs
Normal file
32
LibDgf/Ps2/Vif/VifCodeCmd.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public enum VifCodeCmd : byte
|
||||
{
|
||||
Nop = 0b000_0000,
|
||||
StCycl = 0b000_0001,
|
||||
Offset = 0b000_0010,
|
||||
Base = 0b000_0011,
|
||||
Itop = 0b000_0100,
|
||||
StMod = 0b000_0101,
|
||||
MskPath3 = 0b000_0110,
|
||||
Mark = 0b000_0111,
|
||||
FlushE = 0b001_0000,
|
||||
Flush = 0b001_0001,
|
||||
FlushA = 0b001_0011,
|
||||
MsCal = 0b001_0100,
|
||||
MsCnt = 0b001_0111,
|
||||
MsCalF = 0b001_0101,
|
||||
StMask = 0b010_0000,
|
||||
StRow = 0b011_0000,
|
||||
StCol = 0b011_0001,
|
||||
Mpg = 0b100_1010,
|
||||
Direct = 0b101_0000,
|
||||
DirectHl = 0b101_0001,
|
||||
Unpack = 0b11_00000,
|
||||
Interrupt = 0b1000_0000
|
||||
}
|
||||
}
|
87
LibDgf/Ps2/Vif/VifCodeUnpack.cs
Normal file
87
LibDgf/Ps2/Vif/VifCodeUnpack.cs
Normal file
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public struct VifCodeUnpack
|
||||
{
|
||||
private uint Value;
|
||||
|
||||
public bool Interrupt
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Value & 0x80000000) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Mask
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Value & 0x10000000) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public VifUnpackVnType Vn
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VifUnpackVnType)((Value >> 26) & 0x3);
|
||||
}
|
||||
}
|
||||
|
||||
public VifUnpackVlType Vl
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VifUnpackVlType)((Value >> 24) & 0x3);
|
||||
}
|
||||
}
|
||||
|
||||
public byte Num
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)(Value >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Flag
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Value & 0x8000) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Unsigned
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Value & 0x4000) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort Address
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ushort)(Value & 0x3ff);
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator VifCode(VifCodeUnpack v)
|
||||
{
|
||||
return new VifCode { Value = v.Value | ((uint)VifCodeCmd.Unpack << 24) };
|
||||
}
|
||||
|
||||
public static explicit operator VifCodeUnpack(VifCode v)
|
||||
{
|
||||
if ((v.Cmd & VifCodeCmd.Unpack) != VifCodeCmd.Unpack)
|
||||
throw new ArgumentException("Not an UNPACK code", nameof(v));
|
||||
return new VifCodeUnpack { Value = v.Value };
|
||||
}
|
||||
}
|
||||
}
|
243
LibDgf/Ps2/Vif/VifEmulator.cs
Normal file
243
LibDgf/Ps2/Vif/VifEmulator.cs
Normal file
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public class VifEmulator
|
||||
{
|
||||
public delegate void VuMpgActivate(ushort? address, bool waitGif);
|
||||
|
||||
VuVector[] memory;
|
||||
VifRegisters registers;
|
||||
bool maskPath3;
|
||||
|
||||
public VifEmulator()
|
||||
{
|
||||
memory = new VuVector[1024];
|
||||
for (int i = 0; i < memory.Length; ++i)
|
||||
{
|
||||
memory[i] = new VuVector();
|
||||
}
|
||||
registers = new VifRegisters();
|
||||
}
|
||||
|
||||
public VuVector[] VuMemory => memory;
|
||||
public VifRegisters Registers => registers;
|
||||
|
||||
public void Process(BinaryReader br, int dataLength, VuMpgActivate onVuMpgActivate)
|
||||
{
|
||||
var startPos = br.BaseStream.Position;
|
||||
var endPos = startPos + dataLength;
|
||||
while (br.BaseStream.Position < endPos)
|
||||
{
|
||||
var vifcode = new VifCode { Value = br.ReadUInt32() };
|
||||
registers.Code = vifcode;
|
||||
//if ((vifcode.CmdWithoutInterrupt & VifCodeCmd.Unpack) != VifCodeCmd.Unpack)
|
||||
//{
|
||||
// Console.WriteLine($"VIFcode: {vifcode.CmdWithoutInterrupt} NUM={vifcode.Num} IMMEDIATE={vifcode.Immediate:x4}");
|
||||
//}
|
||||
switch (vifcode.CmdWithoutInterrupt)
|
||||
{
|
||||
case VifCodeCmd.Nop:
|
||||
break;
|
||||
case VifCodeCmd.StCycl:
|
||||
registers.Cycle = vifcode.Immediate;
|
||||
break;
|
||||
case VifCodeCmd.Offset:
|
||||
registers.Ofst = (uint)(vifcode.Immediate & 0x3ff);
|
||||
registers.Stat_Dbf = false;
|
||||
registers.TopS = registers.Base;
|
||||
break;
|
||||
case VifCodeCmd.Base:
|
||||
registers.Base = (uint)(vifcode.Immediate & 0x3ff);
|
||||
break;
|
||||
case VifCodeCmd.Itop:
|
||||
registers.ITopS = (uint)(vifcode.Immediate & 0x3ff);
|
||||
break;
|
||||
case VifCodeCmd.StMod:
|
||||
registers.Mode = (uint)(vifcode.Immediate & 3);
|
||||
break;
|
||||
case VifCodeCmd.MskPath3:
|
||||
maskPath3 = (vifcode.Immediate & 0x8000) != 0;
|
||||
break;
|
||||
case VifCodeCmd.Mark:
|
||||
registers.Mark = vifcode.Immediate;
|
||||
break;
|
||||
case VifCodeCmd.FlushE:
|
||||
case VifCodeCmd.Flush:
|
||||
case VifCodeCmd.FlushA:
|
||||
// Microprogram is run synchronously in emulation
|
||||
break;
|
||||
case VifCodeCmd.MsCal:
|
||||
registers.DoubleBufferSwap();
|
||||
onVuMpgActivate?.Invoke(vifcode.Immediate, false);
|
||||
break;
|
||||
case VifCodeCmd.MsCnt:
|
||||
registers.DoubleBufferSwap();
|
||||
onVuMpgActivate?.Invoke(null, false);
|
||||
break;
|
||||
case VifCodeCmd.MsCalF:
|
||||
registers.DoubleBufferSwap();
|
||||
onVuMpgActivate?.Invoke(vifcode.Immediate, true);
|
||||
break;
|
||||
case VifCodeCmd.StMask:
|
||||
registers.Mask = br.ReadUInt32();
|
||||
break;
|
||||
case VifCodeCmd.StRow:
|
||||
registers.R[0] = br.ReadUInt32();
|
||||
registers.R[1] = br.ReadUInt32();
|
||||
registers.R[2] = br.ReadUInt32();
|
||||
registers.R[3] = br.ReadUInt32();
|
||||
break;
|
||||
case VifCodeCmd.StCol:
|
||||
registers.C[0] = br.ReadUInt32();
|
||||
registers.C[1] = br.ReadUInt32();
|
||||
registers.C[2] = br.ReadUInt32();
|
||||
registers.C[3] = br.ReadUInt32();
|
||||
break;
|
||||
case VifCodeCmd.Mpg:
|
||||
{
|
||||
if (!CheckAlignment(br, startPos, 8))
|
||||
throw new InvalidDataException("MPG data is not aligned.");
|
||||
//Console.WriteLine($"MPG load at 0x{vifcode.Immediate * 8:x4}");
|
||||
// Skip MPG since we don't have a VU to execute it on
|
||||
int skipLength = vifcode.Num;
|
||||
if (skipLength == 0) skipLength = 256;
|
||||
skipLength *= 8;
|
||||
br.BaseStream.Seek(skipLength, SeekOrigin.Current);
|
||||
break;
|
||||
}
|
||||
case VifCodeCmd.Direct:
|
||||
case VifCodeCmd.DirectHl:
|
||||
{
|
||||
if (!CheckAlignment(br, startPos, 16))
|
||||
throw new InvalidDataException("Direct data is not aligned.");
|
||||
//Console.WriteLine($"Direct transfer");
|
||||
// TODO: handle GIFtag
|
||||
int skipLength = vifcode.Immediate;
|
||||
if (skipLength == 0) skipLength = 65536;
|
||||
skipLength *= 16;
|
||||
br.BaseStream.Seek(skipLength, SeekOrigin.Current);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if ((vifcode.Cmd & VifCodeCmd.Unpack) == VifCodeCmd.Unpack)
|
||||
{
|
||||
ProcessVifCodeUnpack(br);
|
||||
AlignReader(br, startPos, 4);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidDataException("Invalid VIFcode command");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessVifCodeUnpack(BinaryReader br)
|
||||
{
|
||||
var vifcode = (VifCodeUnpack)registers.Code;
|
||||
registers.Num = vifcode.Num;
|
||||
//Console.WriteLine($"VIFcode: {VifCodeCmd.Unpack} vn={vifcode.Vn} vl={vifcode.Vl} NUM={vifcode.Num} ADDR={vifcode.Address:x4} FLG={vifcode.Flag} USN={vifcode.Unsigned} m={vifcode.Mask}");
|
||||
int addr = (int)((vifcode.Flag ? registers.TopS : 0) + vifcode.Address);
|
||||
int cycle = 0;
|
||||
bool isV4_5 = vifcode.Vn == VifUnpackVnType.V4 && vifcode.Vl == VifUnpackVlType.L_5;
|
||||
while (registers.Num > 0)
|
||||
{
|
||||
VuVector result = default;
|
||||
bool doSkip = false;
|
||||
bool doMode;
|
||||
bool doMask;
|
||||
if (registers.CycleCl >= registers.CycleWl)
|
||||
{
|
||||
doMode = true;
|
||||
doMask = false;
|
||||
// Skipping write
|
||||
if (cycle < registers.CycleWl)
|
||||
{
|
||||
// Write when under write limit
|
||||
result = br.ReadOneVifCodeUnpack(vifcode);
|
||||
}
|
||||
|
||||
if (cycle == registers.CycleWl - 1)
|
||||
{
|
||||
doSkip = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Filling write
|
||||
throw new NotImplementedException("Filling write not implemented");
|
||||
}
|
||||
|
||||
// Write result
|
||||
result = ApplyMaskAndMode(result, doMode && !isV4_5, doMask);
|
||||
memory[addr++] = result;
|
||||
--registers.Num;
|
||||
|
||||
// TODO: figure out the proper behavior for filling write
|
||||
if (doSkip)
|
||||
{
|
||||
addr += registers.CycleCl - registers.CycleWl;
|
||||
cycle = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
++cycle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VuVector ApplyMaskAndMode(VuVector vector, bool doMode, bool doMask)
|
||||
{
|
||||
if (!doMode && !doMask) return vector;
|
||||
|
||||
uint x = vector.X.Packed;
|
||||
uint y = vector.Y.Packed;
|
||||
uint z = vector.Z.Packed;
|
||||
uint w = vector.W.Packed;
|
||||
|
||||
if (doMask)
|
||||
{
|
||||
throw new NotImplementedException("Masking not implemented");
|
||||
}
|
||||
|
||||
if (doMode)
|
||||
{
|
||||
switch (registers.ModeMod)
|
||||
{
|
||||
case VifMode.None:
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Addition decompression write not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
return new VuVector
|
||||
{
|
||||
X = new VuFloat { Packed = x },
|
||||
Y = new VuFloat { Packed = y },
|
||||
Z = new VuFloat { Packed = z },
|
||||
W = new VuFloat { Packed = w },
|
||||
};
|
||||
}
|
||||
|
||||
// Alignment in bytes
|
||||
static void AlignReader(BinaryReader br, long startPos, int alignment)
|
||||
{
|
||||
var read = br.BaseStream.Position - startPos;
|
||||
var aligned = (read + alignment - 1) / alignment * alignment;
|
||||
br.BaseStream.Seek(aligned - read, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
static bool CheckAlignment(BinaryReader br, long startPos, int alignment)
|
||||
{
|
||||
var read = br.BaseStream.Position - startPos;
|
||||
var aligned = (read + alignment - 1) / alignment * alignment;
|
||||
return read == aligned;
|
||||
}
|
||||
}
|
||||
}
|
13
LibDgf/Ps2/Vif/VifMode.cs
Normal file
13
LibDgf/Ps2/Vif/VifMode.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public enum VifMode : byte
|
||||
{
|
||||
None,
|
||||
Offset,
|
||||
Difference
|
||||
}
|
||||
}
|
71
LibDgf/Ps2/Vif/VifRegisters.cs
Normal file
71
LibDgf/Ps2/Vif/VifRegisters.cs
Normal file
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public class VifRegisters
|
||||
{
|
||||
public uint[] R { get; } = new uint[4];
|
||||
public uint[] C { get; } = new uint[4];
|
||||
public uint Cycle { get; set; }
|
||||
public uint Mask { get; set; }
|
||||
public uint Mode { get; set; }
|
||||
public uint ITop { get; set; }
|
||||
public uint ITopS { get; set; }
|
||||
public uint Base { get; set; }
|
||||
public uint Ofst { get; set; }
|
||||
public uint Top { get; set; }
|
||||
public uint TopS { get; set; }
|
||||
public uint Mark { get; set; }
|
||||
public uint Num { get; set; }
|
||||
public VifCode Code { get; set; }
|
||||
|
||||
// Just this flag because the other ones are not that interesting
|
||||
public bool Stat_Dbf { get; set; }
|
||||
|
||||
public byte CycleCl
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)Cycle;
|
||||
}
|
||||
set
|
||||
{
|
||||
Cycle = (Cycle & 0xffffff00) | value;
|
||||
}
|
||||
}
|
||||
|
||||
public byte CycleWl
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte)(Cycle >> 8);
|
||||
}
|
||||
set
|
||||
{
|
||||
Cycle = (Cycle & 0xffff00ff) | ((uint)value << 8);
|
||||
}
|
||||
}
|
||||
|
||||
public VifMode ModeMod
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VifMode)(Mode & 3);
|
||||
}
|
||||
set
|
||||
{
|
||||
Mode = (uint)value & 3;
|
||||
}
|
||||
}
|
||||
|
||||
public void DoubleBufferSwap()
|
||||
{
|
||||
ITop = ITopS;
|
||||
Top = TopS;
|
||||
TopS = Base + (Stat_Dbf ? Ofst : 0);
|
||||
Stat_Dbf = !Stat_Dbf;
|
||||
}
|
||||
}
|
||||
}
|
14
LibDgf/Ps2/Vif/VifUnpackVlType.cs
Normal file
14
LibDgf/Ps2/Vif/VifUnpackVlType.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public enum VifUnpackVlType
|
||||
{
|
||||
L_32,
|
||||
L_16,
|
||||
L_8,
|
||||
L_5
|
||||
}
|
||||
}
|
14
LibDgf/Ps2/Vif/VifUnpackVnType.cs
Normal file
14
LibDgf/Ps2/Vif/VifUnpackVnType.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public enum VifUnpackVnType
|
||||
{
|
||||
S,
|
||||
V2,
|
||||
V3,
|
||||
V4
|
||||
}
|
||||
}
|
33
LibDgf/Ps2/Vif/VuFloat.cs
Normal file
33
LibDgf/Ps2/Vif/VuFloat.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public struct VuFloat
|
||||
{
|
||||
public uint Packed;
|
||||
|
||||
public static implicit operator double(VuFloat f)
|
||||
{
|
||||
ulong sign = (f.Packed >> 31) & 1;
|
||||
ulong exponent = (f.Packed >> 23) & 0xff;
|
||||
ulong mantissa = f.Packed & 0x7fffff;
|
||||
ulong doubleValue;
|
||||
if (exponent == 0)
|
||||
{
|
||||
doubleValue = sign << 63;
|
||||
}
|
||||
else
|
||||
{
|
||||
doubleValue = (sign << 63) | ((exponent + 1023 - 127) << 52) | (mantissa << 29);
|
||||
}
|
||||
return BitConverter.ToDouble(BitConverter.GetBytes(doubleValue), 0);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ((double)this).ToString();
|
||||
}
|
||||
}
|
||||
}
|
19
LibDgf/Ps2/Vif/VuVector.cs
Normal file
19
LibDgf/Ps2/Vif/VuVector.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Ps2.Vif
|
||||
{
|
||||
public struct VuVector
|
||||
{
|
||||
public VuFloat X;
|
||||
public VuFloat Y;
|
||||
public VuFloat Z;
|
||||
public VuFloat W;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"<{X}, {Y}, {Z}, {W}>";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,12 +11,12 @@ namespace LibDgf.Txm
|
|||
public TxmPixelFormat ImageVideoPixelFormat { get; set; }
|
||||
public short ImageWidth { get; set; }
|
||||
public short ImageHeight { get; set; }
|
||||
public short ImageBufferBase { get; set; }
|
||||
public ushort ImageBufferBase { get; set; }
|
||||
public TxmPixelFormat ClutPixelFormat { get; set; }
|
||||
public byte Misc { get; set; } // 0x0f = level, 0x70 = count, 0x80 = fast count
|
||||
public short ClutWidth { get; set; }
|
||||
public short ClutHeight { get; set; }
|
||||
public short ClutBufferBase { get; set; }
|
||||
public ushort ClutBufferBase { get; set; }
|
||||
|
||||
public void Read(BinaryReader br)
|
||||
{
|
||||
|
@ -24,12 +24,12 @@ namespace LibDgf.Txm
|
|||
ImageVideoPixelFormat = (TxmPixelFormat)br.ReadByte();
|
||||
ImageWidth = br.ReadInt16();
|
||||
ImageHeight = br.ReadInt16();
|
||||
ImageBufferBase = br.ReadInt16();
|
||||
ImageBufferBase = br.ReadUInt16();
|
||||
ClutPixelFormat = (TxmPixelFormat)br.ReadByte();
|
||||
Misc = br.ReadByte();
|
||||
ClutWidth = br.ReadInt16();
|
||||
ClutHeight = br.ReadInt16();
|
||||
ClutBufferBase = br.ReadInt16();
|
||||
ClutBufferBase = br.ReadUInt16();
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter bw)
|
||||
|
|
|
@ -25,5 +25,10 @@ namespace LibDgf
|
|||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static double Convert12BitFixedToDouble(int value)
|
||||
{
|
||||
return value * 0.000244140625; // value / 2^12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@ Currently implemented
|
|||
- font/*.pak: font pack
|
||||
- Sound effects catalog (read support only)
|
||||
- TXM: texture files
|
||||
- PDB/TDB/DB2: Mesh formats (typically contained inside DAT, read support only)
|
||||
|
||||
**PlayStation 2**
|
||||
- VIF emulator (partial)
|
||||
|
||||
**Aqualead (Plug & Play)**
|
||||
(read support only currently)
|
||||
|
|
Loading…
Add table
Reference in a new issue