From 16511cf829dce9bb8dfe07916d7b6e2f710c229b Mon Sep 17 00:00:00 2001 From: frozenreflex Date: Tue, 6 Aug 2024 00:25:58 -0500 Subject: [PATCH 1/8] Scd improvements (cherry picked from commit 776bd28166092bf29c2c9eaf4dbbfdbc0b72f9d7) --- OpenKh.Bbs/Scd.cs | 64 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/OpenKh.Bbs/Scd.cs b/OpenKh.Bbs/Scd.cs index cb9e7af88..a22787c4d 100644 --- a/OpenKh.Bbs/Scd.cs +++ b/OpenKh.Bbs/Scd.cs @@ -41,7 +41,7 @@ public class StreamHeader [Data] public uint StreamSize { get; set; } [Data] public uint ChannelCount { get; set; } [Data] public uint SampleRate { get; set; } - [Data] public uint Codec { get; set; } + [Data] public uint Codec { get; set; } //6 = .ogg, everything else is msadpcm [Data] public uint LoopStart { get; set; } [Data] public uint LoopEnd { get; set; } [Data] public uint ExtraDataSize { get; set; } @@ -61,32 +61,67 @@ public class VAGHeader [Data(Count = 16)] public string Name { get; set; } } - public static Header header = new Header(); - public static TableOffsetHeader tableOffsetHeader = new TableOffsetHeader(); - public static List StreamFiles = new List(); + public Header header = new(); + public TableOffsetHeader tableOffsetHeader = new(); + public List StreamHeaders = []; + public List StreamFiles = []; + public List MediaFiles = []; public static Scd Read(Stream stream) { - Scd scd = new Scd(); - - header = BinaryMapping.ReadObject
(stream); - tableOffsetHeader = BinaryMapping.ReadObject(stream); + var scd = new Scd + { + header = BinaryMapping.ReadObject
(stream), + tableOffsetHeader = BinaryMapping.ReadObject(stream), + }; - stream.Seek(tableOffsetHeader.Table1Offset, SeekOrigin.Begin); + stream.Seek(scd.tableOffsetHeader.Table1Offset, SeekOrigin.Begin); - List SoundOffsets = new List(); - for (int i = 0; i < tableOffsetHeader.Table1ElementCount; i++) + var SoundOffsets = new List(); + for (var i = 0; i < scd.tableOffsetHeader.Table1ElementCount; i++) { SoundOffsets.Add(stream.ReadUInt32()); } - foreach(uint off in SoundOffsets) + for (var index = 0; index < SoundOffsets.Count; index++) { + var off = SoundOffsets[index]; + var next = (int)((index == SoundOffsets.Count - 1 ? stream.Length : SoundOffsets[index + 1]) - off); stream.Seek(off, SeekOrigin.Begin); var streamInfo = BinaryMapping.ReadObject(stream); + scd.StreamHeaders.Add(streamInfo); + + var st = stream.ReadBytes(next - 0x20); + scd.StreamFiles.Add(st); + + //https://github.com/Leinxad/KHPCSoundTools/blob/main/SCDInfo/Program.cs#L109 + if (streamInfo.Codec == 6) + { + var extradataOffset = 0u; + if (streamInfo.AuxChunkCount > 0) extradataOffset += BitConverter.ToUInt32(st.Skip((int)extradataOffset).Take(4).ToArray(), 0); + + var encryptionKey = st[extradataOffset + 0x02]; + var seekTableSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x10).Take(4).ToArray(), 0); + var vorbHeaderSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x14).Take(4).ToArray(), 0); + + var startOffset = extradataOffset + 0x20 + seekTableSize; + + var decryptedFile = st.ToArray(); + + var endPosition = startOffset + vorbHeaderSize; + + for (var i = startOffset; i < endPosition; i++) + { + decryptedFile[i] = (byte)(decryptedFile[i]^encryptionKey); + } + + var oggSize = vorbHeaderSize + streamInfo.StreamSize; + + scd.MediaFiles.Add(decryptedFile.Skip((int)startOffset).Take((int)oggSize).ToArray()); + } + else scd.MediaFiles.Add(Array.Empty()); + - byte[] st = stream.ReadBytes((int)streamInfo.StreamSize); - StreamFiles.Add(st); // Convert to VAG Streams. /*VAGHeader vagHeader = new VAGHeader(); @@ -98,7 +133,6 @@ public static Scd Read(Stream stream) } - return scd; } From 27b5c562a39468d6f5ff21c1448d483c4d43c4a3 Mon Sep 17 00:00:00 2001 From: frozenreflex Date: Wed, 7 Aug 2024 17:22:49 -0500 Subject: [PATCH 2/8] Add preliminary Cvbl support (cherry picked from commit b8e2a8921fff9cdd9b284b04efa890208fabb958) --- OpenKh.Kh1/Cvbl.cs | 368 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 OpenKh.Kh1/Cvbl.cs diff --git a/OpenKh.Kh1/Cvbl.cs b/OpenKh.Kh1/Cvbl.cs new file mode 100644 index 000000000..01825a2ba --- /dev/null +++ b/OpenKh.Kh1/Cvbl.cs @@ -0,0 +1,368 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using OpenKh.Common; +using Xe.BinaryMapper; + +namespace OpenKh.Kh1 +{ + public class Cvbl + { + public class CvblHeader + { + [Data] public uint Unk1 { get; set; } + [Data] public uint NumMeshes { get; set; } + [Data] public ushort NumUnknownEntries { get; set; } + [Data] public ushort HasUnknownEntries { get; set; } + [Data] public uint Unk2 { get; set; } + } + public class MeshEntry + { + [Data] public ushort Unk1 { get; set; } + [Data] public ushort JointStyle { get; set; } + [Data] public int Material { get; set; } + [Data] public int Unk2 { get; set; } + [Data] public uint MeshOffset { get; set; } + } + + public class VertexStyle8 : IVertex + { + [Data] public float NormalX { get; set; } + [Data] public float NormalY { get; set; } + [Data] public float NormalZ { get; set; } + [Data] public ushort JointSlotId { get; set; } + [Data] public byte FaceType { get; set; } // 1 = (-2, -1, 0), 2 = (0, -1, -2) + [Data] public byte UseFace { get; set; } // must == 0 for face + [Data] public float U { get; set; } + [Data] public float V { get; set; } + + [Data] public float Unk1 { get; set; } // is this the weird Y UV coordinate that mdls has? + [Data] public uint Unk2 { get; set; } + + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + [Data] public float Weight { get; set; } + + + + public Vector3 Position + { + get => new(PositionX, PositionY, PositionZ); + set + { + PositionX = value.X; + PositionY = value.Y; + PositionZ = value.Z; + } + } + public Vector3 Normal => new(NormalX, NormalY, NormalZ); + public Vector2 UV => new(U, V); + public ushort[] Joints + { + get => [JointSlotId]; + set + { + if (value is null || value.Length == 0) return; + JointSlotId = value.First(); + } + } + public float[] Weights => [Weight]; + + public int Face => UseFace == 0 ? FaceType : 0; + public int Size => 48; + } + + public class VertexStyle9 : IVertex + { + [Data] public float NormalX { get; set; } + [Data] public float NormalY { get; set; } + [Data] public float NormalZ { get; set; } + [Data] public ushort JointCount { get; set; } + [Data] public byte FaceType { get; set; } // 1 = (-2, -1, 0), 2 = (0, -1, -2) + [Data] public byte UseFace { get; set; } // must == 0 for face + [Data] public float U { get; set; } + [Data] public float V { get; set; } + [Data(Count = 8)] public byte[] JointSlotIds { get; set; } + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + [Data] public float PositionW { get; set; } // ??? maybe unk? + [Data(Count = 8)] public float[] JointWeights { get; set; } + + public Vector3 Position + { + get => new(PositionX, PositionY, PositionZ); + set + { + PositionX = value.X; + PositionY = value.Y; + PositionZ = value.Z; + } + } + public Vector3 Normal => new(NormalX, NormalY, NormalZ); + public Vector2 UV => new(U, V); + public ushort[] Joints + { + get => JointSlotIds.Select(i => (ushort)i).Take(JointCount).ToArray(); + set + { + if (value is null || value.Length != JointSlotIds.Length) return; + JointSlotIds = value.Select(i => (byte)i).ToArray(); + } + } + public float[] Weights => JointWeights.Take(JointCount).ToArray(); + + public int Face => UseFace == 0 ? FaceType : 0; + public int Size => 80; + } + + public class VertexStyle10 : IVertex + { + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + [Data] public ushort JointSlotId { get; set; } + [Data] public byte FaceType { get; set; } // 1 = (-2, -1, 0), 2 = (0, -1, -2) + [Data] public byte UseFace { get; set; } // must == 0 for face + + public Vector3 Position + { + get => new(PositionX, PositionY, PositionZ); + set + { + PositionX = value.X; + PositionY = value.Y; + PositionZ = value.Z; + } + } + + public ushort[] Joints + { + get => [JointSlotId]; + set + { + if (value is null || value.Length == 0) return; + JointSlotId = value.First(); + } + } + public float[] Weights => [1]; + + public int Face => UseFace == 0 ? FaceType : 0; + public int Size => 16; + } + + public interface IVertex + { + public Vector3 Position { get; set; } + public Vector3 Normal => Vector3.Zero; + public Vector2 UV => Vector2.Zero; + public ushort[] Joints { get; set; } + public float[] Weights => Array.Empty(); + public int Face => 0; + public int Size => 0; + } + + public class Submesh + { + public int Material; + public ushort JointStyle; + + public List Vertices = new(); + public List Faces = new(); + public Dictionary JointSlots = new(); + + public Dictionary> ExtraJointData = new(); + } + + public CvblHeader Header; + public List Submeshes = new(); + + private static Dictionary MdlsJointsToDictionary(List joints) + { + if (joints is null) return null; + var dict = new Dictionary(); + for (var i = 0u; i < joints.Count; i++) + { + var j = joints[(int)i]; + + var scaleMatrix = Matrix4x4.CreateScale(new Vector3(j.ScaleX, j.ScaleY, j.ScaleZ)); + + var rotationMatrixX = Matrix4x4.CreateRotationX(j.RotateX); + var rotationMatrixY = Matrix4x4.CreateRotationY(j.RotateY); + var rotationMatrixZ = Matrix4x4.CreateRotationZ(j.RotateZ); + var rotationMatrix = rotationMatrixX * rotationMatrixY * rotationMatrixZ; + + var translationMatrix = Matrix4x4.CreateTranslation(new Vector3(j.TranslateX, j.TranslateY, j.TranslateZ)); + + dict[i] = scaleMatrix * rotationMatrix * translationMatrix; + } + return dict; + } + + public Cvbl(Stream stream, List mdlsJoints) + { + var file = stream.ReadAllBytes(); + var str = new MemoryStream(file); + var joints = MdlsJointsToDictionary(mdlsJoints); + + Header = BinaryMapping.ReadObject(str); + + str.Position = 0; + + if (Header.HasUnknownEntries is not (0 or 1)) return; + var meshEntries = new List(); + + var meshEntriesOffset = 16 + (Header.HasUnknownEntries == 1 ? Header.NumUnknownEntries * 32 : 0); + + str.Seek(meshEntriesOffset, SeekOrigin.Begin); + + for (var i = 0; i < Header.NumMeshes; i++) meshEntries.Add(BinaryMapping.ReadObject(str)); + + foreach (var meshEntry in meshEntries) + { + var mesh = new Submesh + { + Material = meshEntry.Material, + JointStyle = meshEntry.JointStyle, + }; + var jointStyle = meshEntry.JointStyle; + + var jointSlots = new Dictionary(); + for (var i = 0u; i < 48; i++) + { + jointSlots[i] = (0, Matrix4x4.Identity); + } + + var extraJointData = new Dictionary>(); + + + var totalVertCount = 0u; + var run = true; + var subsectionOffset = meshEntry.MeshOffset + 16; + while (run && subsectionOffset < str.Length) + { + var subsectionDataOffset = subsectionOffset + 8; + + str.Seek(subsectionOffset, SeekOrigin.Begin); + + var subsectionType = str.ReadUInt32(); + var subsectionLength = str.ReadUInt32(); + + if (subsectionType == 1) + { + var numVerts = str.ReadUInt32(); + + var submeshDataOffset = subsectionDataOffset + 24; + + str.Seek(submeshDataOffset, SeekOrigin.Begin); + + for (var i = 0u; i < numVerts; i++) + { + IVertex vert = meshEntry.JointStyle switch + { + 8 => BinaryMapping.ReadObject(str), + 9 => BinaryMapping.ReadObject(str), + 10 => BinaryMapping.ReadObject(str), + _ => null, + }; + + if (vert is null) continue; + + var bones = vert.Joints.Select(j => (ushort)jointSlots[j].Item1).ToArray(); + vert.Joints = bones; + + if (joints != null) + vert.Position = RelativeToGlobalVertex(vert.Position, joints, vert.Joints.Select(j => (uint)j).ToArray(), vert.Weights, + vert.Joints.Select(j => jointSlots[j].Item2).ToArray()); + + + str.Seek(submeshDataOffset + ((i + 1) * vert.Size), SeekOrigin.Begin); + + switch (vert.Face) + { + case 1: + mesh.Faces.Add([totalVertCount + i - 2, totalVertCount + i - 1, totalVertCount + i]); + break; + case 2: + mesh.Faces.Add([totalVertCount + i, totalVertCount + i - 1, totalVertCount + i - 2]); + break; + } + mesh.Vertices.Add(vert); + } + totalVertCount += numVerts; + } + else if (subsectionType == 17) + { + var jointId = str.ReadUInt32(); + var jointSlotId = str.ReadUInt32(); + + jointSlots[jointSlotId] = (jointId, jointSlots[jointSlotId].Item2); + + switch (jointStyle) + { + case 8: + { + var data = new List(); + for (var i = 0; i < 28; i++) data.Add(str.ReadUInt32()); + extraJointData[jointSlotId] = data; + + break; + } + case 9: + { + var data = new List(); + for (var i = 0; i < 28; i++) data.Add(str.ReadUInt32()); + extraJointData[jointSlotId] = data; + + jointSlots[jointSlotId] = (jointId, new Matrix4x4( + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), + str.ReadSingle(), str.ReadSingle(), str.ReadSingle(), str.ReadSingle() + )); + break; + } + case 10: + { + var data = new List(); + for (var i = 0; i < 16; i++) data.Add(str.ReadUInt32()); + extraJointData[jointSlotId] = data; + break; + } + default: + Console.WriteLine($"Warning: Unknown joint style: {jointStyle} @ {subsectionDataOffset}"); + return; + } + } + else if (subsectionType == 32768) run = false; + else break; + subsectionOffset += subsectionLength; + } + + mesh.JointSlots = jointSlots.ToDictionary(i => i.Key, i => i.Value.Item1); + mesh.ExtraJointData = extraJointData; + + Submeshes.Add(mesh); + } + } + + private static Vector3 RelativeToGlobalVertex(Vector3 vertex, Dictionary joints, IReadOnlyList jointIds, IReadOnlyList weights, IReadOnlyList toLocalTransforms) + { + var vert = new Vector4(0, 0, 0, 0); + for (var i = 0; i < jointIds.Count; i++) + { + var jointId = jointIds[i]; + var weight = weights[i]; + var tlt = i < toLocalTransforms.Count ? toLocalTransforms[i] : Matrix4x4.Identity; + var npVert = new Vector4(vertex, 1) * weight; + var globalMat = joints[jointId] * tlt; + npVert = Vector4.Transform(npVert, globalMat); + vert += npVert; + } + return new Vector3(vert.X, vert.Y, vert.Z); + } + } +} From f43981dbec2e870716d57398ed121a48023d1da0 Mon Sep 17 00:00:00 2001 From: Christophe Huybrechts Date: Sun, 24 Aug 2025 22:34:39 +0200 Subject: [PATCH 3/8] Update KH2 doc --- docs/kh2/file/model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/kh2/file/model.md b/docs/kh2/file/model.md index 4b1bca541..d513d2643 100644 --- a/docs/kh2/file/model.md +++ b/docs/kh2/file/model.md @@ -61,7 +61,7 @@ Stored straight after the model [header](#header). | 0x0e | uint8 | Unknown | 0x0f | uint8 | Unknown -UVSC option: +[UVSC option](raw-texture.md#uvsc-uv-scroll): | Bit | Description | |-----|-------------| From 501be0dbebb356a87b2b858c32beee9fb4fd5554 Mon Sep 17 00:00:00 2001 From: Christophe Huybrechts Date: Sun, 24 Aug 2025 22:35:02 +0200 Subject: [PATCH 4/8] KH2 bgm and wd --- OpenKh.Kh2/Bgm.cs | 119 ++++++++++++++++++++++++++++++++++++++++++++++ OpenKh.Kh2/Wd.cs | 7 +++ 2 files changed, 126 insertions(+) create mode 100644 OpenKh.Kh2/Bgm.cs create mode 100644 OpenKh.Kh2/Wd.cs diff --git a/OpenKh.Kh2/Bgm.cs b/OpenKh.Kh2/Bgm.cs new file mode 100644 index 000000000..80791c346 --- /dev/null +++ b/OpenKh.Kh2/Bgm.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Common; +using OpenKh.Kh2.SystemData; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2; + +public class Bgm +{ + [Data] public uint Identifier { get; set; } + [Data] public ushort SequenceID { get; set; } + [Data] public ushort SoundfontID { get; set; } + [Data] public byte TrackCount { get; set; } + [Data] public byte Unk0 { get; set; } + [Data] public byte Unk1 { get; set; } + [Data] public byte Unk2 { get; set; } + [Data] public sbyte Volume { get; set; } + [Data] public byte Unk3 { get; set; } + [Data] public ushort PartsPerQuarterNote { get; set; } + [Data] public uint FileSize { get; set; } + + [Data] public uint Reserved0 { get; set; } + [Data] public uint Reserved1 { get; set; } + [Data] public uint Reserved2 { get; set; } + + public List Tracks = new(); + + public class BgmTrack + { + [Data] public byte[] Raw { get; set; } + } + + public static Bgm Read(Stream stream) + { + var bgm = BinaryMapping.ReadObject(stream); + + bgm.Tracks = Enumerable.Range(0, bgm.TrackCount).Select(_ => stream.ReadBytes((int)stream.ReadUInt32())).ToList(); + + return bgm; + } +} + +public enum BgmTrackCommandType +{ + EndOfTrack = 0x00, + LoopBegin = 0x02, + LoopEnd = 0x03, + SetTempo = 0x08, + Unk0 = 0x0A, + TimeSignature = 0x0C, + Unk1 = 0x0D, + NoteOnPreviousKeyVelocity = 0x10, + NoteOn = 0x11, + NoteOnPreviousVelocity = 0x12, + NoteOnPreviousKey = 0x13, + NoteOffPreviousNote = 0x18, + Unk2 = 0x19, + NoteOff = 0x1A, + ProgramChange = 0x20, + Volume = 0x22, + Expression = 0x24, + Pan = 0x26, + Unk3 = 0x28, + Unk4 = 0x31, + Unk5 = 0x34, + Unk6 = 0x35, + SustainPedal = 0x3C, + Unk7 = 0x3E, + Unk8 = 0x40, + Unk9 = 0x47, + Unk10 = 0x48, + Unk11 = 0x50, + Unk12 = 0x58, + Unk13 = 0x5C, + Portamento = 0x5D, +} + +public static class BgmTrackCommandHelper +{ + public static int ArgumentByteReadCount(this BgmTrackCommandType command) + { + switch (command) + { + case BgmTrackCommandType.SetTempo: + case BgmTrackCommandType.Unk0: + case BgmTrackCommandType.Unk1: + case BgmTrackCommandType.NoteOnPreviousVelocity: + case BgmTrackCommandType.NoteOnPreviousKey: + case BgmTrackCommandType.NoteOff: + case BgmTrackCommandType.ProgramChange: + case BgmTrackCommandType.Volume: + case BgmTrackCommandType.Expression: + case BgmTrackCommandType.Pan: + case BgmTrackCommandType.Unk3: + case BgmTrackCommandType.Unk4: + case BgmTrackCommandType.Unk5: + case BgmTrackCommandType.Unk6: + case BgmTrackCommandType.SustainPedal: + case BgmTrackCommandType.Unk7: + case BgmTrackCommandType.Unk12: + case BgmTrackCommandType.Portamento: + return 1; + case BgmTrackCommandType.TimeSignature: + case BgmTrackCommandType.NoteOn: + case BgmTrackCommandType.Unk2: + case BgmTrackCommandType.Unk9: + return 2; + case BgmTrackCommandType.Unk8: + case BgmTrackCommandType.Unk10: + case BgmTrackCommandType.Unk11: + return 3; + default: + return 0; + } + } +} diff --git a/OpenKh.Kh2/Wd.cs b/OpenKh.Kh2/Wd.cs new file mode 100644 index 000000000..003d94da4 --- /dev/null +++ b/OpenKh.Kh2/Wd.cs @@ -0,0 +1,7 @@ +namespace OpenKh.Kh2 +{ + public class Wd + { + + } +} From 52da10b55ae5ae64f99077d4df8f6c72a45937d0 Mon Sep 17 00:00:00 2001 From: Christophe Huybrechts Date: Sun, 24 Aug 2025 22:36:55 +0200 Subject: [PATCH 5/8] Changes to scd and cvbl files --- OpenKh.Bbs/Scd.cs | 2 +- OpenKh.Kh1/Cvbl.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/OpenKh.Bbs/Scd.cs b/OpenKh.Bbs/Scd.cs index a22787c4d..10764227d 100644 --- a/OpenKh.Bbs/Scd.cs +++ b/OpenKh.Bbs/Scd.cs @@ -65,7 +65,7 @@ public class VAGHeader public TableOffsetHeader tableOffsetHeader = new(); public List StreamHeaders = []; public List StreamFiles = []; - public List MediaFiles = []; + public List MediaFiles = []; //6 = .ogg file, everything else is a .wav with msadpcm codec, throw it at ffmpeg /shrug public static Scd Read(Stream stream) { diff --git a/OpenKh.Kh1/Cvbl.cs b/OpenKh.Kh1/Cvbl.cs index 01825a2ba..35f42d7d5 100644 --- a/OpenKh.Kh1/Cvbl.cs +++ b/OpenKh.Kh1/Cvbl.cs @@ -179,6 +179,7 @@ public class Submesh public CvblHeader Header; public List Submeshes = new(); + public List MeshEntries = new(); private static Dictionary MdlsJointsToDictionary(List joints) { @@ -221,6 +222,8 @@ public Cvbl(Stream stream, List mdlsJoints) for (var i = 0; i < Header.NumMeshes; i++) meshEntries.Add(BinaryMapping.ReadObject(str)); + MeshEntries = meshEntries; + foreach (var meshEntry in meshEntries) { var mesh = new Submesh From a4086b408af227f92b2d01f80935239047438abd Mon Sep 17 00:00:00 2001 From: Christophe Huybrechts Date: Sun, 24 Aug 2025 22:38:02 +0200 Subject: [PATCH 6/8] BBS scd and EGS tools --- OpenKh.Bbs/Scd.cs | 91 +++++++++++++++++++++++++----------------- OpenKh.Egs/EgsTools.cs | 20 ++++++++-- 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/OpenKh.Bbs/Scd.cs b/OpenKh.Bbs/Scd.cs index 10764227d..5a394e50f 100644 --- a/OpenKh.Bbs/Scd.cs +++ b/OpenKh.Bbs/Scd.cs @@ -41,7 +41,7 @@ public class StreamHeader [Data] public uint StreamSize { get; set; } [Data] public uint ChannelCount { get; set; } [Data] public uint SampleRate { get; set; } - [Data] public uint Codec { get; set; } //6 = .ogg, everything else is msadpcm + [Data] public uint Codec { get; set; } //6 = .ogg, 12 = MSADPCM .wav [Data] public uint LoopStart { get; set; } [Data] public uint LoopEnd { get; set; } [Data] public uint ExtraDataSize { get; set; } @@ -61,7 +61,7 @@ public class VAGHeader [Data(Count = 16)] public string Name { get; set; } } - public Header header = new(); + public Header FileHeader = new(); public TableOffsetHeader tableOffsetHeader = new(); public List StreamHeaders = []; public List StreamFiles = []; @@ -69,24 +69,23 @@ public class VAGHeader public static Scd Read(Stream stream) { + stream.Seek(0, SeekOrigin.Begin); + var scd = new Scd { - header = BinaryMapping.ReadObject
(stream), + FileHeader = BinaryMapping.ReadObject
(stream), tableOffsetHeader = BinaryMapping.ReadObject(stream), }; stream.Seek(scd.tableOffsetHeader.Table1Offset, SeekOrigin.Begin); - var SoundOffsets = new List(); - for (var i = 0; i < scd.tableOffsetHeader.Table1ElementCount; i++) - { - SoundOffsets.Add(stream.ReadUInt32()); - } + var soundOffsets = new List(); + for (var i = 0; i < scd.tableOffsetHeader.Table1ElementCount; i++) soundOffsets.Add(stream.ReadUInt32()); - for (var index = 0; index < SoundOffsets.Count; index++) + for (var index = 0; index < soundOffsets.Count; index++) { - var off = SoundOffsets[index]; - var next = (int)((index == SoundOffsets.Count - 1 ? stream.Length : SoundOffsets[index + 1]) - off); + var off = soundOffsets[index]; + var next = (int)(index == soundOffsets.Count - 1 ? stream.Length : soundOffsets[index + 1] - off); stream.Seek(off, SeekOrigin.Begin); var streamInfo = BinaryMapping.ReadObject(stream); scd.StreamHeaders.Add(streamInfo); @@ -95,41 +94,59 @@ public static Scd Read(Stream stream) scd.StreamFiles.Add(st); //https://github.com/Leinxad/KHPCSoundTools/blob/main/SCDInfo/Program.cs#L109 - if (streamInfo.Codec == 6) + switch (streamInfo.Codec) { - var extradataOffset = 0u; - if (streamInfo.AuxChunkCount > 0) extradataOffset += BitConverter.ToUInt32(st.Skip((int)extradataOffset).Take(4).ToArray(), 0); + case 6: + { + var extradataOffset = 0u; + if (streamInfo.AuxChunkCount > 0) extradataOffset += BitConverter.ToUInt32(st.Skip(0x04).Take(4).ToArray(), 0); - var encryptionKey = st[extradataOffset + 0x02]; - var seekTableSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x10).Take(4).ToArray(), 0); - var vorbHeaderSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x14).Take(4).ToArray(), 0); + var encryptionKey = st[extradataOffset + 0x02]; + var seekTableSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x10).Take(4).ToArray(), 0); + var vorbHeaderSize = BitConverter.ToUInt32(st.Skip((int)extradataOffset + 0x14).Take(4).ToArray(), 0); - var startOffset = extradataOffset + 0x20 + seekTableSize; + var startOffset = extradataOffset + 0x20 + seekTableSize; - var decryptedFile = st.ToArray(); + var decryptedFile = st.ToArray(); - var endPosition = startOffset + vorbHeaderSize; + var endPosition = startOffset + vorbHeaderSize; - for (var i = startOffset; i < endPosition; i++) - { - decryptedFile[i] = (byte)(decryptedFile[i]^encryptionKey); - } + for (var i = startOffset; i < endPosition; i++) decryptedFile[i] = (byte)(decryptedFile[i]^encryptionKey); + + var oggSize = vorbHeaderSize + streamInfo.StreamSize; - var oggSize = vorbHeaderSize + streamInfo.StreamSize; + scd.MediaFiles.Add(decryptedFile.Skip((int)startOffset).Take((int)oggSize).ToArray()); + break; + } + case 12: + { + var streamSize = streamInfo.StreamSize; + var channelCount = streamInfo.ChannelCount; + var sampleRate = streamInfo.SampleRate; + + var length = streamSize + (0x4e - 0x8); + + var msadpcm = Array.Empty() + .Concat(BitConverter.GetBytes(0x46464952)) //"RIFF" + .Concat(BitConverter.GetBytes(length)) //overall file size - 8 + .Concat(BitConverter.GetBytes(0x45564157)) //"WAVE" + .Concat(BitConverter.GetBytes(0x20746D66)) //"fmt " + .Concat(BitConverter.GetBytes(0x32)) + .Concat(st.Take(0x32)) + .Concat(BitConverter.GetBytes(0x61746164)) //"data" + .Concat(BitConverter.GetBytes((int)streamSize)) + .Concat(st.Skip(0x32)) + .ToArray(); - scd.MediaFiles.Add(decryptedFile.Skip((int)startOffset).Take((int)oggSize).ToArray()); + scd.MediaFiles.Add(msadpcm); + break; + } + default: + { + scd.MediaFiles.Add(null); + break; + } } - else scd.MediaFiles.Add(Array.Empty()); - - - - // Convert to VAG Streams. - /*VAGHeader vagHeader = new VAGHeader(); - vagHeader.ChannelCount = (byte)streamInfo.ChannelCount; - vagHeader.SamplingFrequency = (byte)streamInfo.SampleRate; - vagHeader.Version = 3; - vagHeader.Magic = 0x70474156; - vagHeader.Name = p.ToString();*/ } diff --git a/OpenKh.Egs/EgsTools.cs b/OpenKh.Egs/EgsTools.cs index 21ffa2f42..5123e7c43 100644 --- a/OpenKh.Egs/EgsTools.cs +++ b/OpenKh.Egs/EgsTools.cs @@ -68,7 +68,13 @@ public class EgsTools #region Extract - public static void Extract(string inputHed, string output, bool doNotExtractAgain = false) + public enum ExtractFileOutputMode + { + SeparateRoot, + Adjacent, + } + + public static void Extract(string inputHed, string output, bool doNotExtractAgain = false, ExtractFileOutputMode outputMode = ExtractFileOutputMode.SeparateRoot) { var outputDir = output ?? Path.GetFileNameWithoutExtension(inputHed); using var hedStream = File.OpenRead(inputHed); @@ -80,7 +86,11 @@ public static void Extract(string inputHed, string output, bool doNotExtractAgai if (!Names.TryGetValue(hash, out var fileName)) fileName = $"{hash}.dat"; - var outputFileName = Path.Combine(outputDir, ORIGINAL_FILES_FOLDER_NAME, fileName); + var outputFileName = outputMode switch + { + ExtractFileOutputMode.Adjacent => Path.Combine(outputDir, fileName), + _ => Path.Combine(outputDir, ORIGINAL_FILES_FOLDER_NAME, fileName), + }; if (doNotExtractAgain && File.Exists(outputFileName)) continue; @@ -92,7 +102,11 @@ public static void Extract(string inputHed, string output, bool doNotExtractAgai File.Create(outputFileName).Using(stream => stream.Write(hdAsset.OriginalData)); - outputFileName = Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName); + outputFileName = outputMode switch + { + ExtractFileOutputMode.Adjacent => Path.Combine(outputDir, $"{fileName}.remastered.d"), + _ => Path.Combine(outputDir, REMASTERED_FILES_FOLDER_NAME, fileName), + }; foreach (var asset in hdAsset.Assets) { From a10c9235820d97afbf18e5efee9b55fd0c1d3ab9 Mon Sep 17 00:00:00 2001 From: Christophe Huybrechts Date: Sun, 24 Aug 2025 22:38:14 +0200 Subject: [PATCH 7/8] Add KH2 bop file --- OpenKh.Kh2/Bop.cs | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 OpenKh.Kh2/Bop.cs diff --git a/OpenKh.Kh2/Bop.cs b/OpenKh.Kh2/Bop.cs new file mode 100644 index 000000000..4d204ed56 --- /dev/null +++ b/OpenKh.Kh2/Bop.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenKh.Common; +using Xe.BinaryMapper; + +namespace OpenKh.Kh2 +{ + public class Bop + { + public class BopEntry + { + [Data] public float PositionX { get; set; } + [Data] public float PositionY { get; set; } + [Data] public float PositionZ { get; set; } + + [Data] public float RotationX { get; set; } + [Data] public float RotationY { get; set; } + [Data] public float RotationZ { get; set; } + + [Data] public float ScaleX { get; set; } + [Data] public float ScaleY { get; set; } + [Data] public float ScaleZ { get; set; } + + [Data] public uint BobIndex { get; set; } + [Data] public uint Group { get; set; } + [Data] public int MotionIndex { get; set; } + [Data] public uint MotionOffset { get; set; } + [Data] public uint Flag { get; set; } + + [Data] public float ModelHUpper { get; set; } + [Data] public float ModelHLower { get; set; } + [Data] public float ModelMUpper { get; set; } + [Data] public float ModelMLower { get; set; } + [Data] public float ModelLUpper { get; set; } + [Data] public float ModelLLower { get; set; } + + [Data] public float PartsHUpper { get; set; } + [Data] public float PartsHLower { get; set; } + [Data] public float PartsMUpper { get; set; } + [Data] public float PartsMLower { get; set; } + [Data] public float PartsLUpper { get; set; } + [Data] public float PartsLLower { get; set; } + } + + public List Entries = new(); + private Bop() + { + + } + public static Bop Read(Stream stream) + { + var bop = new Bop(); + + stream.ReadUInt32(); //the number 8 + var fileSize = stream.ReadUInt32(); + var count = (int)(fileSize / 0x68); + + bop.Entries = Enumerable.Range(0, count).Select(_ => BinaryMapping.ReadObject(stream)).ToList(); + + return bop; + } + } +} From 838650eaf50fde92e37a79f228b616a6b674ed35 Mon Sep 17 00:00:00 2001 From: Christophe Huybrechts Date: Mon, 25 Aug 2025 00:23:42 +0200 Subject: [PATCH 8/8] Fix building. --- OpenKh.Bbs/Scd.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenKh.Bbs/Scd.cs b/OpenKh.Bbs/Scd.cs index 5a394e50f..8c9b158f2 100644 --- a/OpenKh.Bbs/Scd.cs +++ b/OpenKh.Bbs/Scd.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Xe.BinaryMapper; using System.IO; +using System.Linq; using OpenKh.Common; namespace OpenKh.Bbs