From d2b3391794efdfc9bd5daf90f7d7d4a6efb95d76 Mon Sep 17 00:00:00 2001 From: Luciano Ciccariello Date: Mon, 16 Dec 2019 09:01:20 +0000 Subject: [PATCH] Write back a MDLX of type 3 and 4 --- OpenKh.Common/Ps2/Dma.cs | 78 ++++++++ OpenKh.Common/StreamExtensions.cs | 30 +++ OpenKh.Kh2/Mdlx.Model.cs | 317 ++++++++++++++++++++++++------ OpenKh.Kh2/Mdlx.cs | 26 +++ OpenKh.Tests/kh2/MdlxTests.cs | 27 ++- 5 files changed, 409 insertions(+), 69 deletions(-) create mode 100644 OpenKh.Common/Ps2/Dma.cs diff --git a/OpenKh.Common/Ps2/Dma.cs b/OpenKh.Common/Ps2/Dma.cs new file mode 100644 index 000000000..842dbb527 --- /dev/null +++ b/OpenKh.Common/Ps2/Dma.cs @@ -0,0 +1,78 @@ +using Xe.BinaryMapper; + +namespace OpenKh.Common.Ps2 +{ + /// + /// EE User Manual, 6.3.2 + /// + public enum VifOpcode : byte + { + NOP = 0b00000000, + STCYCL = 0b00000001, + OFFSET = 0b00000010, + BASE = 0b00000011, + ITOP = 0b00000100, + STMOD = 0b00000101, + MSKPATH3 = 0b00000110, + MARK = 0b00000111, + FLUSHE = 0b00010000, + FLUSH = 0b00010001, + FLUSHA = 0b00010011, + MSCAL = 0b00010100, + MSCALF = 0b00010101, + MSCNT = 0b00010111, + STMASK = 0b00100000, + STROW = 0b00110000, + STCOL = 0b00110001, + MPG = 0b01001010, + DIRECT = 0b01010000, + DIRECTH = 0b01010001 + } + + /// + /// EE User Manual, 6.3.2 + /// + public class VifCode + { + [Data] public byte Cmd { get; set; } + [Data] public byte Num { get; set; } + [Data] public ushort Immediate { get; set; } + + public VifOpcode Opcode + { + get => (VifOpcode)(Cmd & 7); + set => Cmd = (byte)((byte)value | (Interrupt ? 0x80 : 0)); + } + + public bool Interrupt + { + get => (Cmd >> 7) != 0; + set => Cmd = (byte)((byte)Opcode | (value ? 0x80 : 0)); + } + } + + /// + /// EE User Manual, 5.6 + /// + public class DmaTag + { + /// + /// Quadword count; packet size + /// + [Data] public ushort Qwc { get; set; } + [Data] public ushort Param { get; set; } + [Data] public int Address { get; set; } + + public int TagId + { + get => (Param >> 12) & 3; + set => Param = (ushort)(((value & 3) << 12) | (Irq ? 0x8000 : 0)); + } + + public bool Irq + { + get => (Param >> 15) != 0; + set => Param = (ushort)((TagId << 12) | (value ? 0x8000 : 0)); + } + } +} diff --git a/OpenKh.Common/StreamExtensions.cs b/OpenKh.Common/StreamExtensions.cs index 8182f23a8..a146d54f6 100644 --- a/OpenKh.Common/StreamExtensions.cs +++ b/OpenKh.Common/StreamExtensions.cs @@ -47,6 +47,12 @@ public static int ReadInt32(this Stream stream) => public static uint ReadUInt32(this Stream stream) => new BinaryReader(stream).ReadUInt32(); + public static long ReadInt64(this Stream stream) => + new BinaryReader(stream).ReadInt64(); + + public static ulong ReadUInt64(this Stream stream) => + new BinaryReader(stream).ReadUInt64(); + public static List ReadInt32List(this Stream stream, int offset, int count) { stream.Position = offset; @@ -111,6 +117,30 @@ public static int Write(this Stream stream, IEnumerable items) return (int)stream.Position - oldPosition; } + public static void Write(this Stream stream, byte value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, char value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, short value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, ushort value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, int value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, uint value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, long value) => + new BinaryWriter(stream).Write(value); + + public static void Write(this Stream stream, ulong value) => + new BinaryWriter(stream).Write(value); + public static void Copy(this Stream source, Stream destination, int length, int bufferSize = 65536) { int read; diff --git a/OpenKh.Kh2/Mdlx.Model.cs b/OpenKh.Kh2/Mdlx.Model.cs index c19307ded..964b6b8f6 100644 --- a/OpenKh.Kh2/Mdlx.Model.cs +++ b/OpenKh.Kh2/Mdlx.Model.cs @@ -1,7 +1,9 @@ -// Inspired by Kddf2's khkh_xldM. +// Inspired by Kddf2's khkh_xldM. // Original source code: https://gitlab.com/kenjiuno/khkh_xldM/blob/master/khkh_xldMii/Mdlxfst.cs using OpenKh.Common; +using OpenKh.Common.Ps2; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -12,6 +14,32 @@ namespace OpenKh.Kh2 { public partial class Mdlx { + private static readonly VifCode DefaultVifCode = new VifCode + { + Opcode = VifOpcode.STCYCL, + Interrupt = false, + Num = 1, + Immediate = 0x0100, + }; + private static readonly DmaPacket CloseDmaPacket = new DmaPacket + { + DmaTag = new DmaTag + { + Qwc = 0, + TagId = 1, + Irq = false, + Address = 0 + }, + VifCode = new VifCode + { + Opcode = VifOpcode.NOP, + Interrupt = false, + Num = 0, + Immediate = 0x1700, + }, + Parameter = 0 + }; + public class Bone { [Data] public int Index { get; set; } @@ -32,24 +60,11 @@ public class Bone [Data] public float TranslationW { get; set; } } - public class SubModel + public class DmaPacket { - public List DmaVifs { get; set; } - public List Bones { get; set; } - } - - public class DmaVif - { - public int TextureIndex { get; } - public int[] Alaxi { get; } - public byte[] VifPacket { get; } - - public DmaVif(int texi, int[] alaxi, byte[] bin) - { - TextureIndex = texi; - Alaxi = alaxi; - VifPacket = bin; - } + [Data] public DmaTag DmaTag { get; set; } + [Data] public VifCode VifCode { get; set; } + [Data] public int Parameter { get; set; } } private class SubModelHeader @@ -59,13 +74,10 @@ private class SubModelHeader [Data] public int Unk08 { get; set; } [Data] public int NextOffset { get; set; } [Data] public short BoneCount { get; set; } - [Data] public short Unk12 { get; set; } + [Data] public short Unk { get; set; } [Data] public int BoneOffset { get; set; } - [Data] public int Off4 { get; set; } - [Data] public short DmaChainCount { get; set; } - [Data] public short Unk1e { get; set; } - - public SubModel SubModel { get; set; } + [Data] public int UnkDataOffset { get; set; } + [Data] public int DmaChainCount { get; set; } } private class DmaChainHeader @@ -73,11 +85,50 @@ private class DmaChainHeader [Data] public int Unk00 { get; set; } [Data] public int TextureIndex { get; set; } [Data] public int Unk08 { get; set; } - [Data] public int Unk0c { get; set; } + [Data] public int Unused1 { get; set; } [Data] public int DmaOffset { get; set; } - [Data] public int Unk14 { get; set; } + [Data] public int Count1aOffset { get; set; } [Data] public int DmaLength { get; set; } - [Data] public int Unk1c { get; set; } + [Data] public int Unused2 { get; set; } + } + + public class SubModel + { + public int Type { get; set; } + public int Unk04 { get; set; } + public int Unk08 { get; set; } + public short BoneCount { get; set; } + public short Unk { get; set; } + public int DmaChainCount { get; set; } + + public List Bones { get; internal set; } + public byte[] UnknownData { get; internal set; } + public List DmaChains { get; internal set; } + } + + public class DmaChain + { + public int Unk00 { get; set; } + public int TextureIndex { get; set; } + public int Unk08 { get; set; } + public int DmaLength { get; set; } + public List DmaVifs { get; set; } + } + + public class DmaVif + { + public int TextureIndex { get; } + public int[] Alaxi { get; } + public byte[] VifPacket { get; } + public int BaseAddress { get; } + + public DmaVif(int texi, int[] alaxi, byte[] bin, int baseAddress) + { + TextureIndex = texi; + Alaxi = alaxi; + VifPacket = bin; + BaseAddress = baseAddress; + } } private static IEnumerable ReadAsModel(Stream stream) @@ -88,6 +139,9 @@ private static IEnumerable ReadAsModel(Stream stream) { currentOffset += nextOffset; var subStream = new SubStream(stream, currentOffset, stream.Length - currentOffset); + if (subStream.Length == 0) + yield break; + nextOffset = ReadSubModel(subStream, out var subModel); yield return subModel; @@ -97,40 +151,43 @@ private static IEnumerable ReadAsModel(Stream stream) private static int ReadSubModel(Stream stream, out SubModel subModel) { var header = BinaryMapping.ReadObject(stream); - header.SubModel = new SubModel() + subModel = new SubModel { - DmaVifs = new List(), - Bones = new List(), + Type = header.Type, + Unk04 = header.Unk04, + Unk08 = header.Unk08, + BoneCount = header.BoneCount, + Unk = header.Unk, + DmaChainCount = header.DmaChainCount, }; + + var dmaChainHeaders = For(subModel.DmaChainCount, () => BinaryMapping.ReadObject(stream)); - var dmaChains = For(header.DmaChainCount, () => BinaryMapping.ReadObject(stream)); - for (var j = 0; j < header.DmaChainCount; j++) - { - header.SubModel.DmaVifs.AddRange(ReadDmaChain(stream, dmaChains[j])); - } + stream.Position = header.UnkDataOffset; + subModel.UnknownData = stream.ReadBytes(0x120); if (header.BoneOffset != 0) { stream.Position = header.BoneOffset; - header.SubModel.Bones = For(header.BoneCount, () => ReadBone(stream)).ToList(); + subModel.Bones = For(subModel.BoneCount, () => ReadBone(stream)).ToList(); } - subModel = header.SubModel; + subModel.DmaChains = dmaChainHeaders.Select(x => ReadDmaChain(stream, x)).ToList(); return header.NextOffset; } - private static IEnumerable ReadDmaChain(Stream stream, DmaChainHeader dmaChainHeader) + private static DmaChain ReadDmaChain(Stream stream, DmaChainHeader dmaChainHeader) { var dmaVifs = new List(); - var count1a = stream.SetPosition(dmaChainHeader.Unk14).ReadInt32(); + var count1a = stream.SetPosition(dmaChainHeader.Count1aOffset).ReadInt32(); var alv1 = For(count1a, () => stream.ReadInt32()).ToList(); - var offsetDmaTag = new List(); + var offsetDmaPackets = new List(); var alaxi = new List(); var alaxref = new List(); - offsetDmaTag.Add(dmaChainHeader.DmaOffset); + offsetDmaPackets.Add(dmaChainHeader.DmaOffset); var offsetDmaBase = dmaChainHeader.DmaOffset + 0x10; @@ -138,46 +195,178 @@ private static IEnumerable ReadDmaChain(Stream stream, DmaChainHeader dm { if (alv1[i] == -1) { - offsetDmaTag.Add(offsetDmaBase + 0x10); + offsetDmaPackets.Add(offsetDmaBase + 0x10); offsetDmaBase += 0x20; - } - else - { - offsetDmaBase += 0x10; - } - if (i + 1 == count1a) - { - alaxref.Add(alv1[i]); - alaxi.Add(alaxref.ToArray()); - alaxref.Clear(); - } - else if (alv1[i] == -1) - { alaxi.Add(alaxref.ToArray()); alaxref.Clear(); } else { + offsetDmaBase += 0x10; alaxref.Add(alv1[i]); } } - for (var i = 0; i < offsetDmaTag.Count; i++) + alaxi.Add(alaxref.ToArray()); + alaxref.Clear(); + + var dmaPackets = offsetDmaPackets + .Select(offset => ReadTags(stream.SetPosition(offset)).ToArray()) + .ToArray(); + + for (var i = 0; i < offsetDmaPackets.Count; i++) + { + var dmaTag = dmaPackets[i][0].DmaTag; + var baseAddress = dmaTag.Qwc != 0 ? dmaPackets[i][1].Parameter : 0; + stream.Position = dmaTag.Address & 0x7FFFFFFF; + var vifPacket = stream.ReadBytes(dmaTag.Qwc * 0x10); + dmaVifs.Add(new DmaVif(dmaChainHeader.TextureIndex, alaxi[i], vifPacket, baseAddress)); + } + + return new DmaChain { - stream.Position = offsetDmaTag[i]; - var count = stream.ReadInt16(); - var unknown = stream.ReadInt16(); - var offset = stream.ReadInt32(); + Unk00 = dmaChainHeader.Unk00, + TextureIndex = dmaChainHeader.TextureIndex, + Unk08 = dmaChainHeader.Unk08, + DmaLength = dmaChainHeader.DmaLength, + DmaVifs = dmaVifs, + }; + } - stream.Position = offset & 0x7FFFFFFF; - var vifPacket = stream.ReadBytes(count * 0x10); - dmaVifs.Add(new DmaVif(dmaChainHeader.TextureIndex, alaxi[i], vifPacket)); + private static IEnumerable ReadTags(Stream stream) + { + while (true) + { + var dmaPacket = BinaryMapping.ReadObject(stream); + yield return dmaPacket; + + if (dmaPacket.DmaTag.Qwc == 0) + yield break; } + } + + private static void WriteSubModel(Stream stream, SubModel subModel, int baseAddress) + { + var header = new SubModelHeader + { + Type = subModel.Type, + Unk04 = subModel.Unk04, + Unk08 = subModel.Unk08, + Unk = subModel.Unk, + DmaChainCount = subModel.DmaChains.Count, + }; + + stream.Position += 0x20; // skip header + stream.Position += subModel.DmaChainCount * 0x20; + + if (subModel.Type == Entity) + { + header.UnkDataOffset = (int)stream.Position; + stream.Write(subModel.UnknownData); + + header.BoneOffset = (int)stream.Position; + header.BoneCount = (short)subModel.Bones.Count; + foreach (var bone in subModel.Bones) + WriteBone(stream, bone); + } + else if (subModel.Type == Shadow) + { + header.UnkDataOffset = 0; + header.BoneOffset = 0; + header.BoneCount = subModel.BoneCount; + } + else + throw new NotImplementedException($"Submodel type {subModel.Type} not supported."); + + var dmaChainHeaders = subModel.DmaChains.Select(x => WriteDmaChain(stream, x)).ToList(); + + stream.SetLength(stream.AlignPosition(0x80).Position); + header.NextOffset = baseAddress >= 0 ? (int)(baseAddress + stream.Position) : 0; + + stream.Position = 0; + BinaryMapping.WriteObject(stream, header); + foreach (var dmaChainHeader in dmaChainHeaders) + BinaryMapping.WriteObject(stream, dmaChainHeader); + } + + private static DmaChainHeader WriteDmaChain(Stream stream, DmaChain dmaChain) + { + var dmaChainHeader = new DmaChainHeader + { + Unk00 = dmaChain.Unk00, + TextureIndex = dmaChain.TextureIndex, + Unk08 = dmaChain.Unk08, + DmaLength = dmaChain.DmaLength, + }; + + var dmaVifs = dmaChain.DmaVifs; + + var vifPacketOffsets = new List(); + foreach (var dmaVif in dmaVifs) + { + vifPacketOffsets.Add((int)stream.Position); + stream.Write(dmaVif.VifPacket); + } + + dmaChainHeader.DmaOffset = (int)stream.Position; + + for (var i = 0; i < dmaVifs.Count; i++) + { + if (dmaVifs[i].BaseAddress > 0) + { + BinaryMapping.WriteObject(stream, new DmaPacket + { + DmaTag = new DmaTag + { + Qwc = (ushort)(dmaVifs[i].VifPacket.Length / 0x10), + TagId = 3, + Irq = false, + Address = vifPacketOffsets[i] + }, + VifCode = new VifCode { }, + Parameter = 0 + }); + + for (var j = 0; j < dmaVifs[i].Alaxi.Length; j++) + { + BinaryMapping.WriteObject(stream, new DmaPacket + { + DmaTag = new DmaTag + { + Qwc = 4, + TagId = 3, + Irq = false, + Address = dmaVifs[i].Alaxi[j] + }, + VifCode = DefaultVifCode, + Parameter = dmaVifs[i].BaseAddress + j * 4 + }); + } + } + + BinaryMapping.WriteObject(stream, CloseDmaPacket); + } + + var alv1 = new List(); + foreach (var vif in dmaVifs) + { + alv1.AddRange(vif.Alaxi); + alv1.Add(-1); + } + alv1.RemoveAt(alv1.Count - 1); + + dmaChainHeader.Count1aOffset = (int)stream.Position; + stream.Write(alv1.Count); + foreach (var alvItem in alv1) + stream.Write(alvItem); + + stream.AlignPosition(0x10); - return dmaVifs; + return dmaChainHeader; } private static Bone ReadBone(Stream stream) => BinaryMapping.ReadObject(stream); + private static void WriteBone(Stream stream, Bone bone) => BinaryMapping.WriteObject(stream, bone); } } diff --git a/OpenKh.Kh2/Mdlx.cs b/OpenKh.Kh2/Mdlx.cs index 7d4c80657..ad9cb455d 100644 --- a/OpenKh.Kh2/Mdlx.cs +++ b/OpenKh.Kh2/Mdlx.cs @@ -1,8 +1,10 @@ using OpenKh.Common; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using Xe.BinaryMapper; using Xe.IO; namespace OpenKh.Kh2 @@ -11,6 +13,7 @@ public partial class Mdlx { private const int Map = 2; private const int Entity = 3; + private const int Shadow = 4; private const int ReservedArea = 0x90; public List SubModels { get; } @@ -32,6 +35,29 @@ private Mdlx(Stream stream) } } + public void Write(Stream realStream) + { + var stream = new MemoryStream(); + Write(stream, SubModels); + + realStream.Position = ReservedArea; + realStream.Write(stream.GetBuffer(), 0, (int)stream.Length); + } + + private static void Write(Stream stream, List subModels) + { + var baseAddress = 0; + for (var i = 0; i < subModels.Count; i++) + { + if (i + 1 >= subModels.Count) + baseAddress = -1; + + var subModelStream = new MemoryStream(); + WriteSubModel(subModelStream, subModels[i], baseAddress); + subModelStream.SetPosition(0).Copy(stream, (int)subModelStream.Length); + } + } + public static Mdlx Read(Stream stream) => new Mdlx(stream.SetPosition(0)); diff --git a/OpenKh.Tests/kh2/MdlxTests.cs b/OpenKh.Tests/kh2/MdlxTests.cs index 2fb0c6b45..c384aee2d 100644 --- a/OpenKh.Tests/kh2/MdlxTests.cs +++ b/OpenKh.Tests/kh2/MdlxTests.cs @@ -1,6 +1,7 @@ -using OpenKh.Common; +using OpenKh.Common; using OpenKh.Kh2; using System.IO; +using System.Linq; using Xunit; namespace OpenKh.Tests.kh2 @@ -21,10 +22,12 @@ public void ShouldReadTheCorrectAmountOfSubModels() => File.OpenRead(FileName).U [Fact] public void ShouldReadVifPackets() => File.OpenRead(FileName).Using(stream => { - var dmaVifs = Mdlx.Read(stream).SubModels[0].DmaVifs; - Assert.Equal(58, dmaVifs.Count); - - var dmaVif = dmaVifs[0]; + var dmaChain = Mdlx.Read(stream).SubModels[0].DmaChains; + Assert.Equal(26, dmaChain[0].DmaVifs.Count); + Assert.Equal(4, dmaChain.Count); + Assert.Equal(58, dmaChain.Sum(x => x.DmaVifs.Count)); + + var dmaVif = dmaChain[0].DmaVifs[0]; Assert.Equal(0, dmaVif.TextureIndex); Assert.Equal(10, dmaVif.Alaxi.Length); Assert.Equal(53, dmaVif.Alaxi[0]); @@ -59,6 +62,20 @@ public void ShouldReadBones() => File.OpenRead(FileName).Using(stream => Assert.Equal(0, bone.TranslationW); }); + [Fact] + public void ShouldWriteBackTheExactSameFile() => File.OpenRead(FileName).Using(stream => + { + Helpers.AssertStream(stream, inStream => + { + var mdlx = Mdlx.Read(inStream); + + var outStream = new MemoryStream(); + mdlx.Write(outStream); + + return outStream; + }); + }); + [Fact] public void alb1t2() => File.OpenRead(MapFileName).Using(stream => {