From 67b2837684381ea7232397994c159207dcadeb1b Mon Sep 17 00:00:00 2001 From: Boris Nikolaev Date: Sat, 14 Feb 2026 13:31:12 +0300 Subject: [PATCH] fix for conversion interruption when errors occur --- dq8chr2glb/ChrFile.cs | 17 +++--- dq8chr2glb/Converter/MDSConverter.cs | 56 ++++++++++++++++---- dq8chr2glb/Converter/UniversalMeshBuilder.cs | 33 +++++++++--- dq8chr2glb/Core/MDSFormat/Reader.cs | 2 +- dq8chr2glb/Core/MOTFormat/Importer.cs | 16 +++++- dq8chr2glb/Logger/Log.cs | 29 +++++++++- dq8chr2glb/Program.cs | 9 ++-- dq8chr2glb/TM2Format/TM2Format.cs | 32 ++++++++--- 8 files changed, 158 insertions(+), 36 deletions(-) diff --git a/dq8chr2glb/ChrFile.cs b/dq8chr2glb/ChrFile.cs index 4c543c0..dad4358 100644 --- a/dq8chr2glb/ChrFile.cs +++ b/dq8chr2glb/ChrFile.cs @@ -7,6 +7,7 @@ using dq8chr2glb.Converter; using dq8chr2glb.Core.InfoCfg; using dq8chr2glb.Core.MDSFormat; using dq8chr2glb.Core.MOTFormat; +using dq8chr2glb.Logger; using SixLabors.ImageSharp; using Texture = dq8chr2glb.TM2Format.Texture; @@ -80,7 +81,7 @@ public class ChrFile } var spacer = new string('.', spaces); - Console.WriteLine($" Process: {file.name + spacer} {file.data.Length} bytes"); + Log.Line($" Process: {file.name + spacer} {file.data.Length} bytes"); } public void Clean() @@ -118,7 +119,16 @@ public class ChrFile private void ProcessMDSFile(IncludedFile file, string outputDir) { + if (extract) + { + ProcessRawFile(file, outputDir); + } + var mdsScene = Reader.Read(file.data, file.name); + if (mdsScene == null) + { + return; + } if (convert) { @@ -127,11 +137,6 @@ public class ChrFile converter.Convert(mdsScene, textures, rootName); mdsConverters.Add(converter); } - - if (extract) - { - ProcessRawFile(file, outputDir); - } } private void ProcessConfig(IncludedFile file, string outputDir) diff --git a/dq8chr2glb/Converter/MDSConverter.cs b/dq8chr2glb/Converter/MDSConverter.cs index 75ed4d6..f0916bc 100644 --- a/dq8chr2glb/Converter/MDSConverter.cs +++ b/dq8chr2glb/Converter/MDSConverter.cs @@ -38,6 +38,13 @@ public class MDSConverter var gltfScene = _gltfModel.UseScene("DefaultScene"); var nodesWithParents = new HashSet(); + + if (scene.nodes == null || scene.nodes.Length == 0) + { + Log.Line($"There is nothing to convert in {name}", LogLevel.Info); + return; + } + foreach (var node in scene.nodes) { if (node.parentIndex >= 0) @@ -125,12 +132,13 @@ public class MDSConverter for (var i = clip.startFrame; i < clip.endFrame; i++) { - var keyframe = curve.keyframes[i]; - if (keyframe == null) + if (i >= curve.keyframes.Count || curve.keyframes[i] == null) { continue; } + var keyframe = curve.keyframes[i]; + if (curve.curveType == KeyframeType.Quaternion) { var time = keyframe.frame / FRAMERATE / clip.speed; @@ -161,6 +169,11 @@ public class MDSConverter { foreach (var tex in textures) { + if (tex == null) + { + continue; + } + byte[] imageBytes; using (var ms = new MemoryStream()) { @@ -240,20 +253,36 @@ public class MDSConverter } } - skin.BindJoints(jointBindings); + try + { + skin.BindJoints(jointBindings); + } + catch (Exception e) + { + Log.Error(e); + return; + } foreach (var node in _gltfModel.LogicalNodes) { if (node.Mesh != null) { - if (node.Mesh.Primitives.Any(i => i.VertexAccessors.ContainsKey("WEIGHTS_0"))) + var hasWeights = node.Mesh.Primitives.All(i => i.VertexAccessors.ContainsKey("WEIGHTS_0")); + if (hasWeights) { if (Quaternion.Dot(node.LocalTransform.GetDecomposed().Rotation, Quaternion.Identity) >= 0.999f) { node.LocalTransform = Matrix4x4.Identity; } - node.Skin = skin; + try + { + node.Skin = skin; + } + catch (Exception e) + { + Log.Line(e.TargetSite.Name); + } } } } @@ -273,13 +302,20 @@ public class MDSConverter { var path = Path.Combine(filePath, _name + (textFormat ? ".gltf" : ".glb")); - if (textFormat) + try { - _gltfModel.SaveGLTF(path); + if (textFormat) + { + _gltfModel.SaveGLTF(path); + } + else + { + _gltfModel.SaveGLB(path); + } } - else + catch (Exception e) { - _gltfModel.SaveGLB(path); + Log.Error(e); } } -} \ No newline at end of file +} diff --git a/dq8chr2glb/Converter/UniversalMeshBuilder.cs b/dq8chr2glb/Converter/UniversalMeshBuilder.cs index 99802d0..8c9312d 100644 --- a/dq8chr2glb/Converter/UniversalMeshBuilder.cs +++ b/dq8chr2glb/Converter/UniversalMeshBuilder.cs @@ -20,7 +20,6 @@ namespace dq8chr2glb.Converter var hasUvs = (mdsMesh.features & MeshFeatures.UVs) != 0; var hasSkinning = (mdsMesh.features & MeshFeatures.Weights) != 0; - // Выбираем подходящий тип вершины на основе атрибутов if (hasSkinning) { if (hasUvs) @@ -52,9 +51,9 @@ namespace dq8chr2glb.Converter private static (bool hasUvs, bool hasSkinning) AnalyzeMeshAttributes(MDSMesh mdsMesh) { var hasUvs = mdsMesh.uv != null && mdsMesh.uv.Length > 0 && - Array.Exists(mdsMesh.uv, uv => uv != null && uv.Length >= 2); + Array.Exists(mdsMesh.uv, uv => uv != null && uv.Length >= 2); var hasSkinning = mdsMesh.weights != null && mdsMesh.bones != null && - mdsMesh.bones.Length > 0; // && Array.Exists(mdsMesh.bones, b => b != null) + mdsMesh.bones.Length > 0; return (hasUvs, hasSkinning); } @@ -102,6 +101,8 @@ namespace dq8chr2glb.Converter var boneWeights = mdsMesh.weights[i]; var bindings = new (int JointIndex, float Weight)[Math.Min(4, boneIndices.Length)]; + + var wSum = 0f; for (var b = 0; b < bindings.Length; b++) { var mdsBoneIndex = boneIndices[b]; @@ -109,14 +110,25 @@ namespace dq8chr2glb.Converter { var gltfBoneIndex = nodesMap[mdsBoneIndex]; bindings[b] = (Math.Max(gltfBoneIndex, 0), boneWeights[b]); + wSum += boneWeights[b]; } else { - // Обработка случая, когда MDS-индекс не найден в маппинге - bindings[b] = (0, boneWeights[b]); // или другое значение по умолчанию + bindings[b] = (0, boneWeights[b]); } } + if (wSum < 0.00001f) + { + skinningData.Add(Array.Empty<(int, float)>()); + continue; + } + + for (var index = 0; index < bindings.Length; index++) + { + bindings[index].Weight /= wSum; + } + skinningData.Add(bindings); } else @@ -185,7 +197,16 @@ namespace dq8chr2glb.Converter } } - return root.CreateMesh(meshBuilder); + try + { + return root.CreateMesh(meshBuilder); + } + catch (Exception e) + { + Log.Line($"Create Mesh Error: {mdsMesh.name}", LogLevel.Error); + Log.Error(e); + return null; + } } private static VertexBuilder CreateVertex( diff --git a/dq8chr2glb/Core/MDSFormat/Reader.cs b/dq8chr2glb/Core/MDSFormat/Reader.cs index 05a1186..b3361dc 100644 --- a/dq8chr2glb/Core/MDSFormat/Reader.cs +++ b/dq8chr2glb/Core/MDSFormat/Reader.cs @@ -17,7 +17,7 @@ public class Reader if (header.version != 200) { - Console.WriteLine($"Unknown version: {header.version}"); + Log.Line($"Unknown version: {header.version}."); return null; } diff --git a/dq8chr2glb/Core/MOTFormat/Importer.cs b/dq8chr2glb/Core/MOTFormat/Importer.cs index 8b11b79..07fc154 100644 --- a/dq8chr2glb/Core/MOTFormat/Importer.cs +++ b/dq8chr2glb/Core/MOTFormat/Importer.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Numerics; +using dq8chr2glb.Logger; namespace dq8chr2glb.Core.MOTFormat; @@ -93,7 +94,15 @@ public class Importer keyframe.type = KeyframeType.Quaternion; keyframe.rotation = ReadQuaternion(data, valuesOffset, scale); - curve.keyframes[frame] = keyframe; + try + { + curve.keyframes[frame] = keyframe; + } + catch (Exception e) + { + Log.Line($"Skip negative frame number: {frame}", LogLevel.Warning); + } + valuesOffset += 8; } } @@ -144,6 +153,11 @@ public class Importer var toValues = header.valuesOffset; foreach (var frame in frames) { + if (frame == null || frame < 0) + { + continue; + } + var scale = new Vector3(header.scaleX, header.scaleY, header.scaleZ); diff --git a/dq8chr2glb/Logger/Log.cs b/dq8chr2glb/Logger/Log.cs index 43ae183..08bd8f1 100644 --- a/dq8chr2glb/Logger/Log.cs +++ b/dq8chr2glb/Logger/Log.cs @@ -13,13 +13,38 @@ public enum LogLevel public static class Log { + // public const LogLevel LogLevel = Logger.LogLevel.Info; + public static void Line(object data, LogLevel level = LogLevel.Debug) { + var lastColor = Console.ForegroundColor; + var color = level switch + { + LogLevel.Error => ConsoleColor.Red, + LogLevel.Debug => ConsoleColor.White, + LogLevel.Info => ConsoleColor.White, + LogLevel.Warning => ConsoleColor.Yellow, + LogLevel.None => ConsoleColor.White, + }; + + if (level == LogLevel.Debug) + { + return; + } + + Console.ForegroundColor = color; Console.WriteLine(data.ToString()); + Console.ForegroundColor = lastColor; } - + public static void Error(Exception e, LogLevel level = LogLevel.Error) { - Console.WriteLine($"{e.Message}\n From:\n{e.Source}\nStacktrace:\n{e.StackTrace}"); + var lastColor = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Red; + Console.Write($"[{level}] "); + Console.ForegroundColor = ConsoleColor.DarkRed; + // Console.Write($"{e.Message}\n From:\n{e.Source}\nStacktrace:\n{e.StackTrace}\n"); + Console.Write($"{e.Message}\n"); + Console.ForegroundColor = lastColor; } } diff --git a/dq8chr2glb/Program.cs b/dq8chr2glb/Program.cs index 11855e2..611ef71 100644 --- a/dq8chr2glb/Program.cs +++ b/dq8chr2glb/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using dq8chr2glb.Logger; namespace dq8chr2glb; @@ -64,7 +65,7 @@ public class Program var files = Directory.GetFiles(inputPath, "*.chr", SearchOption.TopDirectoryOnly); foreach (var file in files) { - Console.WriteLine($"Processing: {Path.GetFileName(file)}"); + Log.Line($"Processing: {Path.GetFileName(file)}", LogLevel.Info); try { chrFile.Process(file, outputPath); @@ -78,18 +79,18 @@ public class Program chrFile.Clean(); } - Console.WriteLine(files.Length == 0 ? "No .chr files found in the input directory." : "Done!"); + Log.Line(files.Length == 0 ? "No .chr files found in the input directory." : "Done!", LogLevel.Info); } else { if (!File.Exists(inputPath)) { - Console.WriteLine($"Error: Input file not found: {inputPath}"); + Log.Line($"Input file not found: {inputPath}", LogLevel.Error); return; } chrFile.Process(inputPath, outputPath); - Console.WriteLine("Done!"); + Log.Line("Done!", LogLevel.Info); } } diff --git a/dq8chr2glb/TM2Format/TM2Format.cs b/dq8chr2glb/TM2Format/TM2Format.cs index 8ce483c..13a53c1 100644 --- a/dq8chr2glb/TM2Format/TM2Format.cs +++ b/dq8chr2glb/TM2Format/TM2Format.cs @@ -1,4 +1,5 @@ using System; +using dq8chr2glb.Logger; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -12,6 +13,12 @@ public static class TM2Format var header = TM2Header.GetHeader(data); var image = GetImageBuffer(header, data); + if (image == null) + { + Log.Line($"Failed to create texture {name}", LogLevel.Error); + return null; + } + var img = new Image(header.Width, header.Height); for (var y = 0; y < header.Height; y++) @@ -45,15 +52,23 @@ public static class TM2Format { var palette = new byte[256][]; - for (var i = 0; i < 256; ++i) + try { - var rgba = new byte[4]; - Array.Copy(file, 0x40 + header.ImageSize + i * 4, rgba, 0, 4); - rgba[3] = (byte)Math.Min(rgba[3] << 1, 0xFF); + for (var i = 0; i < 256; ++i) + { + var rgba = new byte[4]; + Array.Copy(file, 0x40 + header.ImageSize + i * 4, rgba, 0, 4); + rgba[3] = (byte)Math.Min(rgba[3] << 1, 0xFF); - (rgba[2], rgba[0]) = (rgba[0], rgba[2]); + (rgba[2], rgba[0]) = (rgba[0], rgba[2]); - palette[(i & 0xe7) | ((i & 0x10) >> 1) | ((i & 0x08) << 1)] = rgba; + palette[(i & 0xe7) | ((i & 0x10) >> 1) | ((i & 0x08) << 1)] = rgba; + } + } + catch (Exception e) + { + Log.Error(e); + palette = null; } return palette; @@ -63,6 +78,11 @@ public static class TM2Format { var palette = GetPalette8bbp(tm2file, header); + if (palette == null) + { + return null; + } + var imageBuf = new byte[4 * header.Width * header.Height]; for (var y = header.Height - 1; y >= 0; --y) {