From 78a8545f7f6c9b983a16a8c45ffba8fdc883f566 Mon Sep 17 00:00:00 2001 From: MeltyPlayer Date: Sat, 13 Jan 2024 03:52:48 -0600 Subject: [PATCH] Started investigating adding support for EverOasis. --- .../CaseInvariantStringDictionary.cs | 32 +++++ .../dictionaries/NullFriendlyDictionary.cs | 3 +- .../src/data/dictionaries/SimpleDictionary.cs | 32 +++++ .../lazy/LazyCaseInvariantStringDictionary.cs | 41 ++++++ .../Fin/Fin/src/data/lazy/LazyDictionary.cs | 14 +- .../Fin/Fin/src/io/FileHierarchy.cs | 6 +- FinModelUtility/Fin/Fin/src/io/FileLinq.cs | 12 +- .../Fin/src/io/archive/ArchiveInterfaces.cs | 25 +++- .../Fin/Fin/src/io/archive/SubArchive.cs | 54 +++++--- .../importers/assimp/AssimpModelImporter.cs | 2 +- .../Cmb/Cmb/src/api/CmbModelImporter.cs | 127 ++++++++++-------- .../Glo/Glo/src/api/GloModelImporter.cs | 15 ++- .../Level5/Level5/src/api/XcModelImporter.cs | 4 +- .../Modl/Modl/src/api/ModlModelReader.cs | 2 +- .../HaloWarsTools/Resources/HWUgxResource.cs | 2 +- .../src/games/RootFileBundleGatherer.cs | 2 + .../ever_oasis/EverOasisFileBundleGatherer.cs | 67 +++++++++ .../threeDs/ThreeDsFileHierarchyExtractor.cs | 34 +++-- .../platforms/threeDs/tools/gar/GarReader.cs | 8 +- .../threeDs/tools/gar/schema/Gar5Subfile.cs | 11 +- .../memory/ZSegments.cs | 2 +- cli/config/ever_oasis.json | 3 + 22 files changed, 369 insertions(+), 129 deletions(-) create mode 100644 FinModelUtility/Fin/Fin/src/data/dictionaries/CaseInvariantStringDictionary.cs create mode 100644 FinModelUtility/Fin/Fin/src/data/dictionaries/SimpleDictionary.cs create mode 100644 FinModelUtility/Fin/Fin/src/data/lazy/LazyCaseInvariantStringDictionary.cs create mode 100644 FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/ever_oasis/EverOasisFileBundleGatherer.cs create mode 100644 cli/config/ever_oasis.json diff --git a/FinModelUtility/Fin/Fin/src/data/dictionaries/CaseInvariantStringDictionary.cs b/FinModelUtility/Fin/Fin/src/data/dictionaries/CaseInvariantStringDictionary.cs new file mode 100644 index 000000000..e3d9fbf10 --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/data/dictionaries/CaseInvariantStringDictionary.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace fin.data.dictionaries { + public class CaseInvariantStringDictionary : IFinDictionary { + private readonly Dictionary impl_ = new(StringComparer.InvariantCultureIgnoreCase); + + public void Clear() => this.impl_.Clear(); + + public int Count => this.impl_.Count; + public IEnumerable Keys => this.impl_.Keys; + public IEnumerable Values => this.impl_.Values; + public bool ContainsKey(string key) => this.impl_.ContainsKey(key); + + public bool TryGetValue(string key, out T value) => this.impl_.TryGetValue(key, out value); + + public T this[string key] { + get => this.impl_[key]; + set => this.impl_[key] = value; + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public IEnumerator<(string Key, T Value)> GetEnumerator() { + foreach (var (key, value) in this.impl_) { + yield return (key, value); + } + } + + } +} diff --git a/FinModelUtility/Fin/Fin/src/data/dictionaries/NullFriendlyDictionary.cs b/FinModelUtility/Fin/Fin/src/data/dictionaries/NullFriendlyDictionary.cs index eb8bc4007..370858372 100644 --- a/FinModelUtility/Fin/Fin/src/data/dictionaries/NullFriendlyDictionary.cs +++ b/FinModelUtility/Fin/Fin/src/data/dictionaries/NullFriendlyDictionary.cs @@ -10,8 +10,7 @@ namespace fin.data.dictionaries { /// /// A dictionary that accepts null keys. /// - public class NullFriendlyDictionary - : IFinDictionary { + public class NullFriendlyDictionary : IFinDictionary { private readonly ConcurrentDictionary impl_ = new(); private bool hasNull_; diff --git a/FinModelUtility/Fin/Fin/src/data/dictionaries/SimpleDictionary.cs b/FinModelUtility/Fin/Fin/src/data/dictionaries/SimpleDictionary.cs new file mode 100644 index 000000000..18037006f --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/data/dictionaries/SimpleDictionary.cs @@ -0,0 +1,32 @@ +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace fin.data.dictionaries { + public class SimpleDictionary(IDictionary? impl = null) + : IFinDictionary { + private readonly IDictionary impl_ = impl ?? new ConcurrentDictionary(); + + public void Clear() => this.impl_.Clear(); + + public int Count => this.impl_.Count; + public IEnumerable Keys => this.impl_.Keys; + public IEnumerable Values => this.impl_.Values; + public bool ContainsKey(TKey key) => this.impl_.ContainsKey(key); + + public bool TryGetValue(TKey key, out TValue value) => this.impl_.TryGetValue(key, out value); + + public TValue this[TKey key] { + get => this.impl_[key]; + set => this.impl_[key] = value; + } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public IEnumerator<(TKey Key, TValue Value)> GetEnumerator() { + foreach (var (key, value) in this.impl_) { + yield return (key, value); + } + } + + } +} diff --git a/FinModelUtility/Fin/Fin/src/data/lazy/LazyCaseInvariantStringDictionary.cs b/FinModelUtility/Fin/Fin/src/data/lazy/LazyCaseInvariantStringDictionary.cs new file mode 100644 index 000000000..0c4ff484b --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/data/lazy/LazyCaseInvariantStringDictionary.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +using fin.data.dictionaries; + +namespace fin.data.lazy { + public class LazyCaseInvariantStringDictionary : ILazyDictionary { + private readonly ILazyDictionary impl_; + + public LazyCaseInvariantStringDictionary(Func handler) { + this.impl_ = new LazyDictionary( + handler, + new SimpleDictionary(new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase))); + } + + public LazyCaseInvariantStringDictionary(Func, string, TValue> handler) { + this.impl_ = new LazyDictionary( + handler, + new SimpleDictionary(new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase))); + } + + public void Clear() => this.impl_.Clear(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + public IEnumerator<(string Key, TValue Value)> GetEnumerator() => this.impl_.GetEnumerator(); + + public int Count => this.impl_.Count; + public IEnumerable Keys => this.impl_.Keys; + public IEnumerable Values => this.impl_.Values; + + public bool ContainsKey(string key) => this.impl_.ContainsKey(key); + public bool TryGetValue(string key, out TValue value) => this.impl_.TryGetValue(key, out value); + + public TValue this[string key] { + get => this.impl_[key]; + set => this.impl_[key] = value; + } + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/data/lazy/LazyDictionary.cs b/FinModelUtility/Fin/Fin/src/data/lazy/LazyDictionary.cs index 086cdcf78..6a1c8232c 100644 --- a/FinModelUtility/Fin/Fin/src/data/lazy/LazyDictionary.cs +++ b/FinModelUtility/Fin/Fin/src/data/lazy/LazyDictionary.cs @@ -9,17 +9,19 @@ namespace fin.data.lazy { /// Dictionary implementation that lazily populates its entries when /// accessed. /// - public class LazyDictionary : ILazyDictionary { - private readonly NullFriendlyDictionary impl_ = new(); + public class LazyDictionary + : ILazyDictionary { + private readonly IFinDictionary impl_; private readonly Func handler_; - public LazyDictionary(Func handler) { + public LazyDictionary(Func handler, IFinDictionary? impl = null) { this.handler_ = handler; + this.impl_ = impl ?? new NullFriendlyDictionary(); } - public LazyDictionary( - Func, TKey, TValue> handler) { - this.handler_ = (TKey key) => handler(this, key); + public LazyDictionary(Func, TKey, TValue> handler, IFinDictionary? impl = null) { + this.handler_ = key => handler(this, key); + this.impl_ = impl ?? new NullFriendlyDictionary(); } public int Count => this.impl_.Count; diff --git a/FinModelUtility/Fin/Fin/src/io/FileHierarchy.cs b/FinModelUtility/Fin/Fin/src/io/FileHierarchy.cs index a7066cc26..6df280963 100644 --- a/FinModelUtility/Fin/Fin/src/io/FileHierarchy.cs +++ b/FinModelUtility/Fin/Fin/src/io/FileHierarchy.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; @@ -286,11 +287,10 @@ public bool TryToGetExistingFileWithFileType( public IEnumerable GetFilesWithNameRecursive( string name) { - name = name.ToLower(); var stack = new FinStack(this); while (stack.TryPop(out var next)) { var match = next.GetExistingFiles() - .FirstOrDefault(file => file.Name.ToLower() == name); + .FirstOrDefault(file => file.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (match != null) { yield return match; } diff --git a/FinModelUtility/Fin/Fin/src/io/FileLinq.cs b/FinModelUtility/Fin/Fin/src/io/FileLinq.cs index 7a6025d53..e34c3282d 100644 --- a/FinModelUtility/Fin/Fin/src/io/FileLinq.cs +++ b/FinModelUtility/Fin/Fin/src/io/FileLinq.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; namespace fin.io { @@ -13,18 +14,13 @@ public static IEnumerable WithName( public static IEnumerable WithFileType( this IEnumerable files, string fileType) where TFile : IReadOnlyTreeFile { - fileType = fileType.ToLower(); - return files.Where(file => file.FileType.ToLower() == fileType); + return files.Where(file => file.FileType.Equals(fileType, StringComparison.OrdinalIgnoreCase)); } public static IEnumerable WithFileTypes( this IEnumerable files, params string[] fileTypes) where TFile : IReadOnlyTreeFile { - for (var i = 0; i < fileTypes.Length; ++i) { - fileTypes[i] = fileTypes[i].ToLower(); - } - - return files.Where(file => fileTypes.Contains(file.FileType.ToLower())); + return files.Where(file => fileTypes.Any(fileType => fileType.Equals(file.FileType, StringComparison.OrdinalIgnoreCase))); } } } \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/io/archive/ArchiveInterfaces.cs b/FinModelUtility/Fin/Fin/src/io/archive/ArchiveInterfaces.cs index b761e87e0..ad8b35c76 100644 --- a/FinModelUtility/Fin/Fin/src/io/archive/ArchiveInterfaces.cs +++ b/FinModelUtility/Fin/Fin/src/io/archive/ArchiveInterfaces.cs @@ -35,16 +35,35 @@ public enum ArchiveExtractionResult { NEWLY_EXTRACTED, } + public interface IArchiveExtractor { + delegate void ArchiveFileProcessor(string archiveFileName, ref string relativeFileName, out bool relativeToRoot); + } + public interface IArchiveExtractor where TArchiveContentFile : IArchiveContentFile { ArchiveExtractionResult TryToExtractIntoNewDirectory( - IReadOnlyGenericFile archive, - ISystemDirectory newDirectory) + IReadOnlyTreeFile archive, + ISystemDirectory targetDirectory) + where TArchiveReader : IArchiveReader, new(); + + ArchiveExtractionResult TryToExtractIntoNewDirectory( + Stream archive, + ISystemDirectory targetDirectory) + where TArchiveReader : IArchiveReader, new(); + + ArchiveExtractionResult TryToExtractIntoNewDirectory( + IReadOnlyTreeFile archive, + ISystemDirectory rootDirectory, + ISystemDirectory targetDirectory, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) where TArchiveReader : IArchiveReader, new(); ArchiveExtractionResult TryToExtractIntoNewDirectory( + string archiveName, Stream archive, - ISystemDirectory newDirectory) + ISystemDirectory rootDirectory, + ISystemDirectory targetDirectory, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) where TArchiveReader : IArchiveReader, new(); } } \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/io/archive/SubArchive.cs b/FinModelUtility/Fin/Fin/src/io/archive/SubArchive.cs index b2d9081f9..ef8b268e5 100644 --- a/FinModelUtility/Fin/Fin/src/io/archive/SubArchive.cs +++ b/FinModelUtility/Fin/Fin/src/io/archive/SubArchive.cs @@ -49,41 +49,58 @@ public void CopyContentFileInto(SubArchiveContentFile archiveContentFile, } public class SubArchiveExtractor : IArchiveExtractor { + public ArchiveExtractionResult TryToExtractIntoNewDirectory(IReadOnlyTreeFile archive, + ISystemDirectory targetDirectory) where TArchiveReader : IArchiveReader, new() + => this.TryToExtractIntoNewDirectory(archive, null, targetDirectory); + + public ArchiveExtractionResult TryToExtractIntoNewDirectory(Stream archive, ISystemDirectory targetDirectory) where TArchiveReader : IArchiveReader, new() + => this.TryToExtractIntoNewDirectory(null, archive, null, targetDirectory); + public ArchiveExtractionResult TryToExtractIntoNewDirectory( - IReadOnlyGenericFile archive, - ISystemDirectory systemDirectory) + IReadOnlyTreeFile archive, + ISystemDirectory rootDirectory, + ISystemDirectory targetDirectory, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) where TArchiveReader : IArchiveReader, new() { - if (systemDirectory is { Exists: true, IsEmpty: false }) { + if (targetDirectory is { Exists: true, IsEmpty: false }) { return ArchiveExtractionResult.ALREADY_EXISTS; } - systemDirectory.Create(); - using var fs = archive.OpenRead(); - return this.TryToExtractIntoExistingDirectory_( + return this.TryToExtractIntoNewDirectory( + archive.NameWithoutExtension, fs, - systemDirectory); + rootDirectory, + targetDirectory, + archiveFileNameProcessor); } public ArchiveExtractionResult TryToExtractIntoNewDirectory( + string archiveName, Stream archive, - ISystemDirectory systemDirectory) + ISystemDirectory rootDirectory, + ISystemDirectory targetDirectory, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) where TArchiveReader : IArchiveReader, new() { - if (systemDirectory.Exists) { + if (targetDirectory is { Exists: true, IsEmpty: false }) { return ArchiveExtractionResult.ALREADY_EXISTS; } - systemDirectory.Create(); - return this.TryToExtractIntoExistingDirectory_( + archiveName, archive, - systemDirectory); + rootDirectory, + targetDirectory, + archiveFileNameProcessor); } private ArchiveExtractionResult TryToExtractIntoExistingDirectory_< TArchiveReader>( + string archiveName, Stream archive, - ISystemDirectory systemDirectory) + ISystemDirectory rootDirectory, + ISystemDirectory targetDirectory, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) where TArchiveReader : IArchiveReader, new() { var archiveReader = new TArchiveReader(); if (!archiveReader.IsValidArchive(archive)) { @@ -96,8 +113,15 @@ private ArchiveExtractionResult TryToExtractIntoExistingDirectory_< var createdDirectories = new HashSet(); foreach (var archiveContentFile in archiveContentFiles) { - var dstFile = new FinFile(Path.Join(systemDirectory.FullPath, - archiveContentFile.RelativeName)); + var relativeToRoot = false; + + var relativeName = archiveContentFile.RelativeName; + if (archiveFileNameProcessor != null) { + archiveFileNameProcessor(archiveName, ref relativeName, out relativeToRoot); + } + + var dstDir = relativeToRoot ? rootDirectory : targetDirectory; + var dstFile = new FinFile(Path.Join(dstDir.FullPath, relativeName)); var dstDirectory = dstFile.GetParentFullPath()!; if (createdDirectories.Add(dstDirectory)) { diff --git a/FinModelUtility/Fin/Fin/src/model/io/importers/assimp/AssimpModelImporter.cs b/FinModelUtility/Fin/Fin/src/model/io/importers/assimp/AssimpModelImporter.cs index 60fc578a2..93abf0d56 100644 --- a/FinModelUtility/Fin/Fin/src/model/io/importers/assimp/AssimpModelImporter.cs +++ b/FinModelUtility/Fin/Fin/src/model/io/importers/assimp/AssimpModelImporter.cs @@ -31,7 +31,7 @@ public unsafe IModel ImportModel(AssimpModelFileBundle modelFileBundle) { var assScene = ctx.ImportFile(mainFile.FullPath); // Adds materials - var lazyFinSatelliteImages = new LazyDictionary( + var lazyFinSatelliteImages = new LazyCaseInvariantStringDictionary( path => mainFile.AssertGetParent() .TryToGetExistingFile(path, out var imageFile) ? FinImage.FromFile(imageFile) diff --git a/FinModelUtility/Formats/Cmb/Cmb/src/api/CmbModelImporter.cs b/FinModelUtility/Formats/Cmb/Cmb/src/api/CmbModelImporter.cs index a4d628364..be75a31a0 100644 --- a/FinModelUtility/Formats/Cmb/Cmb/src/api/CmbModelImporter.cs +++ b/FinModelUtility/Formats/Cmb/Cmb/src/api/CmbModelImporter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using cmb.material; @@ -19,7 +20,6 @@ using fin.math; using fin.model; using fin.model.impl; -using fin.model.io; using fin.model.io.importers; using fin.util.asserts; @@ -60,18 +60,18 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { var filesAndCtxbs = ctxbFiles?.Select(ctxbFile => { - var ctxb = ctxbFile.ReadNew(); - return (ctxbFile, ctxb); - }) + var ctxb = ctxbFile.ReadNew(); + return (ctxbFile, ctxb); + }) .ToList() ?? new List<(IReadOnlyTreeFile shpaFile, Ctxb ctxb)>(); var filesAndShpas = shpaFiles?.Select(shpaFile => { - var shpa = - shpaFile.ReadNew(Endianness.LittleEndian); - return (shpaFile, shpa); - }) + var shpa = + shpaFile.ReadNew(Endianness.LittleEndian); + return (shpaFile, shpa); + }) .ToList() ?? new List<(IReadOnlyTreeFile shpaFile, Shpa shpa)>(); @@ -186,9 +186,9 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { var ctxb = filesAndCtxbs .Select(fileAndCtxb => fileAndCtxb.Item2) - .Single(ctxb => ctxb.Chunk.Entry.Name == cmbTexture.name); - image = ctrTexture.DecodeImage(cmbTexture) - .ReadImage(ctxb.Chunk.Entry.Data); + .FirstOrDefault(ctxb => ctxb.Chunk.Entry.Name == cmbTexture.name); + image = ctxb != null ? ctrTexture.DecodeImage(cmbTexture) + .ReadImage(ctxb.Chunk.Entry.Data) : FinImage.Create1x1FromColor(Color.Magenta); } return image; @@ -253,20 +253,25 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { foreach (var pset in shape.primitiveSets) { foreach (var i in pset.primitive.indices) { if (hasBi && pset.skinningMode != SkinningMode.Single) { - r.Position = cmb.startOffset + - cmb.header.vatrOffset + - cmb.vatr.bIndices.StartOffset + - shape.bIndices.Start + - i * - DataTypeUtil.GetSize(shape.bIndices.DataType) * - shape.boneDimensions; + float[] readBIndices; + if (shape.bIndices.Mode == VertexAttributeMode.Constant) { + readBIndices = shape.bIndices.Constants; + } else { + r.Position = cmb.startOffset + + cmb.header.vatrOffset + + cmb.vatr.bIndices.StartOffset + + shape.bIndices.Start + + i * + DataTypeUtil.GetSize(shape.bIndices.DataType) * + shape.boneDimensions; + readBIndices = + DataTypeUtil.Read(r, shape.boneDimensions, shape.bIndices.DataType) + .Select(value => value * shape.bIndices.Scale) + .ToArray(); + } for (var bi = 0; bi < shape.boneDimensions; ++bi) { - var boneTableIndex = shape.bIndices.Scale * - DataTypeUtil.Read( - r, - shape.bIndices.DataType); bIndices[i * boneCount + bi] = - pset.boneTable[(int) boneTableIndex]; + pset.boneTable[(int) readBIndices[bi]]; } } else { bIndices[i] = shape.primitiveSets[0].boneTable[0]; @@ -322,7 +327,7 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { if (hasClr) { float[] colorValues; - if (shape.normal.Mode == VertexAttributeMode.Constant) { + if (shape.color.Mode == VertexAttributeMode.Constant) { colorValues = shape.color.Constants; } else { r.Position = cmb.startOffset + @@ -343,30 +348,40 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { } if (hasUv0) { - r.Position = cmb.startOffset + - cmb.header.vatrOffset + - cmb.vatr.uv0.StartOffset + - shape.uv0.Start + - 2 * DataTypeUtil.GetSize(shape.uv0.DataType) * i; - var uv0Values = - DataTypeUtil.Read(r, 2, shape.uv0.DataType) - .Select(value => value * shape.uv0.Scale) - .ToArray(); + float[] uv0Values; + if (shape.uv0.Mode == VertexAttributeMode.Constant) { + uv0Values = shape.color.Constants; + } else { + r.Position = cmb.startOffset + + cmb.header.vatrOffset + + cmb.vatr.uv0.StartOffset + + shape.uv0.Start + + 2 * DataTypeUtil.GetSize(shape.uv0.DataType) * i; + uv0Values = + DataTypeUtil.Read(r, 2, shape.uv0.DataType) + .Select(value => value * shape.uv0.Scale) + .ToArray(); + } finVertex.SetUv(0, uv0Values[0], 1 - uv0Values[1]); } if (hasUv1) { - r.Position = cmb.startOffset + - cmb.header.vatrOffset + - cmb.vatr.uv1.StartOffset + - shape.uv1.Start + - 2 * DataTypeUtil.GetSize(shape.uv1.DataType) * i; - var uv1Values = - DataTypeUtil.Read(r, 2, shape.uv1.DataType) - .Select(value => value * shape.uv1.Scale) - .ToArray(); + float[] uv1Values; + if (shape.uv1.Mode == VertexAttributeMode.Constant) { + uv1Values = shape.color.Constants; + } else { + r.Position = cmb.startOffset + + cmb.header.vatrOffset + + cmb.vatr.uv1.StartOffset + + shape.uv1.Start + + 2 * DataTypeUtil.GetSize(shape.uv1.DataType) * i; + uv1Values = + DataTypeUtil.Read(r, 2, shape.uv1.DataType) + .Select(value => value * shape.uv1.Scale) + .ToArray(); + } finVertex.SetUv(1, uv1Values[0], 1 - uv1Values[1]); } @@ -389,14 +404,6 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { : VertexSpace.WORLD; if (hasBw) { - r.Position = cmb.startOffset + - cmb.header.vatrOffset + - cmb.vatr.bWeights.StartOffset + - shape.bWeights.Start + - i * - DataTypeUtil.GetSize(shape.bWeights.DataType) * - boneCount; - var totalWeight = 0f; var boneWeights = new List(); @@ -406,6 +413,14 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { .Select(value => value / 100) .ToArray(); } else { + r.Position = cmb.startOffset + + cmb.header.vatrOffset + + cmb.vatr.bWeights.StartOffset + + shape.bWeights.Start + + i * + DataTypeUtil.GetSize(shape.bWeights.DataType) * + boneCount; + // TODO: Looks like this is rounded to the nearest 2 in the original?? weightValues = DataTypeUtil.Read(r, boneCount, shape.bWeights.DataType) @@ -478,12 +493,12 @@ public IModel ImportModel(CmbModelFileBundle modelFileBundle) { public WrapMode CmbToFinWrapMode(TextureWrapMode cmbMode) => cmbMode switch { - // TODO: Darn, we can't support border colors - TextureWrapMode.ClampToBorder => WrapMode.CLAMP, - TextureWrapMode.Repeat => WrapMode.REPEAT, - TextureWrapMode.ClampToEdge => WrapMode.CLAMP, - TextureWrapMode.Mirror => WrapMode.MIRROR_REPEAT, - _ => throw new ArgumentOutOfRangeException() + // TODO: Darn, we can't support border colors + TextureWrapMode.ClampToBorder => WrapMode.CLAMP, + TextureWrapMode.Repeat => WrapMode.REPEAT, + TextureWrapMode.ClampToEdge => WrapMode.CLAMP, + TextureWrapMode.Mirror => WrapMode.MIRROR_REPEAT, + _ => throw new ArgumentOutOfRangeException() }; public readonly struct CsabReader : IAction { diff --git a/FinModelUtility/Formats/Glo/Glo/src/api/GloModelImporter.cs b/FinModelUtility/Formats/Glo/Glo/src/api/GloModelImporter.cs index 0144fdf79..a3ffa190d 100644 --- a/FinModelUtility/Formats/Glo/Glo/src/api/GloModelImporter.cs +++ b/FinModelUtility/Formats/Glo/Glo/src/api/GloModelImporter.cs @@ -1,6 +1,7 @@ using System.Numerics; using fin.color; +using fin.data.dictionaries; using fin.data.lazy; using fin.data.queues; using fin.image; @@ -28,11 +29,11 @@ public IModel ImportModel(GloModelFileBundle gloModelFileBundle) { var glo = gloFile.ReadNew(); - var textureFilesByName = new Dictionary(); + var textureFilesByName = new CaseInvariantStringDictionary(); foreach (var textureDirectory in textureDirectories) { foreach (var textureFile in textureDirectory.GetExistingFiles()) { if (FinImage.IsSupportedFileType(textureFile)) { - textureFilesByName[textureFile.NameWithoutExtension.ToLower()] = + textureFilesByName[textureFile.NameWithoutExtension] = textureFile; } } @@ -51,10 +52,10 @@ public IModel ImportModel(GloModelFileBundle gloModelFileBundle) { var finRootBone = finModel.Skeleton.Root; - var finTextureMap = new LazyDictionary( + var finTextureMap = new LazyCaseInvariantStringDictionary( textureFilename => { if (!textureFilesByName.TryGetValue( - Path.GetFileNameWithoutExtension(textureFilename).ToLower(), + Path.GetFileNameWithoutExtension(textureFilename), out var textureFile)) { return null; } @@ -78,7 +79,7 @@ public IModel ImportModel(GloModelFileBundle gloModelFileBundle) { return finTexture; }); var withCullingMap = - new LazyDictionary(textureFilename => { + new LazyCaseInvariantStringDictionary(textureFilename => { var finTexture = finTextureMap[textureFilename]; if (finTexture == null) { return finModel.MaterialManager.AddStandardMaterial(); @@ -86,7 +87,7 @@ public IModel ImportModel(GloModelFileBundle gloModelFileBundle) { return finModel.MaterialManager.AddTextureMaterial(finTexture); }); - var withoutCullingMap = new LazyDictionary( + var withoutCullingMap = new LazyCaseInvariantStringDictionary( textureFilename => { var finTexture = finTextureMap[textureFilename]; IMaterial finMaterial = finTexture == null @@ -99,7 +100,7 @@ public IModel ImportModel(GloModelFileBundle gloModelFileBundle) { return finMaterial; }); - var firstMeshMap = new Dictionary(); + var firstMeshMap = new CaseInvariantStringDictionary(); // TODO: Consider separating these out as separate models foreach (var gloObject in glo.Objects) { diff --git a/FinModelUtility/Formats/Level5/Level5/src/api/XcModelImporter.cs b/FinModelUtility/Formats/Level5/Level5/src/api/XcModelImporter.cs index 6345a16e6..e88e289bf 100644 --- a/FinModelUtility/Formats/Level5/Level5/src/api/XcModelImporter.cs +++ b/FinModelUtility/Formats/Level5/Level5/src/api/XcModelImporter.cs @@ -103,7 +103,7 @@ public IModel ImportModel(XcModelFileBundle modelFileBundle) { } } - var lazyTextures = new LazyDictionary(textureName => { + var lazyTextures = new LazyCaseInvariantStringDictionary(textureName => { var textureIndex = modelResourceFile.TextureNames.IndexOf(textureName); var xiFile = modelXc.FilesByExtension[".xi"][textureIndex]; @@ -117,7 +117,7 @@ public IModel ImportModel(XcModelFileBundle modelFileBundle) { return texture; }); - var lazyMaterials = new LazyDictionary( + var lazyMaterials = new LazyCaseInvariantStringDictionary( materialName => { var binMaterial = modelResourceFile.Materials.Single( diff --git a/FinModelUtility/Formats/Modl/Modl/src/api/ModlModelReader.cs b/FinModelUtility/Formats/Modl/Modl/src/api/ModlModelReader.cs index 4a4f9e809..bde2e5618 100644 --- a/FinModelUtility/Formats/Modl/Modl/src/api/ModlModelReader.cs +++ b/FinModelUtility/Formats/Modl/Modl/src/api/ModlModelReader.cs @@ -108,7 +108,7 @@ public async Task ImportModelAsync( var levelDir = modlFile.AssertGetParent(); var baseLevelDir = levelDir.AssertGetParent(); - var textureDictionary = new LazyDictionary>( + var textureDictionary = new LazyCaseInvariantStringDictionary>( async textureNameWithoutExtension => { var textureName = $"{textureNameWithoutExtension}.texr"; IReadOnlyTreeFile textureFile; diff --git a/FinModelUtility/Games/HaloWarsTools/Resources/HWUgxResource.cs b/FinModelUtility/Games/HaloWarsTools/Resources/HWUgxResource.cs index fe8736a9e..6af33192c 100644 --- a/FinModelUtility/Games/HaloWarsTools/Resources/HWUgxResource.cs +++ b/FinModelUtility/Games/HaloWarsTools/Resources/HWUgxResource.cs @@ -88,7 +88,7 @@ private IList GetMaterials(IMaterialManager materialManager, bdt.Serialize(es); } - var lazyTextureDictionary = new LazyDictionary(name + var lazyTextureDictionary = new LazyCaseInvariantStringDictionary(name => LoadTexture(materialManager, name)); var materials = new List(); diff --git a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/RootFileBundleGatherer.cs b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/RootFileBundleGatherer.cs index 247d4d484..1b7f9adb8 100644 --- a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/RootFileBundleGatherer.cs +++ b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/RootFileBundleGatherer.cs @@ -9,6 +9,7 @@ using uni.games.dead_space_2; using uni.games.dead_space_3; using uni.games.doshin_the_giant; +using uni.games.ever_oasis; using uni.games.glover; using uni.games.great_ace_attorney; using uni.games.halo_wars; @@ -47,6 +48,7 @@ public IFileBundleDirectory GatherAllFiles() { new DeadSpace2FileBundleGatherer(), new DeadSpace3FileBundleGatherer(), new DoshinTheGiantFileBundleGatherer(), + new EverOasisFileBundleGatherer(), new GloverFileBundleGatherer(), new GreatAceAttorneyFileBundleGatherer(), new HaloWarsFileBundleGatherer(), diff --git a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/ever_oasis/EverOasisFileBundleGatherer.cs b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/ever_oasis/EverOasisFileBundleGatherer.cs new file mode 100644 index 000000000..09c4025f3 --- /dev/null +++ b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/games/ever_oasis/EverOasisFileBundleGatherer.cs @@ -0,0 +1,67 @@ +using cmb.api; + +using fin.data.queues; +using fin.io; +using fin.io.bundles; + +using uni.platforms.threeDs; + +namespace uni.games.ever_oasis { + using IAnnotatedCmbBundle = IAnnotatedFileBundle; + + public class EverOasisFileBundleGatherer + : IAnnotatedFileBundleGatherer { + public IEnumerable GatherFileBundles() { + if (!new ThreeDsFileHierarchyExtractor().TryToExtractFromGame( + "ever_oasis", + out var fileHierarchy, + archiveFileNameProcessor: this.ArchiveFileNameProcessor_)) { + return Enumerable.Empty(); + } + + return new AnnotatedFileBundleGathererAccumulatorWithInput< + CmbModelFileBundle, + IFileHierarchy>(fileHierarchy) + .Add(this.GetAutomaticModels_) + .GatherFileBundles(); + } + + private void ArchiveFileNameProcessor_(string archiveName, ref string relativeName, out bool relativeToRoot) { + if (relativeName.StartsWith("C:")) { + relativeName = relativeName[2..]; + relativeToRoot = true; + return; + } + + relativeToRoot = false; + } + + private IEnumerable GetAutomaticModels_( + IFileHierarchy fileHierarchy) { + var queue = new FinQueue(fileHierarchy.Root); + while (queue.TryDequeue(out var dir)) { + if (dir.TryToGetExistingSubdir("model", out var modelDir)) { + dir.TryToGetExistingSubdir("anim", out var animDir); + dir.TryToGetExistingSubdir("texture_set", out var textureSetDir); + + var cmbFiles = modelDir.GetFilesWithFileType(".cmb").ToArray(); + var csabFiles = animDir?.GetFilesWithFileType(".csab").ToArray(); + var ctxbFiles = textureSetDir?.GetFilesWithFileType(".ctxb").ToArray(); + + if (cmbFiles.Length == 1 || (csabFiles?.Length ?? 0) == 0) { + foreach (var cmbFile in cmbFiles) { + yield return new CmbModelFileBundle( + "ever_oasis", + cmbFile, + csabFiles, + ctxbFiles, + null).Annotate(cmbFile); + } + } + } else { + queue.Enqueue(dir.GetExistingSubdirs()); + } + } + } + } +} \ No newline at end of file diff --git a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/ThreeDsFileHierarchyExtractor.cs b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/ThreeDsFileHierarchyExtractor.cs index a52f408a6..6356fa73f 100644 --- a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/ThreeDsFileHierarchyExtractor.cs +++ b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/ThreeDsFileHierarchyExtractor.cs @@ -9,13 +9,14 @@ namespace uni.platforms.threeDs { public class ThreeDsFileHierarchyExtractor { public bool TryToExtractFromGame(string gameName, - out IFileHierarchy fileHierarchy) { + out IFileHierarchy fileHierarchy, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) { if (!TryToFindRom_(gameName, out var romFile)) { fileHierarchy = default; return false; } - fileHierarchy = this.ExtractFromRom_(romFile); + fileHierarchy = this.ExtractFromRom_(romFile, archiveFileNameProcessor); return true; } @@ -28,21 +29,24 @@ private static bool TryToFindRom_(string gameName, out IReadOnlySystemFile romFi ".3ds", ".cia"); - private IFileHierarchy ExtractFromRom_(IReadOnlySystemFile romFile) { + private IFileHierarchy ExtractFromRom_(IReadOnlySystemFile romFile, + IArchiveExtractor.ArchiveFileProcessor? archiveFileNameProcessor = null) { IFileHierarchy fileHierarchy; switch (romFile.FileType) { case ".cia": { - new Ctrtool.CiaExtractor().Run(romFile, out fileHierarchy); - break; - } + new Ctrtool.CiaExtractor().Run(romFile, out fileHierarchy); + break; + } case ".3ds": case ".cci": { - new Ctrtool.CciExtractor().Run(romFile, out fileHierarchy); - break; - } + new Ctrtool.CciExtractor().Run(romFile, out fileHierarchy); + break; + } default: throw new NotSupportedException(); } + var rootDir = fileHierarchy.Root.Impl; + var archiveExtractor = new SubArchiveExtractor(); var didDecompress = false; @@ -52,7 +56,9 @@ private IFileHierarchy ExtractFromRom_(IReadOnlySystemFile romFile) { didChange |= archiveExtractor.TryToExtractIntoNewDirectory( zarFile, - new FinDirectory(zarFile.FullNameWithoutExtension)) == + rootDir, + new FinDirectory(zarFile.FullNameWithoutExtension), + archiveFileNameProcessor) == ArchiveExtractionResult.NEWLY_EXTRACTED; } @@ -60,7 +66,9 @@ private IFileHierarchy ExtractFromRom_(IReadOnlySystemFile romFile) { didChange |= archiveExtractor.TryToExtractIntoNewDirectory( garFile, - new FinDirectory(garFile.FullNameWithoutExtension)) == + rootDir, + new FinDirectory(garFile.FullNameWithoutExtension), + archiveFileNameProcessor) == ArchiveExtractionResult.NEWLY_EXTRACTED; } @@ -69,8 +77,10 @@ private IFileHierarchy ExtractFromRom_(IReadOnlySystemFile romFile) { didChange |= archiveExtractor.TryToExtractIntoNewDirectory( garFile, + rootDir, new FinDirectory( - garFile.FullPath.SubstringUpTo(".gar"))) == + garFile.FullPath.SubstringUpTo(".gar")), + archiveFileNameProcessor) == ArchiveExtractionResult.NEWLY_EXTRACTED; } diff --git a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/GarReader.cs b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/GarReader.cs index dea097c02..720397fb4 100644 --- a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/GarReader.cs +++ b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/GarReader.cs @@ -36,14 +36,14 @@ public IEnumerable GetFiles( foreach (var file in fileType.Files) { var fileName = file.FullPath ?? file.FileName; - if (!fileName.EndsWith($".{fileType.TypeName}")) { + if (!fileName.EndsWith($".{fileType.TypeName}", StringComparison.OrdinalIgnoreCase)) { fileName = $"{fileName}.{fileType.TypeName}"; } yield return new SubArchiveContentFile { - RelativeName = fileName, - Position = file.Position, - Length = file.Length, + RelativeName = fileName, + Position = file.Position, + Length = file.Length, }; } } diff --git a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/schema/Gar5Subfile.cs b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/schema/Gar5Subfile.cs index 83ceaf0b5..cc9369abe 100644 --- a/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/schema/Gar5Subfile.cs +++ b/FinModelUtility/UniversalAssetTool/UniversalAssetTool/src/platforms/threeDs/tools/gar/schema/Gar5Subfile.cs @@ -1,6 +1,4 @@ -using System.IO; - -using schema.binary; +using schema.binary; namespace uni.platforms.threeDs.tools.gar.schema { public class Gar5Subfile : IGarSubfile { @@ -33,10 +31,9 @@ public Gar5Subfile( if (Path.GetExtension(this.FileName) == string.Empty) { this.FileName += $".{fileType.TypeName}"; - - if (this.FullPath != null) { - this.FullPath += $".{fileType.TypeName}"; - } + } + if (this.FullPath != null && Path.GetExtension(this.FullPath) == string.Empty) { + this.FullPath += $".{fileType.TypeName}"; } this.Position = (int) fileOffset; diff --git a/FinModelUtility/Utility of Time CSharp/memory/ZSegments.cs b/FinModelUtility/Utility of Time CSharp/memory/ZSegments.cs index 2d41f48cc..1e3df45f2 100644 --- a/FinModelUtility/Utility of Time CSharp/memory/ZSegments.cs +++ b/FinModelUtility/Utility of Time CSharp/memory/ZSegments.cs @@ -60,7 +60,7 @@ public static ZSegments InitializeFromFile(IGenericFile romFile) { nameOffset = -1; break; } - case "03-02-21 00:16:31": { + case "03-02-21 00:16:31" or "03-02-21 00:49:18": { nameOffset = 0xBE80; break; } diff --git a/cli/config/ever_oasis.json b/cli/config/ever_oasis.json new file mode 100644 index 000000000..a22b47ef2 --- /dev/null +++ b/cli/config/ever_oasis.json @@ -0,0 +1,3 @@ +{ + "Scale": .02 +} \ No newline at end of file