diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f30d000 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*.cs] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf diff --git a/.gitignore b/.gitignore index ae4e793..bcf8194 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ LanternExtractor/bin/ *.DS_Store packages/ .vs/ +.vscode/ diff --git a/LanternExtractor/App.config b/LanternExtractor/App.config deleted file mode 100644 index 88fa402..0000000 --- a/LanternExtractor/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/LanternExtractor/ClientData/musictracks.txt b/LanternExtractor/ClientData/musictracks.txt deleted file mode 100644 index 196c1c3..0000000 --- a/LanternExtractor/ClientData/musictracks.txt +++ /dev/null @@ -1,595 +0,0 @@ -# Lantern Music Tracks - -#airplane -akanon-2 -airplane_0 -entrance_fanfare-1 -arpeggiated_runs -airplane_1 -heroism-1 - -#akanon -akanon -gfaydark-1 -gfaydark-2 -felwithe_2 -felwithe_0 -felwithe_1 -arena -gypsies - -#befallen -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 - -#blackburrow -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 - -#butcher -entrance_fanfare -entrance_fanfare_1 - -#cauldron -entrance_fanfare -entrance_fanfare_1 -entrance_fanfare_1-1 -entrance_fanfare-1 -sea - -#cobaltscar -cobaltscar -underwater - -#crushbone -gfaydark-4 -airplane_0 -entrance_fanfare-1 -eerie_3 -arpeggiated_runs -neriak_0-1 - -#crystal -erudsxing_0 -gfaydark-1 -cobaltscar -gfaydark - -#damage -damage_0 -damage_1 -damage_2 -damage_3 -damage_4 - -#damage1 -damage_0 -damage_1 -damage_2 -damage_3 -damage_4 - -#damage2 -damage_0 -damage_1 -damage_2 -damage_3 -damage_4 - -#eastkarana -cascade -eastkarana - -#eerie -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 - -#erudnext -felwithe_0 -cascade -karana_river -entrance_fanfare_1-1 -bard -freportn -karana_river-1 -brass_harmonies - -#eudnint -cascade -karana_river -entrance_fanfare_1-1 -bard -freportn -karana_river-1 -brass_harmonies -felwithe_1 - -#erudsxing -erudsxing_0 -erudsxing_1 - -#fearplane -eerie_8 -eerie_9 -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_7 - -#felwithea -felwithe_0 -felwithe_1 -felwithe_2 - -#freporte -freeport -lionsmane -fishsale -cottage -gypsies -death_variant -eerie_4 -eerie_5 - -#freportn -harprun -gypsies-1 -gypsies -eqtheme -arena -brass_harmonies -akanon-1 -lionsmane -fishsale -bard -entrance_fanfare_1-1 -cottage -freportn -nightchords -string_fanfare -brass_fanfare - -#freportw -heroism -hogcaller -militia -arena -entrance_fanfare_1-1 -eastkarana -karana_river-1 - -#frozenshadow -eerie_7 -eerie_0 -lavastorm-1 -templeoflife -qeynos_0-1 -gfaydark - -#gfaydark -entrance_fanfare -entrance_fanfare_1 -entrance_fanfare_1-1 -entrance_fanfare-1 -gfaydark -eerie_9 - -#gl -attack_0 -attack_1 -attack_2 -death -airplane_0-1 -gl_5 -gl_6 -crouching -underwater -gl_9 -gl_10 -gl_11 -gl_12 -bard_intro -bard_main -gl_15 -gl_16 -gl_17 -gl_18 -gl_19 -sea -merchant -gfaydark-5 -guildmaster - -#grobb -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 - -#guktop -eerie_0 -eerie_1 -eerie_5 -eerie_7 -arpeggiated_runs -neriak_0-1 -eerie_3 -maidensfancy -hogcaller -entrance_fanfare_1 - -#halas -felwithe_0 -felwithe_1 -felwithe_2 -arena -hogcaller -maidensfancy -lionsmane -fishsale -cottage -eerie_4 - -#hateplane -felwithe_0 -felwithe_2 -felwithe_1 -arpeggiated_runs -bard-2 -neriak_0-1 - -#innothule -entrance_fanfare -entrance_fanfare_1 -eerie_3 -eerie_2 - -#kael -kael_0 -kael_1 -attack_1 -arena - -#kaladima -entrance_fanfare_1 -arena -nightchords -cascade -hogcaller -lionsmane -freportn -heroism -brass_harmonies-1 - -#lavastorm -lavastorm -eerie_1 - -#lfaydark -cascade -gfaydark-2 -entrance_fanfare_1-1 -gypsies-1 -eerie_5 -militia - -#mistmoore -cascade -eastkarana -eerie_1 -entrance_fanfare -felwithe_1 -felwithe_2 -felwithe_0 -neriak_0-1 -karana_river -bard-2 -brass_harmonies -string_fanfare -freportn-1 -nightchords -gfaydark-3 -heroism-1 - -#najena -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 - -#nektulos -lavastorm-1 - -#neriaka -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 -neriak_0 -neriak_1 -neriak_2 -arpeggiated_runs -fishsale -maidensfancy - -#neriakb -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 -neriak_0 -neriak_1 -neriak_2 -arpeggiated_runs -fishsale -maidensfancy - -#neriakc -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_4 -eerie_5 -eerie_6 -eerie_7 -eerie_8 -eerie_9 -eerie_10 -eerie_11 -neriak_0 -neriak_1 -neriak_2 -arpeggiated_runs -fishsale -maidensfancy - -#northkarana -cascade -eastkarana - -#nro -nro -entrance_fanfare_1 -freeport -gypsies -eerie_7 - -#opener -opener_0 - -#opener2 -eastkarana -character_select - -#opener3 -opener_3 -character_select - -#opener4 -eqtheme -character_select - -#paw -eerie_0 -eerie_1 -crouching - -#pickchar -character_select-1 - -#qcat -eerie_0 -eerie_1 -crouching - -#qey2hh1 -cottage -maidensfancy -karana_river - -#qeynos -arena -qeynos_gates -eerie_0 -eerie_1 -templeoflife -bard -eqtheme -gypsies -lionsmane -fishsale -cottage -freeport -qeynos_0 - -#qeynos2 -arena -qeynos_gates -eerie_0 -eerie_1 -templeoflife -bard-1 - -#qeytoqrg -arena -qeytoqrg -eerie_0 -eerie_1 -templeoflife -cottage -maidensfancy -entrance_fanfare - -#qrg -arena -qeytoqrg -templeoflife -qeynos_gates_brass - -#rathemtn -cascade -eastkarana -gypsies-2 -entrance_fanfare_1 -entrance_fanfare_1-1 -eerie_2 -eerie_3 - -#rivervale -rivervale - -#runnyeye -eerie_0 -eerie_1 -crouching - -#skyshrine -gl_12 -bard_intro -skyshrine -beethoven6 -nightchords -sea -gfaydark -gfaydark-2 - -#soldungb -lavastorm -eerie_10 -eerie_11 -eerie_0 -eerie_1 -eerie_2 -eerie_3 -eerie_6-1 - -#southkarana -cascade -eastkarana -eerie_2 -entrance_fanfare -maidensfancy -akanon-1 - -#steamfont -gfaydark-4 -entrance_fanfare -entrance_fanfare_1-1 -akanon-1 -lavastorm -eerie_3 -eerie_5 - -#templeveeshan -templeveeshan -gfaydark-5 -attack_1 -attack_0 - -#thurgadina -templeveeshan -attack_1 -attack_0 -thurgadina_0 -templeoflife -thurgadina_1 - -#thurgadinb -thurgadina_1 -thurgadinb - -#tox -entrance_fanfare_1 -karana_river -gypsies-1 -cascade -karana_river-1 - -#unrest -eastkarana -eerie_2 -eerie_1 -entrance_fanfare -felwithe_1 -felwithe_2 -neriak_0-1 -karana_river -brass_harmonies -string_fanfare -freportn-1 -gfaydark-3 -heroism-1 - -#velketor -velketor_0 -velketor_1 - -#wakening -wakening -gfaydark \ No newline at end of file diff --git a/LanternExtractor/EQ/Archive/ArchiveBase.cs b/LanternExtractor/EQ/Archive/ArchiveBase.cs new file mode 100644 index 0000000..145ea74 --- /dev/null +++ b/LanternExtractor/EQ/Archive/ArchiveBase.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.IO; +using LanternExtractor.Infrastructure; +using LanternExtractor.Infrastructure.Logger; + +namespace LanternExtractor.EQ.Archive +{ + public abstract class ArchiveBase + { + public string FilePath { get; } + public string FileName { get; } + protected List Files = new List(); + protected Dictionary FileNameReference = new Dictionary(); + protected ILogger Logger; + public bool IsWldArchive { get; set; } + public Dictionary FilenameChanges = new Dictionary(); + + protected ArchiveBase(string filePath, ILogger logger) + { + FilePath = filePath; + FileName = Path.GetFileName(filePath); + Logger = logger; + } + + public abstract bool Initialize(); + + public ArchiveFile GetFile(string fileName) + { + return !FileNameReference.ContainsKey(fileName) ? null : FileNameReference[fileName]; + } + + public ArchiveFile GetFile(int index) + { + if (index < 0 || index >= Files.Count) + { + return null; + } + + return Files[index]; + } + + public ArchiveFile[] GetAllFiles() + { + return Files.ToArray(); + } + + public void WriteAllFiles(string folder) + { + foreach (var file in Files) + { + FileWriter.WriteBytesToDisk(file.Bytes, folder, file.Name); + } + } + + public void RenameFile(string originalName, string newName) + { + if (!FileNameReference.ContainsKey(originalName)) + { + return; + } + + var file = FileNameReference[originalName]; + FileNameReference.Remove(originalName); + file.Name = newName; + FileNameReference[newName] = file; + } + } +} diff --git a/LanternExtractor/EQ/Archive/ArchiveFactory.cs b/LanternExtractor/EQ/Archive/ArchiveFactory.cs new file mode 100644 index 0000000..9bf0b9f --- /dev/null +++ b/LanternExtractor/EQ/Archive/ArchiveFactory.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using LanternExtractor.Infrastructure.Logger; + +namespace LanternExtractor.EQ.Archive +{ + public class ArchiveFactory + { + private const uint T3dMagic = 0xffff3d02; + private const uint PfsMagic = 0x20534650; + + public static ArchiveBase GetArchive(string filePath, ILogger logger) + { + if (!File.Exists(filePath)) + { + // Skip detection and let the archive Initialize fail. + return new NullArchive(filePath, logger); + } + + var archiveType = GetArchiveTypeFromMagic(filePath); + if (archiveType == ArchiveType.Unknown) + { + archiveType = GetArchiveTypeFromFilename(filePath); + } + + switch (archiveType) + { + case ArchiveType.Pfs: + return new PfsArchive(filePath, logger); + case ArchiveType.T3d: + return new T3dArchive(filePath, logger); + default: + throw new ArgumentException("Unknown archive type", "filePath"); + } + } + + private static ArchiveType GetArchiveTypeFromMagic(string filePath) + { + uint data; + using (BinaryReader br = new BinaryReader(File.OpenRead(filePath))) + { + data = br.ReadUInt32(); + if (data == T3dMagic) + { + return ArchiveType.T3d; + } + + data = br.ReadUInt32(); + if (data == PfsMagic) + { + return ArchiveType.Pfs; + } + } + + return ArchiveType.Unknown; + } + + private static ArchiveType GetArchiveTypeFromFilename(string filePath) + { + string archiveExt = Path.GetExtension(filePath)?.ToLower(); + + switch (archiveExt) + { + case LanternStrings.T3dFormatExtension: + return ArchiveType.T3d; + case LanternStrings.S3dFormatExtension: + case LanternStrings.PfsFormatExtension: + case LanternStrings.PakFormatExtension: + return ArchiveType.Pfs; + } + + return ArchiveType.Unknown; + } + } +} diff --git a/LanternExtractor/EQ/Pfs/PfsFile.cs b/LanternExtractor/EQ/Archive/ArchiveFile.cs similarity index 72% rename from LanternExtractor/EQ/Pfs/PfsFile.cs rename to LanternExtractor/EQ/Archive/ArchiveFile.cs index 3cbe3fd..528ded7 100644 --- a/LanternExtractor/EQ/Pfs/PfsFile.cs +++ b/LanternExtractor/EQ/Archive/ArchiveFile.cs @@ -1,15 +1,10 @@ -namespace LanternExtractor.EQ.Pfs +namespace LanternExtractor.EQ.Archive { /// /// This class represents a single file in the archive /// - public class PfsFile + public abstract class ArchiveFile { - /// - /// The CRC of the PFSFile - /// - public uint Crc { get; } - /// /// The inflated size of the file in bytes /// @@ -29,13 +24,12 @@ public class PfsFile /// The name of the file /// public string Name { get; set; } - - public PfsFile(uint crc, uint size, uint offset, byte[] bytes) + + public ArchiveFile(uint size, uint offset, byte[] bytes) { - Crc = crc; Size = size; Offset = offset; Bytes = bytes; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Archive/ArchiveType.cs b/LanternExtractor/EQ/Archive/ArchiveType.cs new file mode 100644 index 0000000..b6bfe40 --- /dev/null +++ b/LanternExtractor/EQ/Archive/ArchiveType.cs @@ -0,0 +1,9 @@ +namespace LanternExtractor.EQ.Archive +{ + public enum ArchiveType + { + Unknown = 0, + Pfs = 1, + T3d = 2 + } +} diff --git a/LanternExtractor/EQ/Archive/NullArchive.cs b/LanternExtractor/EQ/Archive/NullArchive.cs new file mode 100644 index 0000000..d7a9e34 --- /dev/null +++ b/LanternExtractor/EQ/Archive/NullArchive.cs @@ -0,0 +1,16 @@ +using LanternExtractor.Infrastructure.Logger; + +namespace LanternExtractor.EQ.Archive +{ + public class NullArchive : ArchiveBase + { + public NullArchive(string filePath, ILogger logger) : base(filePath, logger) + { + } + + public override bool Initialize() + { + return false; + } + } +} diff --git a/LanternExtractor/EQ/Pfs/PfsArchive.cs b/LanternExtractor/EQ/Archive/PfsArchive.cs similarity index 64% rename from LanternExtractor/EQ/Pfs/PfsArchive.cs rename to LanternExtractor/EQ/Archive/PfsArchive.cs index fda1b24..3881792 100644 --- a/LanternExtractor/EQ/Pfs/PfsArchive.cs +++ b/LanternExtractor/EQ/Archive/PfsArchive.cs @@ -2,41 +2,26 @@ using System.Collections.Generic; using System.IO; using Ionic.Zlib; -using LanternExtractor.Infrastructure; using LanternExtractor.Infrastructure.Logger; -namespace LanternExtractor.EQ.Pfs +namespace LanternExtractor.EQ.Archive { /// /// Loads and can extract files in the PFS archive /// - public class PfsArchive + public class PfsArchive : ArchiveBase { - public string FilePath { get; } - public string FileName { get; } - - private List _files = new List(); - private Dictionary _fileNameReference = new Dictionary(); - private ILogger _logger; - - public bool IsWldArchive { get; set; } - - public Dictionary FilenameChanges = new Dictionary(); - - public PfsArchive(string filePath, ILogger logger) + public PfsArchive(string filePath, ILogger logger) : base(filePath, logger) { - FilePath = filePath; - FileName = Path.GetFileName(filePath); - _logger = logger; } - public bool Initialize() + public override bool Initialize() { - _logger.LogInfo("PfsArchive: Started initialization of archive: " + FileName); + Logger.LogInfo("PfsArchive: Started initialization of archive: " + FileName); if (!File.Exists(FilePath)) { - _logger.LogError("PfsArchive: File does not exist at: " + FilePath); + Logger.LogError("PfsArchive: File does not exist at: " + FilePath); return false; } @@ -44,6 +29,8 @@ public bool Initialize() { var reader = new BinaryReader(fileStream); int directoryOffset = reader.ReadInt32(); + var pfsMagic = reader.ReadUInt32(); + var pfsVersion = reader.ReadInt32(); reader.BaseStream.Position = directoryOffset; int fileCount = reader.ReadInt32(); @@ -57,7 +44,7 @@ public bool Initialize() if (offset > reader.BaseStream.Length) { - _logger.LogError("PfsArchive: Corrupted PFS length detected!"); + Logger.LogError("PfsArchive: Corrupted PFS length detected!"); return false; } @@ -75,16 +62,16 @@ public bool Initialize() if (deflatedLength >= reader.BaseStream.Length) { - _logger.LogError("PfsArchive: Corrupted file length detected!"); + Logger.LogError("PfsArchive: Corrupted file length detected!"); return false; } byte[] compressedBytes = reader.ReadBytes((int)deflatedLength); byte[] inflatedBytes; - if (!InflateBlock(compressedBytes, (int)inflatedLength, out inflatedBytes, _logger)) + if (!InflateBlock(compressedBytes, (int)inflatedLength, out inflatedBytes, Logger)) { - _logger.LogError("PfsArchive: Error occured inflating data"); + Logger.LogError("PfsArchive: Error occured inflating data"); return false; } @@ -92,7 +79,9 @@ public bool Initialize() inflatedSize += inflatedLength; } - if (crc == 0x61580AC9) + // EQZip saved archives use 0xFFFFFFFFU for filenames + // https://github.com/Shendare/EQZip/blob/b181ec7658ea9880984d58271cbab924ab8dd702/EQArchive.cs#L517 + if (crc == 0x61580AC9 || (crc == 0xFFFFFFFFU && fileNames.Count == 0)) { var dictionaryStream = new MemoryStream(fileBytes); var dictionary = new BinaryReader(dictionaryStream); @@ -110,27 +99,42 @@ public bool Initialize() continue; } - _files.Add(new PfsFile(crc, size, offset, fileBytes)); + Files.Add(new PfsFile(crc, size, offset, fileBytes)); reader.BaseStream.Position = cachedOffset; } // Sort files by offset so we can assign names - _files.Sort((x, y) => x.Offset.CompareTo(y.Offset)); + Files.Sort((x, y) => x.Offset.CompareTo(y.Offset)); // Assign file names - for (int i = 0; i < _files.Count; ++i) + for (int i = 0; i < Files.Count; ++i) { - _files[i].Name = fileNames[i]; - _fileNameReference[fileNames[i]] = _files[i]; - - if (!IsWldArchive && fileNames[i].EndsWith(".wld")) + switch(pfsVersion) { - IsWldArchive = true; + case 0x10000: + // PFS version 1 files do not appear to contain the filenames + if (Files[i] is PfsFile pfsFile) + { + pfsFile.Name = $"{pfsFile.Crc:X8}.bin"; + } + break; + case 0x20000: + Files[i].Name = fileNames[i]; + FileNameReference[fileNames[i]] = Files[i]; + + if (!IsWldArchive && fileNames[i].EndsWith(LanternStrings.WldFormatExtension)) + { + IsWldArchive = true; + } + break; + default: + Logger.LogError("PfsArchive: Unexpected pfs version: " + FileName); + break; } } - _logger.LogInfo("PfsArchive: Finished initialization of archive: " + FileName); + Logger.LogInfo("PfsArchive: Finished initialization of archive: " + FileName); } return true; @@ -186,45 +190,5 @@ private static bool InflateBlock(byte[] deflatedBytes, int inflatedSize, out byt } } - public PfsFile GetFile(string fileName) - { - return !_fileNameReference.ContainsKey(fileName) ? null : _fileNameReference[fileName]; - } - - public PfsFile GetFile(int index) - { - if (index < 0 || index >= _files.Count) - { - return null; - } - - return _files[index]; - } - - public PfsFile[] GetAllFiles() - { - return _files.ToArray(); - } - - public void WriteAllFiles(string folder) - { - foreach (var file in _files) - { - FileWriter.WriteBytesToDisk(file.Bytes, folder, file.Name); - } - } - - public void RenameFile(string originalName, string newName) - { - if (!_fileNameReference.ContainsKey(originalName)) - { - return; - } - - var file = _fileNameReference[originalName]; - _fileNameReference.Remove(originalName); - file.Name = newName; - _fileNameReference[newName] = file; - } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Archive/PfsFile.cs b/LanternExtractor/EQ/Archive/PfsFile.cs new file mode 100644 index 0000000..714a626 --- /dev/null +++ b/LanternExtractor/EQ/Archive/PfsFile.cs @@ -0,0 +1,18 @@ +namespace LanternExtractor.EQ.Archive +{ + /// + /// This class represents a single file in the archive + /// + public class PfsFile : ArchiveFile + { + /// + /// The CRC of the PFSFile + /// + public uint Crc { get; } + + public PfsFile(uint crc, uint size, uint offset, byte[] bytes) : base(size, offset, bytes) + { + Crc = crc; + } + } +} diff --git a/LanternExtractor/EQ/Archive/T3dArchive.cs b/LanternExtractor/EQ/Archive/T3dArchive.cs new file mode 100644 index 0000000..e2143cf --- /dev/null +++ b/LanternExtractor/EQ/Archive/T3dArchive.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LanternExtractor.Infrastructure; +using LanternExtractor.Infrastructure.Logger; + +namespace LanternExtractor.EQ.Archive +{ + /// + /// Loads and can extract files in the T3D archive + /// + public class T3dArchive : ArchiveBase + { + private static readonly byte[] T3dMagic = new byte[] {0x02, 0x3D, 0xFF, 0xFF}; + private static readonly byte[] T3dVersion = new byte[] {0x00, 0x57, 0x01, 0x00}; + + public T3dArchive(string filePath, ILogger logger) : base(filePath, logger) + { + } + + public override bool Initialize() + { + Logger.LogInfo("T3dArchive: Started initialization of archive: " + FileName); + + if (!File.Exists(FilePath)) + { + Logger.LogError("T3dArchive: File does not exist at: " + FilePath); + return false; + } + + using (var fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read)) + { + var reader = new BinaryReader(fileStream); + + var magic = reader.ReadBytes(4); + if (!magic.SequenceEqual(T3dMagic)) + { + Logger.LogError("T3dArchive: Incorrect file magic"); + return false; + } + + var version = reader.ReadBytes(4); + if (!version.SequenceEqual(T3dVersion)) + { + Logger.LogError("T3dArchive: Incorrect file version"); + return false; + } + + var fileCount = reader.ReadUInt32(); + var filenamesLength = reader.ReadUInt32(); + + var offsetPairs = new List<(uint FileOffset, uint FileNameOffset)>(); + for (int i = 0; i < fileCount; i++) + { + var fileNameBaseOffset = (uint) reader.BaseStream.Position; + offsetPairs.Add((reader.ReadUInt32(), reader.ReadUInt32() + fileNameBaseOffset)); + } + + var totalFilesize = reader.ReadUInt64(); + + for (int i = 0; i < fileCount - 1; i++) + { + var fileOffset = offsetPairs[i].FileOffset; + var fileNameOffset = offsetPairs[i].FileNameOffset; + var nextFileOffset = i == fileCount - 2 ? totalFilesize : offsetPairs[i + 1].FileOffset; + uint fileSize = (uint) (nextFileOffset - fileOffset); + var fileBytes = new byte[fileSize]; + + reader.BaseStream.Position = fileOffset; + reader.Read(fileBytes); + + var file = new T3dFile(fileSize, fileOffset, fileBytes); + + reader.BaseStream.Position = fileNameOffset; + file.Name = reader.ReadNullTerminatedString().ToLower(); + + if (!IsWldArchive && file.Name.EndsWith(LanternStrings.WldFormatExtension)) + { + IsWldArchive = true; + } + + Files.Add(file); + FileNameReference[file.Name] = file; + } + } + + return true; + } + } +} diff --git a/LanternExtractor/EQ/Archive/T3dFile.cs b/LanternExtractor/EQ/Archive/T3dFile.cs new file mode 100644 index 0000000..21cf0bd --- /dev/null +++ b/LanternExtractor/EQ/Archive/T3dFile.cs @@ -0,0 +1,12 @@ +namespace LanternExtractor.EQ.Archive +{ + /// + /// This class represents a single file in the archive + /// + public class T3dFile : ArchiveFile + { + public T3dFile(uint size, uint offset, byte[] bytes) : base(size, offset, bytes) + { + } + } +} diff --git a/LanternExtractor/EQ/ArchiveExtractor.cs b/LanternExtractor/EQ/ArchiveExtractor.cs index e9b267a..45166be 100644 --- a/LanternExtractor/EQ/ArchiveExtractor.cs +++ b/LanternExtractor/EQ/ArchiveExtractor.cs @@ -1,5 +1,5 @@ using System.IO; -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Sound; using LanternExtractor.EQ.Wld; using LanternExtractor.EQ.Wld.Helpers; @@ -19,33 +19,32 @@ public static void Extract(string path, string rootFolder, ILogger logger, Setti return; } - + var archive = ArchiveFactory.GetArchive(path, logger); string shortName = archiveName.Split('_')[0]; - var s3dArchive = new PfsArchive(path, logger); - if (!s3dArchive.Initialize()) + if (!archive.Initialize()) { - logger.LogError("LanternExtractor: Failed to initialize PFS archive at path: " + path); + logger.LogError("LanternExtractor: Failed to initialize archive at path: " + path); return; } if (settings.RawS3dExtract) { - s3dArchive.WriteAllFiles(Path.Combine(rootFolder + shortName, archiveName)); + archive.WriteAllFiles(Path.Combine(rootFolder, archiveName)); return; } // For non WLD files, we can just extract their contents // Used for pure texture archives (e.g. bmpwad.s3d) and sound archives (e.g. snd1.pfs) - // The difference between this and the raw export is that it will convert images to .png - if (!s3dArchive.IsWldArchive) + // The difference between this and the raw export is that it will convert images to PNG + if (!archive.IsWldArchive) { - WriteS3dTextures(s3dArchive, rootFolder + shortName, logger); + WriteS3dTextures(archive, rootFolder + shortName, logger); - if (EqFileHelper.IsSoundArchive(archiveName)) + if (EqFileHelper.IsUsedSoundArchive(archiveName)) { - var soundFolder = settings.ExportSoundsToSingleFolder ? "sounds" : shortName; - WriteS3dSounds(s3dArchive, rootFolder + soundFolder, logger); + WriteS3dSounds(archive, + Path.Combine(rootFolder, settings.ExportSoundsToSingleFolder ? "sounds" : shortName), logger); } return; @@ -53,7 +52,7 @@ public static void Extract(string path, string rootFolder, ILogger logger, Setti string wldFileName = archiveName + LanternStrings.WldFormatExtension; - PfsFile wldFileInArchive = s3dArchive.GetFile(wldFileName); + var wldFileInArchive = archive.GetFile(wldFileName); if (wldFileInArchive == null) { @@ -63,49 +62,50 @@ public static void Extract(string path, string rootFolder, ILogger logger, Setti if (EqFileHelper.IsEquipmentArchive(archiveName)) { - ExtractArchiveEquipment(rootFolder, logger, settings, wldFileInArchive, shortName, s3dArchive); + ExtractArchiveEquipment(rootFolder, logger, settings, wldFileInArchive, shortName, archive); } else if (EqFileHelper.IsSkyArchive(archiveName)) { - ExtractArchiveSky(rootFolder, logger, settings, wldFileInArchive, shortName, s3dArchive); + ExtractArchiveSky(rootFolder, logger, settings, wldFileInArchive, shortName, archive); } - else if (EqFileHelper.IsCharactersArchive(archiveName)) + else if (EqFileHelper.IsCharacterArchive(archiveName)) { - ExtractArchiveCharacters(path, rootFolder, logger, settings, archiveName, wldFileInArchive, shortName, s3dArchive); + ExtractArchiveCharacters(path, rootFolder, logger, settings, archiveName, wldFileInArchive, shortName, + archive); } else if (EqFileHelper.IsObjectsArchive(archiveName)) { - ExtractArchiveObjects(path, rootFolder, logger, settings, wldFileInArchive, shortName, s3dArchive); + ExtractArchiveObjects(path, rootFolder, logger, settings, wldFileInArchive, shortName, archive); } else { - ExtractArchiveZone(path, rootFolder, logger, settings, shortName, wldFileInArchive, s3dArchive); + ExtractArchiveZone(path, rootFolder, logger, settings, shortName, wldFileInArchive, archive); } MissingTextureFixer.Fix(archiveName); - } private static void ExtractArchiveZone(string path, string rootFolder, ILogger logger, Settings settings, - string shortName, PfsFile wldFileInArchive, PfsArchive s3dArchive) + string shortName, ArchiveFile wldFileInArchive, ArchiveBase archive) { // Some Kunark zones have a "_lit" which needs to be injected into the main zone file - var s3dArchiveLit = new PfsArchive(path.Replace(shortName, shortName + "_lit"), logger); + var archiveLit = ArchiveFactory.GetArchive(path.Replace(shortName, shortName + "_lit"), logger); WldFileZone wldFileLit = null; - if (s3dArchiveLit.Initialize()) + if (archiveLit.Initialize()) { - var litWldFileInArchive = s3dArchiveLit.GetFile(shortName + "_lit.wld"); + var litWldFileInArchive = archiveLit.GetFile(shortName + "_lit.wld"); wldFileLit = new WldFileZone(litWldFileInArchive, shortName, WldType.Zone, logger, settings); wldFileLit.Initialize(rootFolder, false); - var litWldLightsFileInArchive = s3dArchiveLit.GetFile(shortName + "_lit.wld"); + var litWldLightsFileInArchive = archiveLit.GetFile(shortName + "_lit.wld"); if (litWldLightsFileInArchive != null) { var lightsWldFile = - new WldFileLights(litWldLightsFileInArchive, shortName, WldType.Lights, logger, settings, wldFileLit); + new WldFileLights(litWldLightsFileInArchive, shortName, WldType.Lights, logger, settings, + wldFileLit); lightsWldFile.Initialize(rootFolder); } } @@ -116,15 +116,16 @@ private static void ExtractArchiveZone(string path, string rootFolder, ILogger l if (settings.ExportZoneWithObjects) { wldFile.BasePath = path; - wldFile.BaseS3DArchive = s3dArchive; + wldFile.BaseS3DArchive = archive; wldFile.WldFileToInject = wldFileLit; wldFile.RootFolder = rootFolder; wldFile.ShortName = shortName; } + InitializeWldAndWriteTextures(wldFile, rootFolder, rootFolder + shortName + "/Zone/Textures/", - s3dArchive, settings, logger); + archive, settings, logger); - PfsFile lightsFileInArchive = s3dArchive.GetFile("lights" + LanternStrings.WldFormatExtension); + var lightsFileInArchive = archive.GetFile("lights" + LanternStrings.WldFormatExtension); if (lightsFileInArchive != null) { @@ -133,7 +134,7 @@ private static void ExtractArchiveZone(string path, string rootFolder, ILogger l lightsWldFile.Initialize(rootFolder); } - PfsFile zoneObjectsFileInArchive = s3dArchive.GetFile("objects" + LanternStrings.WldFormatExtension); + var zoneObjectsFileInArchive = archive.GetFile("objects" + LanternStrings.WldFormatExtension); if (zoneObjectsFileInArchive != null) { @@ -142,19 +143,19 @@ private static void ExtractArchiveZone(string path, string rootFolder, ILogger l zoneObjectsWldFile.Initialize(rootFolder); } - ExtractSoundData(shortName, rootFolder, settings); + ExtractSoundData(shortName, rootFolder, logger, settings); } private static void ExtractArchiveObjects(string path, string rootFolder, ILogger logger, Settings settings, - PfsFile wldFileInArchive, string shortName, PfsArchive s3dArchive) + ArchiveFile wldFileInArchive, string shortName, ArchiveBase archive) { // Some zones have a "_2_obj" which needs to be injected into the main zone file - var s3dArchiveObj2 = new PfsArchive(path.Replace(shortName + "_obj", shortName + "_2_obj"), logger); + var archiveObj2 = ArchiveFactory.GetArchive(path.Replace(shortName + "_obj", shortName + "_2_obj"), logger); WldFileZone wldFileObj2 = null; - if (s3dArchiveObj2.Initialize()) + if (archiveObj2.Initialize()) { - var obj2WldFileInArchive = s3dArchiveObj2.GetFile(shortName + "_2_obj.wld"); + var obj2WldFileInArchive = archiveObj2.GetFile(shortName + "_2_obj.wld"); wldFileObj2 = new WldFileZone(obj2WldFileInArchive, shortName, WldType.Zone, logger, settings); wldFileObj2.Initialize(rootFolder, false); @@ -163,26 +164,26 @@ private static void ExtractArchiveObjects(string path, string rootFolder, ILogge var wldFile = new WldFileZone(wldFileInArchive, shortName, WldType.Objects, logger, settings, wldFileObj2); InitializeWldAndWriteTextures(wldFile, rootFolder, rootFolder + ShortnameHelper.GetCorrectZoneShortname(shortName) + "/Objects/Textures/", - s3dArchive, settings, logger); + archive, settings, logger); } private static void ExtractArchiveCharacters(string path, string rootFolder, ILogger logger, Settings settings, - string archiveName, PfsFile wldFileInArchive, string shortName, PfsArchive s3dArchive) + string archiveName, ArchiveFile wldFileInArchive, string shortName, ArchiveBase archive) { WldFileCharacters wldFileToInject = null; // global3_chr contains just animations if (archiveName.StartsWith("global3_chr")) { - var s3dArchive2 = new PfsArchive(path.Replace("global3_chr", "global_chr"), logger); + var archive2 = ArchiveFactory.GetArchive(path.Replace("global3_chr", "global_chr"), logger); - if (!s3dArchive2.Initialize()) + if (!archive2.Initialize()) { - logger.LogError("Failed to initialize PFS archive at path: " + path); + logger.LogError("Failed to initialize archive at path: " + path); return; } - PfsFile wldFileInArchive2 = s3dArchive2.GetFile("global_chr.wld"); + var wldFileInArchive2 = archive2.GetFile("global_chr.wld"); wldFileToInject = new WldFileCharacters(wldFileInArchive2, "global_chr", WldType.Characters, logger, settings); @@ -198,44 +199,44 @@ private static void ExtractArchiveCharacters(string path, string rootFolder, ILo : ShortnameHelper.GetCorrectZoneShortname(shortName) + "/Characters/Textures/"); InitializeWldAndWriteTextures(wldFile, rootFolder, exportPath, - s3dArchive, settings, logger); + archive, settings, logger); } - private static void ExtractArchiveSky(string rootFolder, ILogger logger, Settings settings, PfsFile wldFileInArchive, - string shortName, PfsArchive s3dArchive) + private static void ExtractArchiveSky(string rootFolder, ILogger logger, Settings settings, + ArchiveFile wldFileInArchive, string shortName, ArchiveBase archive) { var wldFile = new WldFileZone(wldFileInArchive, shortName, WldType.Sky, logger, settings); InitializeWldAndWriteTextures(wldFile, rootFolder, rootFolder + shortName + "/Textures/", - s3dArchive, settings, logger); + archive, settings, logger); } private static void ExtractArchiveEquipment(string rootFolder, ILogger logger, Settings settings, - PfsFile wldFileInArchive, string shortName, PfsArchive s3dArchive) + ArchiveFile wldFileInArchive, string shortName, ArchiveBase archive) { var wldFile = new WldFileEquipment(wldFileInArchive, shortName, WldType.Equipment, logger, settings); var exportPath = rootFolder + - (settings.ExportEquipmentToSingleFolder && - settings.ModelExportFormat == ModelExportFormat.Intermediate - ? "equipment/Textures/" - : shortName + "/Textures/"); + (settings.ExportEquipmentToSingleFolder && + settings.ModelExportFormat == ModelExportFormat.Intermediate + ? "equipment/Textures/" + : shortName + "/Textures/"); - InitializeWldAndWriteTextures(wldFile, rootFolder, exportPath, s3dArchive, settings, logger); + InitializeWldAndWriteTextures(wldFile, rootFolder, exportPath, archive, settings, logger); } private static void InitializeWldAndWriteTextures(WldFile wldFile, string rootFolder, string texturePath, - PfsArchive s3dArchive, Settings settings, ILogger logger) + ArchiveBase archive, Settings settings, ILogger logger) { if (settings.ModelExportFormat != ModelExportFormat.GlTF) { wldFile.Initialize(rootFolder); - s3dArchive.FilenameChanges = wldFile.FilenameChanges; - WriteWldTextures(s3dArchive, wldFile, texturePath, logger); + archive.FilenameChanges = wldFile.FilenameChanges; + WriteWldTextures(archive, wldFile, texturePath, logger); } else // Exporting to GlTF requires that the texture images already be present { wldFile.Initialize(rootFolder, false); - s3dArchive.FilenameChanges = wldFile.FilenameChanges; - WriteWldTextures(s3dArchive, wldFile, texturePath, logger); + archive.FilenameChanges = wldFile.FilenameChanges; + WriteWldTextures(archive, wldFile, texturePath, logger); wldFile.ExportData(); } } @@ -245,7 +246,7 @@ private static void InitializeWldAndWriteTextures(WldFile wldFile, string rootFo /// /// /// - private static void WriteS3dSounds(PfsArchive s3dArchive, string filePath, ILogger logger) + private static void WriteS3dSounds(ArchiveBase s3dArchive, string filePath, ILogger logger) { var allFiles = s3dArchive.GetAllFiles(); @@ -259,13 +260,13 @@ private static void WriteS3dSounds(PfsArchive s3dArchive, string filePath, ILogg } /// - /// Writes textures from the PFS archive to disk, converting them to PNG + /// Writes textures from the archive to disk, converting them to PNG /// - /// + /// /// - private static void WriteS3dTextures(PfsArchive s3dArchive, string filePath, ILogger logger) + private static void WriteS3dTextures(ArchiveBase archive, string filePath, ILogger logger) { - var allFiles = s3dArchive.GetAllFiles(); + var allFiles = archive.GetAllFiles(); foreach (var file in allFiles) { @@ -277,12 +278,12 @@ private static void WriteS3dTextures(PfsArchive s3dArchive, string filePath, ILo } /// - /// Writes textures from the PFS archive to disk, handling masked materials from the WLD + /// Writes textures from the archive to disk, handling masked materials from the WLD /// - /// + /// /// /// - public static void WriteWldTextures(PfsArchive s3dArchive, WldFile wldFile, string zoneName, ILogger logger) + public static void WriteWldTextures(ArchiveBase archive, WldFile wldFile, string zoneName, ILogger logger) { var allBitmaps = wldFile.GetAllBitmapNames(); var maskedBitmaps = wldFile.GetMaskedBitmaps(); @@ -290,13 +291,13 @@ public static void WriteWldTextures(PfsArchive s3dArchive, WldFile wldFile, stri foreach (var bitmap in allBitmaps) { string filename = bitmap; - if (s3dArchive.FilenameChanges != null && - s3dArchive.FilenameChanges.ContainsKey(Path.GetFileNameWithoutExtension(bitmap))) + if (archive.FilenameChanges != null && + archive.FilenameChanges.ContainsKey(Path.GetFileNameWithoutExtension(bitmap))) { - filename = s3dArchive.FilenameChanges[Path.GetFileNameWithoutExtension(bitmap)] + ".bmp"; + filename = archive.FilenameChanges[Path.GetFileNameWithoutExtension(bitmap)] + ".bmp"; } - var pfsFile = s3dArchive.GetFile(filename); + var pfsFile = archive.GetFile(filename); if (pfsFile == null) { @@ -308,15 +309,24 @@ public static void WriteWldTextures(PfsArchive s3dArchive, WldFile wldFile, stri } } - private static void ExtractSoundData(string shortName, string rootFolder, Settings settings) + private static void ExtractSoundData(string shortName, string rootFolder, ILogger logger, Settings settings) { + var envAudio = EnvAudio.Instance; + var ealFilePath = Path.Combine(settings.EverQuestDirectory, "defaults.dat"); + if (!envAudio.Load(ealFilePath)) + { + envAudio.Load(Path.ChangeExtension(ealFilePath, ".eal")); + } + var sounds = new EffSndBnk(settings.EverQuestDirectory + shortName + "_sndbnk" + LanternStrings.SoundFormatExtension); sounds.Initialize(); + var soundEntries = new EffSounds( - settings.EverQuestDirectory + shortName + "_sounds" + LanternStrings.SoundFormatExtension, sounds); - soundEntries.Initialize(); + settings.EverQuestDirectory + shortName + "_sounds" + LanternStrings.SoundFormatExtension, + sounds, envAudio); + soundEntries.Initialize(logger); soundEntries.ExportSoundData(shortName, rootFolder); } } diff --git a/LanternExtractor/EQ/ClientDataCopier.cs b/LanternExtractor/EQ/ClientDataCopier.cs index dbdb957..39c4488 100644 --- a/LanternExtractor/EQ/ClientDataCopier.cs +++ b/LanternExtractor/EQ/ClientDataCopier.cs @@ -6,7 +6,7 @@ namespace LanternExtractor.EQ { public static class ClientDataCopier { - public const string ClientDataDirectory = "clientdata"; + private const string ClientDataDirectory = "clientdata"; public static void Copy(string fileName, string rootFolder, ILogger logger, Settings settings) { diff --git a/LanternExtractor/EQ/MusicCopier.cs b/LanternExtractor/EQ/MusicCopier.cs new file mode 100644 index 0000000..013e24c --- /dev/null +++ b/LanternExtractor/EQ/MusicCopier.cs @@ -0,0 +1,45 @@ +using System.IO; +using System.Linq; +using LanternExtractor.Infrastructure.Logger; + +namespace LanternExtractor.EQ +{ + public static class MusicCopier + { + private const string MusicDirectory = "music"; + + public static void Copy(string shortname, ILogger logger, Settings settings) + { + if (shortname != "music" && shortname != "all") + { + return; + } + + if (!settings.CopyMusic) + { + return; + } + + var xmiFiles = Directory.GetFiles(settings.EverQuestDirectory, "*.*", SearchOption.AllDirectories) + .Where(EqFileHelper.IsMusicFile).ToList(); + var destinationFolder = "Exports/" + MusicDirectory; + + if (!Directory.Exists(destinationFolder)) + { + Directory.CreateDirectory(destinationFolder); + } + + foreach (var xmi in xmiFiles) + { + var fileName = Path.GetFileName(xmi); + var destination = Path.Combine(destinationFolder, fileName); + if (File.Exists(destination)) + { + continue; + } + + File.Copy(xmi, destination); + } + } + } +} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/AudioInstance.cs b/LanternExtractor/EQ/Sound/AudioInstance.cs new file mode 100644 index 0000000..cd8d664 --- /dev/null +++ b/LanternExtractor/EQ/Sound/AudioInstance.cs @@ -0,0 +1,20 @@ +namespace LanternExtractor.EQ.Sound +{ + public abstract class AudioInstance + { + protected AudioInstance(AudioType type, float posX, float posY, float posZ, float radius) + { + AudioType = type; + PosX = posX; + PosY = posY; + PosZ = posZ; + Radius = radius; + } + + public AudioType AudioType { get; } + public float PosX { get; } + public float PosY { get; } + public float PosZ { get; } + public float Radius { get; } + } +} diff --git a/LanternExtractor/EQ/Sound/AudioType.cs b/LanternExtractor/EQ/Sound/AudioType.cs new file mode 100644 index 0000000..f58e840 --- /dev/null +++ b/LanternExtractor/EQ/Sound/AudioType.cs @@ -0,0 +1,23 @@ +namespace LanternExtractor.EQ.Sound +{ + /// + /// Describes the way the sound is heard by the player + /// + public enum AudioType : byte + { + /// + /// Sounds that play at a constant volume + /// + Sound2d = 0, + + /// + /// Music instance. Can specify both day and night trackID. + /// + Music = 1, + + /// + /// Sounds that have a falloff - the farther the player is from the center, the quieter it becomes. + /// + Sound3d = 2, + } +} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/ClientSounds.cs b/LanternExtractor/EQ/Sound/ClientSounds.cs new file mode 100644 index 0000000..1470ac2 --- /dev/null +++ b/LanternExtractor/EQ/Sound/ClientSounds.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace LanternExtractor.EQ.Sound +{ + public static class ClientSounds + { + // Hardcoded client sounds - verified that no other references exist in Trilogy client + private static Dictionary _clientSounds = new Dictionary + { + { 39, "death_me" }, + { 143, "thunder1" }, + { 144, "thunder2" }, + { 158, "wind_lp1" }, + { 159, "rainloop" }, + { 160, "torch_lp" }, + { 161, "watundlp" }, + }; + + public static string GetClientSound(int index) + { + return _clientSounds.TryGetValue(index, out var soundName) ? soundName : SoundConstants.Unknown; + } + } +} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/EffSndBnk.cs b/LanternExtractor/EQ/Sound/EffSndBnk.cs index 2f758d3..9122f07 100644 --- a/LanternExtractor/EQ/Sound/EffSndBnk.cs +++ b/LanternExtractor/EQ/Sound/EffSndBnk.cs @@ -10,11 +10,9 @@ namespace LanternExtractor.EQ.Sound /// public class EffSndBnk { - public readonly List EmitSounds = new List(); - - public readonly List LoopSounds = new List(); - - private readonly string _soundFilePath; + private List _emitSounds = new List(); + private List _loopSounds = new List(); + private string _soundFilePath; public EffSndBnk(string soundFilePath) { @@ -23,15 +21,14 @@ public EffSndBnk(string soundFilePath) public void Initialize() { - List currentList = null; - if (!File.Exists(_soundFilePath)) { return; } + + List currentList = null; string fileText = File.ReadAllText(_soundFilePath); - List parsedLines = TextParser.ParseTextByNewline(fileText); if (parsedLines == null || parsedLines.Count == 0) @@ -41,15 +38,18 @@ public void Initialize() foreach (var line in parsedLines) { + if (string.IsNullOrEmpty(line)) + { + continue; + } + switch (line) { - case "": + case SoundConstants.Emit: + currentList = _emitSounds; continue; - case "EMIT": - currentList = EmitSounds; - continue; - case "LOOP": - currentList = LoopSounds; + case SoundConstants.Loop: + currentList = _loopSounds; continue; default: currentList?.Add(line); @@ -57,5 +57,25 @@ public void Initialize() } } } + + private string GetValueFromList(int index, ref List list) + { + if (index < 0 || index >= list.Count) + { + return SoundConstants.Unknown; + } + + return list[index]; + } + + public string GetEmitSound(int index) + { + return GetValueFromList(index, ref _emitSounds); + } + + public string GetLoopSound(int index) + { + return GetValueFromList(index, ref _loopSounds); + } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/EffSounds.cs b/LanternExtractor/EQ/Sound/EffSounds.cs index 336b3a6..8bbd0da 100644 --- a/LanternExtractor/EQ/Sound/EffSounds.cs +++ b/LanternExtractor/EQ/Sound/EffSounds.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text; +using LanternExtractor.Infrastructure.Logger; namespace LanternExtractor.EQ.Sound { @@ -12,44 +13,38 @@ namespace LanternExtractor.EQ.Sound /// public class EffSounds { + public static int EntryLengthInBytes = 84; + /// /// The sound bank referenced for sound names /// private readonly EffSndBnk _soundBank; - + + private readonly EnvAudio _envAudio; private readonly string _soundFilePath; - - private readonly List _soundEntries = new List(); - - private readonly List _musicTrackEntries = new List(); - - private const int EntryLengthInBytes = 84; - - public EffSounds(string soundFilePath, EffSndBnk soundBank) + private readonly List _audioInstances = new List(); + + public EffSounds(string soundFilePath, EffSndBnk soundBank, EnvAudio envAudio) { _soundFilePath = soundFilePath; _soundBank = soundBank; + _envAudio = envAudio; } - - public void Initialize() + + public void Initialize(ILogger logger) { if (_soundBank == null || !File.Exists(_soundFilePath)) { return; } - FileStream file = File.Open(_soundFilePath, FileMode.Open); - - string zoneShortName = Path.GetFileNameWithoutExtension(_soundFilePath).Split('_')[0]; - - LoadMusicTrackNames(zoneShortName); - + var file = File.Open(_soundFilePath, FileMode.Open); var reader = new BinaryReader(file); - - int fileLength = (int) reader.BaseStream.Length; + int fileLength = (int)reader.BaseStream.Length; if (fileLength % EntryLengthInBytes != 0) { + logger.LogError($"Invalid .eff file - size must be multiple of {EntryLengthInBytes}"); return; } @@ -57,233 +52,191 @@ public void Initialize() for (int i = 0; i < entryCount; ++i) { - var newSound = new SoundEntry(); - newSound.UnkRef00 = reader.ReadInt32(); - newSound.UnkRef04 = reader.ReadInt32(); - newSound.Reserved = reader.ReadInt32(); - newSound.Sequence = reader.ReadInt32(); + var basePosition = EntryLengthInBytes * i; + reader.BaseStream.Position = basePosition + 16; + float posX = reader.ReadSingle(); + float posY = reader.ReadSingle(); + float posZ = reader.ReadSingle(); + float radius = reader.ReadSingle(); - newSound.PosX = reader.ReadSingle(); - newSound.PosY = reader.ReadSingle(); - newSound.PosZ = reader.ReadSingle(); - newSound.Radius = reader.ReadSingle(); + reader.BaseStream.Position = basePosition + 56; - newSound.CooldownDay = reader.ReadInt32(); - newSound.CooldownNight = reader.ReadInt32(); - newSound.RandomDelay = reader.ReadInt32(); + var typeByte = reader.ReadByte(); - newSound.Unk44 = reader.ReadInt32(); - int soundId1 = reader.ReadInt32(); - int soundId2 = reader.ReadInt32(); - - byte soundType = reader.ReadByte(); - newSound.SoundType = (SoundType) soundType; - - if (soundType == 0 || soundType == 2 || soundType == 3) - { - // Find the sound names - EmissionType newSoundEmissionType = newSound.EmissionType; - newSound.SoundIdDay = GetSoundString(soundId1, _soundBank, ref newSoundEmissionType); - EmissionType soundEmissionType = newSound.EmissionType; - newSound.SoundIdNight = GetSoundString(soundId2, _soundBank, ref soundEmissionType); - } - else + if (!Enum.IsDefined(typeof(AudioType), typeByte)) { - newSound.SoundIdDay = GetMusicTrackName(soundId1); - newSound.SoundIdNight = GetMusicTrackName(soundId1); + logger.LogError($"Unable to parse sound type: {typeByte}"); + continue; } - newSound.UnkPad57 = reader.ReadByte(); - newSound.UnkPad58 = reader.ReadByte(); - newSound.UnkPad59 = reader.ReadByte(); - - newSound.AsDistance = reader.ReadInt32(); - newSound.UnkRange64 = reader.ReadInt32(); - newSound.FadeOutMs = reader.ReadInt32(); - newSound.UnkRange72 = reader.ReadInt32(); - newSound.FullVolRange = reader.ReadInt32(); - newSound.UnkRange80 = reader.ReadInt32(); + var type = (AudioType)typeByte; + reader.BaseStream.Position = basePosition + 48; + int soundId1 = reader.ReadInt32(); + string sound1 = GetSoundName(soundId1); - if (newSound.SoundIdDay != "" || newSound.SoundIdNight != "") + if (type == AudioType.Music) { - _soundEntries.Add(newSound); + int soundId2 = reader.ReadInt32(); + reader.BaseStream.Position = basePosition + 60; + int loopCountDay = reader.ReadInt32(); + int loopCountNight = reader.ReadInt32(); + int fadeOutMs = reader.ReadInt32(); + var musicInstance = new MusicInstance(type, posX, posY, posZ, radius, soundId1, soundId2, + loopCountDay, loopCountNight, fadeOutMs); + _audioInstances.Add(musicInstance); } - } - } - - private void LoadMusicTrackNames(string zoneShortName) - { - string[] trackLines = File.ReadAllLines("ClientData/musictracks.txt"); - - bool isTargetZone = false; - - foreach (string line in trackLines) - { - if (!isTargetZone) + else if (type == AudioType.Sound2d) { - if (line == "#" + zoneShortName) - { - isTargetZone = true; - } - - continue; + int soundId2 = reader.ReadInt32(); + string sound2 = GetSoundName(soundId2); + reader.BaseStream.Position = basePosition + 32; + int cooldown1 = reader.ReadInt32(); + int cooldown2 = reader.ReadInt32(); + int cooldownRandom = reader.ReadInt32(); + reader.BaseStream.Position = basePosition + 60; + int volume1Raw = reader.ReadInt32(); + float volume1 = GetEalSoundVolume(sound1, volume1Raw); + int volume2Raw = reader.ReadInt32(); + float volume2 = GetEalSoundVolume(sound2, volume2Raw); + var soundInstance = new SoundInstance2d(type, posX, posY, posZ, radius, volume1, sound1, cooldown1, + sound2, cooldown2, cooldownRandom, volume2); + _audioInstances.Add(soundInstance); } - - if (line == string.Empty) + else { - break; + reader.BaseStream.Position = basePosition + 32; + int cooldown1 = reader.ReadInt32(); + reader.BaseStream.Position = basePosition + 40; + int cooldownRandom = reader.ReadInt32(); + reader.BaseStream.Position = basePosition + 60; + int volumeRaw = reader.ReadInt32(); + float volume = GetEalSoundVolume(sound1, volumeRaw); + reader.BaseStream.Position = basePosition + 72; + int multiplier = reader.ReadInt32(); + var soundInstance = new SoundInstance3d(type, posX, posY, posZ, radius, volume, sound1, + cooldown1, cooldownRandom, multiplier); + _audioInstances.Add(soundInstance); } - - _musicTrackEntries.Add(line); } } - /// - /// Returns the name of the sound based on either the internal reference or the sound back definitions - /// The client uses specific integer ranges to identify the type. - /// There are also a handful of hardcoded sound ids. - /// - /// The id index of the sound - /// The sound bank in which to look - /// The emission type - /// The name of the sound - private string GetSoundString(int soundId, EffSndBnk soundBank, ref EmissionType soundType) + private string GetSoundName(int soundId) { - if (soundId == 0) + var emissionType = GetEmissionType(soundId); + switch (emissionType) { - return string.Empty; + case EmissionType.None: + return string.Empty; + case EmissionType.Emit: + return _soundBank.GetEmitSound(soundId - 1); + case EmissionType.Loop: + return _soundBank.GetLoopSound(soundId - 162); + case EmissionType.Internal: + return ClientSounds.GetClientSound(soundId); + default: + return SoundConstants.Unknown; } + } - if (soundId >= 1 && soundId < 32 && soundId < soundBank.EmitSounds.Count) + private float GetEalSoundVolume(string soundName, int volumeRaw) + { + if (volumeRaw > 0) { - soundType = EmissionType.Emit; - return soundBank.EmitSounds[soundId - 1]; + volumeRaw = -volumeRaw; } - // Hardcoded client sounds - verified that no other references exist in Trilogy client - if (soundId >= 32 && soundId < 162) - { - soundType = EmissionType.Internal; + return volumeRaw == 0 ? _envAudio.GetVolumeLinear(soundName) : _envAudio.GetVolumeLinear(volumeRaw); + } - switch (soundId) - { - case 39: - return "death_me"; - case 143: - return "thunder1"; - case 144: - return "thunder2"; - case 158: - return "wind_lp1"; - case 159: - return "rainloop"; - case 160: - return "torch_lp"; - case 161: - return "watundlp"; - } + private EmissionType GetEmissionType(int soundId) + { + if (soundId <= 0) + { + return EmissionType.None; } - if (soundId < 162 || soundId >= 162 + soundBank.LoopSounds.Count) + if (soundId < 32) { - return string.Empty; + return EmissionType.Emit; } - - soundType = EmissionType.Loop; - return soundBank.LoopSounds[soundId - 161 - 1]; + return soundId < 162 ? EmissionType.Internal : EmissionType.Loop; } - + public void ExportSoundData(string zoneName, string rootFolder) { - var soundExport = new StringBuilder(); + var sound2dExport = new StringBuilder(); + var sound3dExport = new StringBuilder(); var musicExport = new StringBuilder(); - foreach (SoundEntry entry in _soundEntries) + foreach (var entry in _audioInstances) { - if (entry.SoundType == SoundType.Music) + if (entry.AudioType == AudioType.Music) { - musicExport.Append(entry.PosX); - musicExport.Append(","); - musicExport.Append(entry.PosZ); - musicExport.Append(","); - musicExport.Append(entry.PosY); - musicExport.Append(","); - musicExport.Append(entry.Radius); - musicExport.Append(","); - musicExport.Append(entry.SoundIdDay); - musicExport.Append(","); - musicExport.Append(entry.SoundIdNight); - musicExport.Append(","); - musicExport.Append(entry.AsDistance); // Day loop count - musicExport.Append(","); - musicExport.Append(entry.UnkRange64); // Night loop count - musicExport.Append(","); - musicExport.Append(entry.FadeOutMs); - musicExport.AppendLine(); + if (!(entry is MusicInstance music)) + { + continue; + } + + musicExport.AppendLine(string.Join(",", music.PosX, music.PosZ, music.PosY, music.Radius, + music.TrackIndexDay, music.TrackIndexNight, music.LoopCountDay, music.LoopCountNight, + music.FadeOutMs)); + } + else if (entry.AudioType == AudioType.Sound2d) + { + if (!(entry is SoundInstance2d sound2d)) + { + continue; + } + + sound2dExport.AppendLine(string.Join(",", sound2d.PosX, sound2d.PosZ, + sound2d.PosY, sound2d.Radius, sound2d.Sound1, sound2d.Sound2, + sound2d.Cooldown1, sound2d.Cooldown2, sound2d.CooldownRandom, sound2d.Volume1, sound2d.Volume2)); } else { - soundExport.Append((int)entry.SoundType); - soundExport.Append(","); - soundExport.Append(entry.PosX); - soundExport.Append(","); - soundExport.Append(entry.PosZ); - soundExport.Append(","); - soundExport.Append(entry.PosY); - soundExport.Append(","); - soundExport.Append(entry.Radius); - soundExport.Append(","); - soundExport.Append(entry.SoundIdDay); - soundExport.Append(","); - soundExport.Append(entry.SoundIdNight); - soundExport.Append(","); - soundExport.Append(entry.CooldownDay); - soundExport.Append(","); - soundExport.Append(entry.CooldownNight); - soundExport.Append(","); - soundExport.Append(entry.RandomDelay); - soundExport.AppendLine(); + if (!(entry is SoundInstance3d sound3d)) + { + continue; + } + + sound3dExport.AppendLine(string.Join(",", sound3d.PosX, sound3d.PosZ, + sound3d.PosY, sound3d.Radius, sound3d.Sound1, sound3d.Cooldown1, sound3d.CooldownRandom, sound3d.Volume1, + sound3d.Multiplier)); } } - string exportPath = rootFolder + zoneName + "/Zone/"; + string exportPath = Path.Combine(rootFolder, zoneName, "Zone/"); - if (soundExport.Length != 0) + if (musicExport.Length > 0) { StringBuilder exportHeader = new StringBuilder(); - exportHeader.AppendLine(LanternStrings.ExportHeaderTitle + "Sound Instances"); - exportHeader.AppendLine("# Format: SoundType, PosX, PosY, PosZ, Radius, SoundIdDay, SoundIdNight, CooldownDay, CooldownNight, RandomDelay"); - + exportHeader.AppendLine(LanternStrings.ExportHeaderTitle + "Music Instances"); + exportHeader.AppendLine( + "# Format: PosX, PosY, PosZ, Radius, MusicIndexDay, MusicIndexNight, LoopCountDay, LoopCountNight, FadeOutMs"); Directory.CreateDirectory(exportPath); - File.WriteAllText(exportPath + "sound_instances.txt", exportHeader.ToString() + soundExport); + File.WriteAllText(exportPath + "music_instances.txt", exportHeader.ToString() + musicExport); } - if (musicExport.Length != 0) + if (sound2dExport.Length > 0) { StringBuilder exportHeader = new StringBuilder(); - exportHeader.AppendLine(LanternStrings.ExportHeaderTitle + "Music Instances"); - exportHeader.AppendLine("# Format: PosX, PosY, PosZ, Radius, SoundIdDay, SoundIdNight, DayLoopCount, NightLoopCount, FadeOutMs"); - + exportHeader.AppendLine(LanternStrings.ExportHeaderTitle + "Sound 2D Instances"); + exportHeader.AppendLine( + "# Format: PosX, PosY, PosZ, Radius, SoundNameDay, SoundNameNight, CooldownDay, CooldownNight, CooldownRandom, VolumeDay, VolumeNight"); Directory.CreateDirectory(exportPath); - File.WriteAllText(exportPath + "music_instances.txt", exportHeader.ToString() + musicExport); + File.WriteAllText(exportPath + "sound2d_instances.txt", exportHeader.ToString() + sound2dExport); } - } - /// - /// Gets the name of the music track if it exists - /// - /// The zone shortname - /// The index of the track - /// The name of the track - private string GetMusicTrackName(int index) - { - if (index < 0 || index >= _musicTrackEntries.Count) + if (sound3dExport.Length > 0) { - return "Unknown"; + StringBuilder exportHeader = new StringBuilder(); + exportHeader.AppendLine(LanternStrings.ExportHeaderTitle + "Sound 3D Instances"); + exportHeader.AppendLine( + "# Format: PosX, PosY, PosZ, Radius, SoundName, Cooldown, CooldownRandom, Volume, Multiplier"); + Directory.CreateDirectory(exportPath); + File.WriteAllText(exportPath + "sound3d_instances.txt", exportHeader.ToString() + sound3dExport); } - - return _musicTrackEntries[index]; } } -} +} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/EmissionType.cs b/LanternExtractor/EQ/Sound/EmissionType.cs index fc604b7..ec0543f 100644 --- a/LanternExtractor/EQ/Sound/EmissionType.cs +++ b/LanternExtractor/EQ/Sound/EmissionType.cs @@ -2,19 +2,21 @@ { public enum EmissionType { + None = 0, + /// /// Emitted sounds - things like bird noises /// - Emit = 0, + Emit = 1, /// /// Looped sounds - things like the ocean or a lake /// - Loop = 1, + Loop = 2, /// /// Sounds that are internal to the client /// - Internal = 2 + Internal = 3 } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/EnvAudio.cs b/LanternExtractor/EQ/Sound/EnvAudio.cs new file mode 100644 index 0000000..52667c5 --- /dev/null +++ b/LanternExtractor/EQ/Sound/EnvAudio.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using EalTools; + +namespace LanternExtractor.EQ.Sound +{ + public class EnvAudio + { + public static EnvAudio Instance { get; } = new EnvAudio(); + + public EalData Data { get; private set; } + + private bool _loaded; + private string _ealFilePath; + private EalFile _ealFile; + private Dictionary _sourceLevels; + + static EnvAudio() + { + } + + private EnvAudio() + { + } + + public bool Load(string ealFilePath) + { + if (_ealFilePath == ealFilePath) + { + return _loaded; + } + + if (ealFilePath == null || !File.Exists(ealFilePath)) + { + return false; + } + + _ealFilePath = ealFilePath; + + // Allow other threads to open the same file for reading + _ealFile = new EalFile(new FileStream( + _ealFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)); + + if (!_ealFile.Initialize()) + { + return false; + } + + Data = _ealFile.Data; + + if (Data == null) + { + return false; + } + + _sourceLevels = Data.SourceModels.ToDictionary( + s => Path.GetFileNameWithoutExtension(s.Name).ToLower(), + s => s.SourceAttributes.EaxAttributes.DirectPathLevel + ); + + _loaded = true; + + return _loaded; + } + + private int GetVolumeEq(string soundFile) + { + var volume = 0; + _sourceLevels?.TryGetValue(soundFile, out volume); + return volume; + } + + public float GetVolumeLinear(string soundFile) + { + var volumeEq = GetVolumeEq(soundFile); + return GetVolumeLinear(volumeEq); + } + + public float GetVolumeLinear(int directAudioLevel) + { + var linear = MathF.Pow(10.0f, directAudioLevel / 2000.0f); + return Math.Clamp(linear, 0f, 1f); + } + } +} diff --git a/LanternExtractor/EQ/Sound/MusicInstance.cs b/LanternExtractor/EQ/Sound/MusicInstance.cs new file mode 100644 index 0000000..0e5411f --- /dev/null +++ b/LanternExtractor/EQ/Sound/MusicInstance.cs @@ -0,0 +1,22 @@ +namespace LanternExtractor.EQ.Sound +{ + public class MusicInstance : AudioInstance + { + public MusicInstance(AudioType type, float posX, float posY, float posZ, float radius, int trackIndexDay, + int trackIndexNight, int loopCountDay, int loopCountNight, int fadeOutMs) : base(type, posX, posY, posZ, + radius) + { + TrackIndexDay = trackIndexDay; + TrackIndexNight = trackIndexNight; + LoopCountDay = loopCountDay; + LoopCountNight = loopCountNight; + FadeOutMs = fadeOutMs; + } + + public int TrackIndexDay { get; } + public int TrackIndexNight { get; } + public int LoopCountDay { get; } + public int LoopCountNight { get; } + public int FadeOutMs { get; } + } +} diff --git a/LanternExtractor/EQ/Sound/SoundConstants.cs b/LanternExtractor/EQ/Sound/SoundConstants.cs new file mode 100644 index 0000000..e4ff969 --- /dev/null +++ b/LanternExtractor/EQ/Sound/SoundConstants.cs @@ -0,0 +1,9 @@ +namespace LanternExtractor.EQ.Sound +{ + public class SoundConstants + { + public const string Emit = "EMIT"; + public const string Loop = "LOOP"; + public const string Unknown = "Unknown"; + } +} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/SoundEntry.cs b/LanternExtractor/EQ/Sound/SoundEntry.cs deleted file mode 100644 index 1e13a52..0000000 --- a/LanternExtractor/EQ/Sound/SoundEntry.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace LanternExtractor.EQ.Sound -{ - /// - /// Contains information about a single sound instance in the world - /// Big thanks to Shendare from the EQEmu forums for sharing this information - /// More documentation will come as I verify all of this info - /// - public class SoundEntry - { - public EmissionType EmissionType { get; set; } - public int UnkRef00 { get; set; } - public int UnkRef04 { get; set; } - public int Reserved { get; set; } - public int Sequence { get; set; } - public float PosX { get; set; } - public float PosY { get; set; } - public float PosZ { get; set; } - public float Radius { get; set; } - public int CooldownDay { get; set; } - public int CooldownNight { get; set; } - public int RandomDelay { get; set; } - public int Unk44 { get; set; } - public string SoundIdDay { get; set; } - public string SoundIdNight { get; set; } - public SoundType SoundType { get; set; } - public byte UnkPad57 { get; set; } - public byte UnkPad58 { get; set; } - public byte UnkPad59 { get; set; } - public int AsDistance { get; set; } - public int UnkRange64 { get; set; } - public int FadeOutMs { get; set; } - public int UnkRange72 { get; set; } - public int FullVolRange { get; set; } - public int UnkRange80 { get; set; } - } -} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/SoundInstance.cs b/LanternExtractor/EQ/Sound/SoundInstance.cs new file mode 100644 index 0000000..f05a1cb --- /dev/null +++ b/LanternExtractor/EQ/Sound/SoundInstance.cs @@ -0,0 +1,19 @@ +namespace LanternExtractor.EQ.Sound +{ + public abstract class SoundInstance : AudioInstance + { + public string Sound1 { get; } + public float Volume1 { get; } + public int Cooldown1 { get; } + public int CooldownRandom { get; } + + protected SoundInstance(AudioType type, float posX, float posY, float posZ, float radius, float volume1, + string sound1, int cooldown1, int cooldownRandom) : base(type, posX, posY, posZ, radius) + { + Sound1 = sound1; + Volume1 = volume1; + Cooldown1 = cooldown1; + CooldownRandom = cooldownRandom; + } + } +} diff --git a/LanternExtractor/EQ/Sound/SoundInstance2d.cs b/LanternExtractor/EQ/Sound/SoundInstance2d.cs new file mode 100644 index 0000000..fe2db15 --- /dev/null +++ b/LanternExtractor/EQ/Sound/SoundInstance2d.cs @@ -0,0 +1,18 @@ +namespace LanternExtractor.EQ.Sound +{ + public class SoundInstance2d : SoundInstance + { + public string Sound2 { get; } + public float Volume2 { get; } + public int Cooldown2 { get; } + + public SoundInstance2d(AudioType type, float posX, float posY, float posZ, float radius, float volume1, + string sound1, int cooldown1, string sound2, int cooldown2, int cooldownRandom, float volume2) : base(type, posX, posY, posZ, radius, volume1, + sound1, cooldown1, cooldownRandom) + { + Sound2 = sound2; + Cooldown2 = cooldown2; + Volume2 = volume2; + } + } +} diff --git a/LanternExtractor/EQ/Sound/SoundInstance3d.cs b/LanternExtractor/EQ/Sound/SoundInstance3d.cs new file mode 100644 index 0000000..0cc9f82 --- /dev/null +++ b/LanternExtractor/EQ/Sound/SoundInstance3d.cs @@ -0,0 +1,14 @@ +namespace LanternExtractor.EQ.Sound +{ + public class SoundInstance3d : SoundInstance + { + public int Multiplier; + + public SoundInstance3d(AudioType type, float posX, float posY, float posZ, float radius, float volume, + string sound1, int cooldown1, int cooldownRandom, int multiplier) : base(type, posX, posY, posZ, radius, volume, sound1, + cooldown1, cooldownRandom) + { + Multiplier = multiplier; + } + } +} diff --git a/LanternExtractor/EQ/Sound/SoundTest.cs b/LanternExtractor/EQ/Sound/SoundTest.cs new file mode 100644 index 0000000..b70bccf --- /dev/null +++ b/LanternExtractor/EQ/Sound/SoundTest.cs @@ -0,0 +1,35 @@ +using System.IO; +using System.Linq; + +namespace LanternExtractor.EQ.Sound +{ + /// + /// A small helper class useful for sound isolation and testing + /// + public static class SoundTest + { + public static void OutputSingleInstance(BinaryWriter writer, int index, string fileName) + { + writer.BaseStream.Position = 0; + var memoryStream = new MemoryStream(); + writer.BaseStream.CopyTo(memoryStream); + var bytes = memoryStream.ToArray().Skip(index * EffSounds.EntryLengthInBytes) + .Take(EffSounds.EntryLengthInBytes).ToArray(); + File.WriteAllBytes(fileName, bytes); + } + + public static void ModifyInstance(BinaryWriter writer, string fileName) + { + writer.BaseStream.Position = 16; // positions + writer.Write(0f); + writer.Write(0f); + writer.Write(50f); + + + writer.BaseStream.Position = 0; + var memoryStream = new MemoryStream(); + writer.BaseStream.CopyTo(memoryStream); + File.WriteAllBytes(fileName, memoryStream.ToArray()); + } + } +} \ No newline at end of file diff --git a/LanternExtractor/EQ/Sound/SoundType.cs b/LanternExtractor/EQ/Sound/SoundType.cs deleted file mode 100644 index 7743a76..0000000 --- a/LanternExtractor/EQ/Sound/SoundType.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace LanternExtractor.EQ.Sound -{ - /// - /// Describes the way the sound is heard by the player - /// - public enum SoundType : byte - { - /// - /// Sounds that play at a constant volume - /// - NoFalloff = 0, - - /// - /// Background music. Can specify both a daytime and nighttime music via the soundID - /// Music usually has a large fade out delay - /// - Music = 1, - - /// - /// Sounds that have a falloff - the further the player is from the center, the quieter it becomes - /// - Falloff = 2, - - /// - /// Sounds that stay at full volume until the user has wandered outside of the FullVolRange - /// - FullVolumeRange = 3, - } -} \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/DataTypes/Polygon.cs b/LanternExtractor/EQ/Wld/DataTypes/Polygon.cs index 1114014..e1ac7f9 100644 --- a/LanternExtractor/EQ/Wld/DataTypes/Polygon.cs +++ b/LanternExtractor/EQ/Wld/DataTypes/Polygon.cs @@ -9,7 +9,8 @@ public Polygon GetCopy() IsSolid = this.IsSolid, Vertex1 = this.Vertex1, Vertex2 = this.Vertex2, - Vertex3 = this.Vertex3 + Vertex3 = this.Vertex3, + MaterialIndex = this.MaterialIndex }; } @@ -17,5 +18,6 @@ public Polygon GetCopy() public int Vertex1 { get; set; } public int Vertex2 { get; set; } public int Vertex3 { get; set; } + public int MaterialIndex { get; set; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RegionObstacle.cs b/LanternExtractor/EQ/Wld/DataTypes/RegionObstacle.cs new file mode 100644 index 0000000..1537bb1 --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RegionObstacle.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using GlmSharp; + +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public class RegionObstacle + { + /// bit 0 - is a FLOOR + /// bit 1 - is a GEOMETRYCUTTINGOBSTACLE + /// bit 2 - has USERDATA %s + public int Flags { get; set; } + + /// NEXTREGION %d + public int NextRegion { get; set; } + + /// XY_VERTEX 0 %d + /// XYZ_VERTEX 0 %d + /// XY_LINE 0 %d %d + /// XY_EDGE 0 %d %d + /// XYZ_EDGE 0 %d %d + /// PLANE 0 %d + /// EDGEPOLYGON 0 + /// EDGEWALL 0 %d + public RegionObstacleType ObstacleType { get; set; } + + // NUMVERTICES %d + public int NumVertices { get; set; } + + /// VERTEXLIST %d ...%d + public List VertextList { get; set; } + + /// NORMALABCD %f %f %f %f + public vec4 NormalAbcd { get; set; } + + /// EDGEWALL 0 %d + /// Binary values are 0 based. "EDGEWALL 0 1" becomes edge_wall[0] + public int EdgeWall { get; set; } + + /// Length of USERDATA string + public int UserDataSize { get; set; } + + /// USERDATA %s + public byte[] UserData { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RegionObstacleType.cs b/LanternExtractor/EQ/Wld/DataTypes/RegionObstacleType.cs new file mode 100644 index 0000000..8b20a91 --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RegionObstacleType.cs @@ -0,0 +1,15 @@ +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public enum RegionObstacleType + { + XyVertex = 8, + XyzVertex = 9, + XyLine = 10, + XyEdge = 11, + XyzEdge = 12, + Plane = 13, + EdgePolygon = 14, + EdgeWall = 18, + EdgePolygonNormalAbcd = -15, + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RegionType.cs b/LanternExtractor/EQ/Wld/DataTypes/RegionType.cs index d0217de..41f7186 100644 --- a/LanternExtractor/EQ/Wld/DataTypes/RegionType.cs +++ b/LanternExtractor/EQ/Wld/DataTypes/RegionType.cs @@ -7,7 +7,7 @@ public enum RegionType Lava = 2, Pvp = 3, Zoneline = 4, - WaterBlockLOS = 5, + WaterBlockLos = 5, FreezingWater = 6, Slippery = 7, Unknown = 8, diff --git a/LanternExtractor/EQ/Wld/DataTypes/RegionVisList.cs b/LanternExtractor/EQ/Wld/DataTypes/RegionVisList.cs new file mode 100644 index 0000000..0cd0d99 --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RegionVisList.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public class RegionVisList + { + /// RANGE %d + public int RangeCount { get; set; } + + public List Ranges { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RegionVisNode.cs b/LanternExtractor/EQ/Wld/DataTypes/RegionVisNode.cs new file mode 100644 index 0000000..cbba04e --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RegionVisNode.cs @@ -0,0 +1,19 @@ +using GlmSharp; + +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public class RegionVisNode + { + /// NORMALABCD %f %f %f %f + public vec4 NormalAbcd { get; set; } + + /// VISLISTINDEX %d + public int VisListIndex { get; set; } + + /// FRONTTREE %d + public int FrontTree { get; set; } + + /// BACKTREE %d + public int BackTree { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RegionWall.cs b/LanternExtractor/EQ/Wld/DataTypes/RegionWall.cs new file mode 100644 index 0000000..26bd1c1 --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RegionWall.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using GlmSharp; + +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public class RegionWall + { + /// bit 0 - has FLOOR (is floor?) + /// bit 1 - has RENDERMETHOD and NORMALABCD (is renderable?) + public int Flags { get; set; } + + /// NUMVERTICES %d + public int NumVertices { get; set; } + + /// RENDERMETHOD ... + public RenderMethod RenderMethod { get; set; } + + /// RENDERINFO + public RenderInfo RenderInfo { get; set; } + + /// NORMALABCD %f %f %f %f + public vec4 NormalAbcd { get; set; } + + /// VERTEXLIST %d ...%d + /// Binary values are 0 based. "VERTEXLIST 1" becomes vertex_list[0] + public List VertexList { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RenderInfo.cs b/LanternExtractor/EQ/Wld/DataTypes/RenderInfo.cs new file mode 100644 index 0000000..0c0c340 --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RenderInfo.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.IO; +using GlmSharp; +using LanternExtractor.EQ.Wld.Fragments; +using LanternExtractor.Infrastructure; + +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public class RenderInfo + { + public int Flags { get; set; } + public int Pen { get; set; } + public float Brightness { get; set; } + public float ScaledAmbient { get; set; } + public BitmapInfoReference SimpleSpriteReference { get; set; } + public UvInfo UvInfo { get; set; } + public List UvMap { get; set; } + + public static RenderInfo Parse(BinaryReader reader, List fragments) + { + var renderInfo = new RenderInfo + { + Flags = reader.ReadInt32() + }; + + var ba = new BitAnalyzer(renderInfo.Flags); + + var hasPen = ba.IsBitSet(0); + var hasBrightness = ba.IsBitSet(1); + var hasScaledAmbient = ba.IsBitSet(2); + var hasSimpleSprite = ba.IsBitSet(3); + var hasUvInfo = ba.IsBitSet(4); + var hasUvMap = ba.IsBitSet(5); + var isTwoSided = ba.IsBitSet(6); + + if (hasPen) + { + renderInfo.Pen = reader.ReadInt32(); + } + + if (hasBrightness) + { + renderInfo.Brightness = reader.ReadSingle(); + } + + if (hasScaledAmbient) + { + renderInfo.ScaledAmbient = reader.ReadSingle(); + } + + if (hasSimpleSprite) + { + var fragmentRef = reader.ReadInt32(); + renderInfo.SimpleSpriteReference = fragments[fragmentRef - 1] as BitmapInfoReference; + } + + if (hasUvInfo) + { + renderInfo.UvInfo = new UvInfo + { + UvOrigin = new vec3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), + UAxis = new vec3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), + VAxis = new vec3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()) + }; + } + + if (hasUvMap) + { + var uvMapCount = reader.ReadInt32(); + renderInfo.UvMap = new List(); + for (var i = 0; i < uvMapCount; i++) + { + renderInfo.UvMap.Add(new vec2(reader.ReadSingle(), reader.ReadSingle())); + } + } + + return renderInfo; + } + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/RenderMethod.cs b/LanternExtractor/EQ/Wld/DataTypes/RenderMethod.cs new file mode 100644 index 0000000..507f86e --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/RenderMethod.cs @@ -0,0 +1,9 @@ +namespace LanternExtractor.EQ.Wld.DataTypes +{ + // TODO: Break out flags into built-in and user defined + // EQ's user defined combinations are defined as `MaterialType` + public class RenderMethod + { + public int Flags { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/DataTypes/UVInfo.cs b/LanternExtractor/EQ/Wld/DataTypes/UVInfo.cs new file mode 100644 index 0000000..28cc45d --- /dev/null +++ b/LanternExtractor/EQ/Wld/DataTypes/UVInfo.cs @@ -0,0 +1,11 @@ +using GlmSharp; + +namespace LanternExtractor.EQ.Wld.DataTypes +{ + public class UvInfo + { + public vec3 UvOrigin { get; set; } + public vec3 UAxis { get; set; } + public vec3 VAxis { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/Exporters/ActorGltfExporter.cs b/LanternExtractor/EQ/Wld/Exporters/ActorGltfExporter.cs index da47e1f..5aa0ef5 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ActorGltfExporter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ActorGltfExporter.cs @@ -1,4 +1,4 @@ -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.DataTypes; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.Infrastructure.Logger; @@ -64,9 +64,9 @@ private static void ExportZone(WldFileZone wldFileZone, Settings settings, ILogg } // Find associated _obj archive e.g. qeytoqrg_obj.s3d, open it and add meshes and materials to our list - var objPath = wldFileZone.BasePath.Replace(".s3d", "_obj.s3d"); + var objPath = EqFileHelper.ObjArchivePath(wldFileZone.BasePath); var objArchive = Path.GetFileNameWithoutExtension(objPath); - var s3dObjArchive = new PfsArchive(objPath, logger); + var s3dObjArchive = ArchiveFactory.GetArchive(objPath, logger); if (s3dObjArchive.Initialize()) { string wldFileName = objArchive + LanternStrings.WldFormatExtension; @@ -309,4 +309,4 @@ private static void ExportSkeletalActor(Actor actor, Settings settings, WldFile // KHR_materials_variants extension is made for this, but no support for it in SharpGLTF } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/ActorObjExporter.cs b/LanternExtractor/EQ/Wld/Exporters/ActorObjExporter.cs index 29902a9..80b264b 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ActorObjExporter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ActorObjExporter.cs @@ -2,7 +2,7 @@ using System.IO; using System.Linq; using GlmSharp; -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.DataTypes; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.EQ.Wld.Helpers; @@ -12,7 +12,7 @@ namespace LanternExtractor.EQ.Wld.Exporters { public static class ActorObjExporter { - public static Dictionary> _backupVertices = new Dictionary>(); + public static Dictionary> BackupVertices = new Dictionary>(); public static void ExportActors(WldFile wldFile, Settings settings, ILogger logger) { @@ -62,7 +62,7 @@ private static void ExportZone(WldFileZone wldFile, Settings settings, ILogger l var shortName = wldFile.ShortName; // Get object instances within this zone file to map up and instantiate later - PfsFile zoneObjectsFileInArchive = s3dArchive.GetFile("objects" + LanternStrings.WldFormatExtension); + var zoneObjectsFileInArchive = s3dArchive.GetFile("objects" + LanternStrings.WldFormatExtension); if (zoneObjectsFileInArchive != null) { var zoneObjectsWldFile = new WldFileZoneObjects(zoneObjectsFileInArchive, shortName, @@ -72,9 +72,9 @@ private static void ExportZone(WldFileZone wldFile, Settings settings, ILogger l } // Find associated _obj archive e.g. qeytoqrg_obj.s3d, open it and add meshes and materials to our list - string objPath = path.Replace(".s3d", "_obj.s3d"); + string objPath = EqFileHelper.ObjArchivePath(path); string objArchive = Path.GetFileNameWithoutExtension(objPath); - var s3dObjArchive = new PfsArchive(objPath, logger); + var s3dObjArchive = ArchiveFactory.GetArchive(objPath, logger); if (s3dObjArchive.Initialize()) { string wldFileName = objArchive + LanternStrings.WldFormatExtension; @@ -254,7 +254,7 @@ private static void WriteAnimationFrame(SkeletonHierarchy skeleton, string anima { foreach (var mesh in skeleton.Meshes) { - _backupVertices[mesh] = MeshExportHelper.ShiftMeshVertices(mesh, skeleton, + BackupVertices[mesh] = MeshExportHelper.ShiftMeshVertices(mesh, skeleton, wldFile.WldType == WldType.Characters, animation, frameIndex); meshWriter.AddFragmentData(mesh); } @@ -266,7 +266,7 @@ private static void WriteAnimationFrame(SkeletonHierarchy skeleton, string anima settings.ExportZoneMeshGroups, wldFile.ZoneShortname); meshWriter2.SetIsCharacterModel(true); meshWriter2.AddFragmentData(skeleton.Meshes[0]); - _backupVertices[m2] = MeshExportHelper.ShiftMeshVertices(m2, skeleton, + BackupVertices[m2] = MeshExportHelper.ShiftMeshVertices(m2, skeleton, wldFile.WldType == WldType.Characters, animation, frameIndex); meshWriter2.AddFragmentData(m2); meshWriter2.WriteAssetToFile(GetMeshPath(wldFile, FragmentNameCleaner.CleanName(skeleton), i + 1)); @@ -290,7 +290,7 @@ private static void WriteAnimationFrame(SkeletonHierarchy skeleton, string anima private static void RestoreVertices() { - foreach (var shiftedMesh in _backupVertices) + foreach (var shiftedMesh in BackupVertices) { shiftedMesh.Key.Vertices = shiftedMesh.Value; } @@ -326,4 +326,4 @@ private static string GetMaterialListPath(WldFile wldFile, string materialListNa materialListName + "_" + skinIndex + ".mtl"; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/ActorWriter.cs b/LanternExtractor/EQ/Wld/Exporters/ActorWriter.cs index e673959..3e9dc83 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ActorWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ActorWriter.cs @@ -30,30 +30,30 @@ public override void AddFragmentData(WldFragment data) if (_actorType == ActorType.Skeletal) { - _export.Append(FragmentNameCleaner.CleanName(actor)); - _export.Append(","); - _export.Append(FragmentNameCleaner.CleanName(actor.SkeletonReference.SkeletonHierarchy)); - _export.AppendLine(); + Export.Append(FragmentNameCleaner.CleanName(actor)); + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(actor.SkeletonReference.SkeletonHierarchy)); + Export.AppendLine(); } else if (_actorType == ActorType.Static) { - _export.Append(FragmentNameCleaner.CleanName(actor)); - _export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(actor)); + Export.Append(","); if (actor.MeshReference.Mesh != null) { - _export.Append(FragmentNameCleaner.CleanName(actor.MeshReference.Mesh)); + Export.Append(FragmentNameCleaner.CleanName(actor.MeshReference.Mesh)); } else if (actor.MeshReference.LegacyMesh != null) { - _export.Append(FragmentNameCleaner.CleanName(actor.MeshReference.LegacyMesh)); + Export.Append(FragmentNameCleaner.CleanName(actor.MeshReference.LegacyMesh)); } - _export.AppendLine(); + Export.AppendLine(); } else { - _export.AppendLine(FragmentNameCleaner.CleanName(actor)); + Export.AppendLine(FragmentNameCleaner.CleanName(actor)); } _actorCount++; @@ -61,7 +61,7 @@ public override void AddFragmentData(WldFragment data) public override void WriteAssetToFile(string fileName) { - if (_export.Length == 0) + if (Export.Length == 0) { return; } @@ -70,7 +70,7 @@ public override void WriteAssetToFile(string fileName) headerBuilder.AppendLine(LanternStrings.ExportHeaderTitle + "Actor List"); headerBuilder.AppendLine("# Total models: " + _actorCount); - _export.Insert(0, headerBuilder.ToString()); + Export.Insert(0, headerBuilder.ToString()); base.WriteAssetToFile(fileName); } diff --git a/LanternExtractor/EQ/Wld/Exporters/ActorWriterNew.cs b/LanternExtractor/EQ/Wld/Exporters/ActorWriterNew.cs index 095ca6b..4b2993f 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ActorWriterNew.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ActorWriterNew.cs @@ -15,24 +15,24 @@ public override void AddFragmentData(WldFragment data) return; } - _export.Append(actor.ActorType.ToString()); - _export.Append(","); - _export.Append(actor.ReferenceName); + Export.Append(actor.ActorType.ToString()); + Export.Append(","); + Export.Append(actor.ReferenceName); - _export.Append(FragmentNameCleaner.CleanName(actor)); - _export.AppendLine(); + Export.Append(FragmentNameCleaner.CleanName(actor)); + Export.AppendLine(); } public override void WriteAssetToFile(string fileName) { - if (_export.Length == 0) + if (Export.Length == 0) { return; } StringBuilder headerBuilder = new StringBuilder(); headerBuilder.AppendLine(LanternStrings.ExportHeaderTitle + "Actor"); - _export.Insert(0, headerBuilder.ToString()); + Export.Insert(0, headerBuilder.ToString()); base.WriteAssetToFile(fileName); } } diff --git a/LanternExtractor/EQ/Wld/Exporters/ActorWriterNewGlobal.cs b/LanternExtractor/EQ/Wld/Exporters/ActorWriterNewGlobal.cs index 8d0ba0d..38a3425 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ActorWriterNewGlobal.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ActorWriterNewGlobal.cs @@ -88,7 +88,7 @@ public override void WriteAssetToFile(string fileName) foreach (var o in _objects) { - _export.AppendLine(o); + Export.AppendLine(o); } //StringBuilder headerBuilder = new StringBuilder(); diff --git a/LanternExtractor/EQ/Wld/Exporters/AmbientLightColorWriter.cs b/LanternExtractor/EQ/Wld/Exporters/AmbientLightColorWriter.cs index f0d75af..22cd4c0 100644 --- a/LanternExtractor/EQ/Wld/Exporters/AmbientLightColorWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/AmbientLightColorWriter.cs @@ -6,8 +6,8 @@ public class AmbientLightColorWriter : TextAssetWriter { public AmbientLightColorWriter() { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Ambient Light Color"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + "R, G, B"); + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Ambient Light Color"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "R, G, B"); } public override void AddFragmentData(WldFragment data) @@ -19,11 +19,11 @@ public override void AddFragmentData(WldFragment data) return; } - _export.Append(globalAmbientLight.Color.R.ToString()); - _export.Append(","); - _export.Append(globalAmbientLight.Color.G.ToString()); - _export.Append(","); - _export.Append(globalAmbientLight.Color.B.ToString()); + Export.Append(globalAmbientLight.Color.R.ToString()); + Export.Append(","); + Export.Append(globalAmbientLight.Color.G.ToString()); + Export.Append(","); + Export.Append(globalAmbientLight.Color.B.ToString()); } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Exporters/AnimationWriter.cs b/LanternExtractor/EQ/Wld/Exporters/AnimationWriter.cs index 6023733..019d1cb 100644 --- a/LanternExtractor/EQ/Wld/Exporters/AnimationWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/AnimationWriter.cs @@ -10,7 +10,7 @@ public class AnimationWriter : TextAssetWriter public AnimationWriter(bool isCharacterAnimation) { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Animation"); + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Animation"); _isCharacterAnimation = isCharacterAnimation; } @@ -38,9 +38,9 @@ public override void AddFragmentData(WldFragment data) Animation anim = skeleton.Animations[_targetAnimation]; - _export.AppendLine("# Animation: " + _targetAnimation); - _export.AppendLine("framecount," + anim.FrameCount); - _export.AppendLine("totalTimeMs," + anim.AnimationTimeMs); + Export.AppendLine("# Animation: " + _targetAnimation); + Export.AppendLine("framecount," + anim.FrameCount); + Export.AppendLine("totalTimeMs," + anim.AnimationTimeMs); for (int i = 0; i < skeleton.Skeleton.Count; ++i) { @@ -87,39 +87,39 @@ public override void AddFragmentData(WldFragment data) private void CreateTrackString(string fullPath, int frame, BoneTransform boneTransform, int delay) { - _export.Append(fullPath); - _export.Append(","); + Export.Append(fullPath); + Export.Append(","); - _export.Append(frame); - _export.Append(","); + Export.Append(frame); + Export.Append(","); - _export.Append(boneTransform.Translation.x); - _export.Append(","); + Export.Append(boneTransform.Translation.x); + Export.Append(","); - _export.Append(boneTransform.Translation.z); - _export.Append(","); + Export.Append(boneTransform.Translation.z); + Export.Append(","); - _export.Append(boneTransform.Translation.y); - _export.Append(","); + Export.Append(boneTransform.Translation.y); + Export.Append(","); - _export.Append(-boneTransform.Rotation.x); - _export.Append(","); + Export.Append(-boneTransform.Rotation.x); + Export.Append(","); - _export.Append(-boneTransform.Rotation.z); - _export.Append(","); + Export.Append(-boneTransform.Rotation.z); + Export.Append(","); - _export.Append(-boneTransform.Rotation.y); - _export.Append(","); + Export.Append(-boneTransform.Rotation.y); + Export.Append(","); - _export.Append(boneTransform.Rotation.w); - _export.Append(","); + Export.Append(boneTransform.Rotation.w); + Export.Append(","); - _export.Append(boneTransform.Scale); - _export.Append(","); + Export.Append(boneTransform.Scale); + Export.Append(","); - _export.Append(delay.ToString()); + Export.Append(delay.ToString()); - _export.AppendLine(); + Export.AppendLine(); } diff --git a/LanternExtractor/EQ/Wld/Exporters/BspTreeWriter.cs b/LanternExtractor/EQ/Wld/Exporters/BspTreeWriter.cs index 3446750..729c6ca 100644 --- a/LanternExtractor/EQ/Wld/Exporters/BspTreeWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/BspTreeWriter.cs @@ -8,10 +8,10 @@ public class BspTreeWriter : TextAssetWriter { public BspTreeWriter() { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "BSP Tree"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + + Export.AppendLine(LanternStrings.ExportHeaderTitle + "BSP Tree"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "Normal nodes: NormalX, NormalY, NormalZ, SplitDistance, LeftNodeId, RightNodeId"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + + Export.AppendLine(LanternStrings.ExportHeaderFormat + "Leaf nodes: BSPRegionId, RegionType"); } @@ -29,24 +29,24 @@ public override void AddFragmentData(WldFragment data) // Normal node if (node.Region == null) { - _export.Append(node.NormalX.ToString(_numberFormat)); - _export.Append(","); - _export.Append(node.NormalZ.ToString(_numberFormat)); - _export.Append(","); - _export.Append(node.NormalY.ToString(_numberFormat)); - _export.Append(","); - _export.Append(node.SplitDistance.ToString(_numberFormat)); - _export.Append(","); - _export.Append(node.LeftNode.ToString(_numberFormat)); - _export.Append(","); - _export.Append(node.RightNode.ToString(_numberFormat)); - _export.AppendLine(); + Export.Append(node.NormalX.ToString(NumberFormat)); + Export.Append(","); + Export.Append(node.NormalZ.ToString(NumberFormat)); + Export.Append(","); + Export.Append(node.NormalY.ToString(NumberFormat)); + Export.Append(","); + Export.Append(node.SplitDistance.ToString(NumberFormat)); + Export.Append(","); + Export.Append(node.LeftNode.ToString(NumberFormat)); + Export.Append(","); + Export.Append(node.RightNode.ToString(NumberFormat)); + Export.AppendLine(); } else // Leaf node { - _export.Append(node.RegionId.ToString(_numberFormat)); - _export.Append(","); + Export.Append(node.RegionId.ToString(NumberFormat)); + Export.Append(","); string types = string.Empty; @@ -67,11 +67,11 @@ public override void AddFragmentData(WldFragment data) types = RegionType.Normal.ToString(); } - _export.Append(types); + Export.Append(types); if (node.Region.RegionType == null) { - _export.AppendLine(); + Export.AppendLine(); continue; } @@ -81,30 +81,30 @@ public override void AddFragmentData(WldFragment data) if (zoneline != null) { - _export.Append(","); - _export.Append(zoneline.Type.ToString()); - _export.Append(","); + Export.Append(","); + Export.Append(zoneline.Type.ToString()); + Export.Append(","); if (zoneline.Type == ZonelineType.Reference) { - _export.Append(zoneline.Index); + Export.Append(zoneline.Index); } else { - _export.Append(zoneline.ZoneIndex); - _export.Append(","); - _export.Append(zoneline.Position.x); - _export.Append(","); - _export.Append(zoneline.Position.y); - _export.Append(","); - _export.Append(zoneline.Position.z); - _export.Append(","); - _export.Append(zoneline.Heading); + Export.Append(zoneline.ZoneIndex); + Export.Append(","); + Export.Append(zoneline.Position.x); + Export.Append(","); + Export.Append(zoneline.Position.y); + Export.Append(","); + Export.Append(zoneline.Position.z); + Export.Append(","); + Export.Append(zoneline.Heading); } } } - _export.AppendLine(); + Export.AppendLine(); } } } diff --git a/LanternExtractor/EQ/Wld/Exporters/GltfWriter.cs b/LanternExtractor/EQ/Wld/Exporters/GltfWriter.cs index 85294f3..b4baea4 100644 --- a/LanternExtractor/EQ/Wld/Exporters/GltfWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/GltfWriter.cs @@ -81,13 +81,13 @@ public enum ModelGenerationMode "p03", // rotating "p06", // swim "p07", // sitting - "p08", // stand (arms at sides) + "p08", // stand (arms at sides) "sky" }; private static readonly Matrix4x4 MirrorXAxisMatrix = Matrix4x4.CreateReflection(new Plane(1, 0, 0, 0)); private static readonly Matrix4x4 CorrectedWorldMatrix = MirrorXAxisMatrix * Matrix4x4.CreateScale(0.1f); - + private SceneBuilder _scene; private IMeshBuilder _combinedMeshBuilder; private ISet _meshMaterialsToSkip; @@ -110,7 +110,7 @@ public GltfWriter(bool exportVertexColors, GltfExportFormat exportFormat, ILogge public override void AddFragmentData(WldFragment fragment) { AddFragmentData( - mesh:(Mesh)fragment, + mesh:(Mesh)fragment, generationMode:ModelGenerationMode.Separate ); } @@ -123,10 +123,10 @@ public void AddFragmentData(Mesh mesh, SkeletonHierarchy skeleton, } AddFragmentData( - mesh: mesh, - generationMode: ModelGenerationMode.Combine, - isSkinned: true, - meshNameOverride: meshNameOverride, + mesh: mesh, + generationMode: ModelGenerationMode.Combine, + isSkinned: true, + meshNameOverride: meshNameOverride, singularBoneIndex: singularBoneIndex); } @@ -167,7 +167,7 @@ public void GenerateGltfMaterials(IEnumerable materialLists, strin if (string.IsNullOrEmpty(imageFileNameWithoutExtension)) continue; var imagePath = $"{textureImageFolder}{eqMaterial.GetFirstBitmapExportFilename()}"; - + ImageBuilder imageBuilder; if (ShaderTypesThatNeedAlphaAddedToImage.Contains(eqMaterial.ShaderType)) { @@ -187,7 +187,7 @@ public void GenerateGltfMaterials(IEnumerable materialLists, strin var imageName = Path.GetFileNameWithoutExtension(imagePath); imageBuilder = ImageBuilder.From(new MemoryImage(imagePath), imageName); } - + var gltfMaterial = new MaterialBuilder(materialName) .WithDoubleSide(false) .WithMetallicRoughnessShader() @@ -235,13 +235,13 @@ public void GenerateGltfMaterials(IEnumerable materialLists, strin } public void AddFragmentData( - Mesh mesh, - ModelGenerationMode generationMode, - bool isSkinned = false, + Mesh mesh, + ModelGenerationMode generationMode, + bool isSkinned = false, string meshNameOverride = null, - int singularBoneIndex = -1, - ObjectInstance objectInstance = null, - int instanceIndex = 0, + int singularBoneIndex = -1, + ObjectInstance objectInstance = null, + int instanceIndex = 0, bool isZoneMesh = false) { var meshName = meshNameOverride ?? FragmentNameCleaner.CleanName(mesh); @@ -251,8 +251,8 @@ public void AddFragmentData( var canExportVertexColors = _exportVertexColors && ((objectInstance?.Colors?.Colors != null && objectInstance.Colors.Colors.Any()) || (mesh?.Colors != null && mesh.Colors.Any())); - - if (mesh.AnimatedVerticesReference != null && !canExportVertexColors && objectInstance != null && + + if (mesh.AnimatedVerticesReference != null && !canExportVertexColors && objectInstance != null && _sharedMeshes.TryGetValue(meshName, out var existingMesh)) { if (generationMode == ModelGenerationMode.Separate) @@ -284,7 +284,7 @@ public void AddFragmentData( // Keeping track of vertex indexes for each vertex position in case it's an // animated mesh so we can create morph targets later var gltfVertexPositionToWldVertexIndex = new Dictionary(); - + var polygonIndex = 0; foreach (var materialGroup in mesh.MaterialGroups) { @@ -338,7 +338,7 @@ public void AddFragmentData( { _scene.AddRigidMesh(gltfMesh, transformMatrix); _sharedMeshes[meshName] = gltfMesh; - } + } } } @@ -355,9 +355,9 @@ public void ApplyAnimationToSkeleton(SkeletonHierarchy skeleton, string animatio var poseArray = isCharacterAnimation ? skeleton.Animations[DefaultModelPoseAnimationKey].TracksCleanedStripped : skeleton.Animations[DefaultModelPoseAnimationKey].TracksCleaned; - + if (poseArray == null) return; - + for (var i = 0; i < skeleton.Skeleton.Count; i++) { var boneName = isCharacterAnimation @@ -396,9 +396,9 @@ public void ApplyAnimationToSkeleton(SkeletonHierarchy skeleton, string animatio } public void AddCombinedMeshToScene( - bool isZoneMesh = false, - string meshName = null, - string skeletonModelBase = null, + bool isZoneMesh = false, + string meshName = null, + string skeletonModelBase = null, ObjectInstance objectInstance = null) { IMeshBuilder combinedMesh; @@ -625,7 +625,7 @@ private IVertexBuilder GetGltfVertex( return (VertexBuilder)vertexBuilder; } - private (Vector4 v0, Vector4 v1, Vector4 v2) GetVertexColorVectors(Mesh mesh, + private (Vector4 v0, Vector4 v1, Vector4 v2) GetVertexColorVectors(Mesh mesh, (int v0, int v1, int v2) vertexIndices, ObjectInstance objectInstance = null) { var objInstanceColors = objectInstance?.Colors?.Colors ?? new List(); @@ -659,11 +659,12 @@ private void AddAnimatedMeshMorphTargets(Mesh mesh, IMeshBuilder(); var weights = new List(); - var frameDelay = mesh.AnimatedVerticesReference.MeshAnimatedVertices.Delay/1000f; + var animatedVertices = mesh.AnimatedVerticesReference.GetAnimatedVertices(); + var frameDelay = animatedVertices.Delay/1000f; - for (var frame = 0; frame < mesh.AnimatedVerticesReference.MeshAnimatedVertices.Frames.Count; frame++) + for (var frame = 0; frame < animatedVertices.Frames.Count; frame++) { - var vertexPositionsForFrame = mesh.AnimatedVerticesReference.MeshAnimatedVertices.Frames[frame]; + var vertexPositionsForFrame = animatedVertices.Frames[frame]; var morphTarget = gltfMesh.UseMorphTarget(frame); foreach (var vertexGeometry in gltfVertexPositionToWldVertexIndex.Keys) @@ -720,7 +721,7 @@ private List AddNewSkeleton(SkeletonHierarchy skeleton) return skeletonNodes; } - private void ApplyBoneTransformation(NodeBuilder boneNode, DataTypes.BoneTransform boneTransform, + private void ApplyBoneTransformation(NodeBuilder boneNode, DataTypes.BoneTransform boneTransform, string animationKey, int timeMs, bool staticPose) { var scaleVector = new Vector3(boneTransform.Scale); diff --git a/LanternExtractor/EQ/Wld/Exporters/LegacyMeshIntermediateAssetWriter.cs b/LanternExtractor/EQ/Wld/Exporters/LegacyMeshIntermediateAssetWriter.cs index df69add..ccdc0db 100644 --- a/LanternExtractor/EQ/Wld/Exporters/LegacyMeshIntermediateAssetWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/LegacyMeshIntermediateAssetWriter.cs @@ -1,4 +1,6 @@ -using LanternExtractor.EQ.Wld.DataTypes; +using System; +using GlmSharp; +using LanternExtractor.EQ.Wld.DataTypes; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.EQ.Wld.Helpers; @@ -9,84 +11,201 @@ namespace LanternExtractor.EQ.Wld.Exporters /// public class LegacyMeshIntermediateAssetWriter : TextAssetWriter { + private bool _useGroups; + private bool _isCollisionMesh; + private bool _isFirstMesh = true; + private int _currentBaseIndex; + + public override void ClearExportData() + { + base.ClearExportData(); + _isFirstMesh = true; + _currentBaseIndex = 0; + } + + public LegacyMeshIntermediateAssetWriter(bool useGroups, bool isCollisionMesh) + { + _useGroups = useGroups; + _isCollisionMesh = isCollisionMesh; + } + public override void AddFragmentData(WldFragment data) { - if (!(data is LegacyMesh am)) + if (!(data is LegacyMesh lm)) { return; } - - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Alternate Mesh Intermediate Format"); - _export.AppendLine($"ml,{FragmentNameCleaner.CleanName(am.MaterialList)}"); - foreach (var v in am.Vertices) + if (_isCollisionMesh && lm.PolyhedronReference != null) { - _export.Append("v"); - _export.Append(","); - _export.Append(v.x); - _export.Append(","); - _export.Append(v.z); - _export.Append(","); - _export.Append(v.y); - _export.AppendLine(); + var polyhedron = lm.PolyhedronReference.Polyhedron; + + // TODO: polyhedron scale factor + foreach(var vertex in polyhedron.Vertices) + { + Export.Append("v"); + Export.Append(","); + Export.Append(vertex.x + lm.Center.x); + Export.Append(","); + Export.Append(vertex.z + lm.Center.z); + Export.Append(","); + Export.Append(vertex.y + lm.Center.y); + Export.AppendLine(); + } + + foreach(var polygon in polyhedron.Faces) + { + Export.Append("i"); + Export.Append(","); + Export.Append(0); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex1); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex2); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex3); + Export.AppendLine(); + } + + return; } - - foreach (var uv in am.TexCoords) + + if (!_isCollisionMesh && (_isFirstMesh || _useGroups)) { - _export.Append("uv"); - _export.Append(","); - _export.Append(uv.x); - _export.Append(","); - _export.Append(uv.y); - _export.AppendLine(); + Export.Append("ml"); + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(lm.MaterialList)); + Export.AppendLine(); + _isFirstMesh = false; } - - foreach (var n in am.Normals) + + foreach (var vertex in lm.Vertices) + { + Export.Append("v"); + Export.Append(","); + Export.Append(vertex.x + lm.Center.x); + Export.Append(","); + Export.Append(vertex.z + lm.Center.z); + Export.Append(","); + Export.Append(vertex.y + lm.Center.y); + Export.AppendLine(); + } + + foreach (var uv in lm.TexCoords) + { + Export.Append("uv"); + Export.Append(","); + Export.Append(uv.x); + Export.Append(","); + Export.Append(uv.y); + Export.AppendLine(); + } + + foreach (var normal in lm.Normals) { - _export.Append("n"); - _export.Append(","); - _export.Append(n.x); - _export.Append(","); - _export.Append(n.y); - _export.Append(","); - _export.Append(n.z); - _export.AppendLine(); + Export.Append("n"); + Export.Append(","); + Export.Append(normal.x); + Export.Append(","); + Export.Append(normal.y); + Export.Append(","); + Export.Append(normal.z); + Export.AppendLine(); } int currentPolygon = 0; - for (var i = 0; i < am.RenderGroups.Count; i++) + for (var i = 0; i < lm.RenderGroups.Count; i++) { - var renderGroup = am.RenderGroups[i]; + var renderGroup = lm.RenderGroups[i]; for (int j = 0; j < renderGroup.PolygonCount; ++j) { - Polygon polygon = am.Polygons[j + currentPolygon]; - - _export.Append("i"); - _export.Append(","); - _export.Append(renderGroup.MaterialIndex); - _export.Append(","); - _export.Append(polygon.Vertex1); - _export.Append(","); - _export.Append(polygon.Vertex2); - _export.Append(","); - _export.Append(polygon.Vertex3); - _export.AppendLine(); + Polygon polygon = lm.Polygons[currentPolygon]; + currentPolygon++; + + Export.Append("i"); + Export.Append(","); + Export.Append(renderGroup.MaterialIndex); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex1); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex2); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex3); + Export.AppendLine(); + } + } + + if (lm.RenderGroups.Count == 0) + { + foreach (var polygon in lm.Polygons) + { + Export.Append("i"); + Export.Append(","); + Export.Append(polygon.MaterialIndex); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex1); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex2); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex3); + Export.AppendLine(); } + } - currentPolygon += renderGroup.PolygonCount; + foreach (var bone in lm.MobPieces) + { + Export.Append("b"); + Export.Append(","); + Export.Append(bone.Key); + Export.Append(","); + Export.Append(bone.Value.Start); + Export.Append(","); + Export.Append(bone.Value.Count); + Export.AppendLine(); } - - foreach (var bone in am.MobPieces) + + var animatedVertices = lm.AnimatedVerticesReference?.GetAnimatedVertices(); + if (animatedVertices != null && !_isCollisionMesh) { - _export.Append("b"); - _export.Append(","); - _export.Append(bone.Key); - _export.Append(","); - _export.Append(bone.Value.Start); - _export.Append(","); - _export.Append(bone.Value.Count); - _export.AppendLine(); + Export.Append("ad"); + Export.Append(","); + Export.Append(animatedVertices.Delay); + Export.AppendLine(); + + for (var i = 0; i < animatedVertices.Frames.Count; i++) + { + foreach (vec3 position in animatedVertices.Frames[i]) + { + Export.Append("av"); + Export.Append(","); + Export.Append(i); + Export.Append(","); + Export.Append(position.x + lm.Center.x); + Export.Append(","); + Export.Append(position.z + lm.Center.z); + Export.Append(","); + Export.Append(position.y + lm.Center.y); + Export.AppendLine(); + } + } } + + if (!_useGroups) + { + _currentBaseIndex += lm.Vertices.Count; + } + } + + public override void WriteAssetToFile(string fileName) + { + if (Export.Length == 0) + { + return; + } + + Export.Insert(0, LanternStrings.ExportHeaderTitle + "Alternate Mesh Intermediate Format" + Environment.NewLine); + + base.WriteAssetToFile(fileName); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/LightInstancesWriter.cs b/LanternExtractor/EQ/Wld/Exporters/LightInstancesWriter.cs index 2e23340..37a6999 100644 --- a/LanternExtractor/EQ/Wld/Exporters/LightInstancesWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/LightInstancesWriter.cs @@ -6,8 +6,8 @@ public class LightInstancesWriter : TextAssetWriter { public LightInstancesWriter() { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Light Instances"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Light Instances"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "PosX, PosY, PosZ, Radius, ColorR, ColorG, ColorB"); } @@ -20,20 +20,20 @@ public override void AddFragmentData(WldFragment data) return; } - _export.Append(light.Position.x.ToString(_numberFormat)); - _export.Append(","); - _export.Append(light.Position.z.ToString(_numberFormat)); - _export.Append(","); - _export.Append(light.Position.y.ToString(_numberFormat)); - _export.Append(","); - _export.Append(light.Radius.ToString(_numberFormat)); - _export.Append(","); - _export.Append(light.LightReference.LightSource.Color.r.ToString(_numberFormat)); - _export.Append(","); - _export.Append(light.LightReference.LightSource.Color.g.ToString(_numberFormat)); - _export.Append(","); - _export.Append(light.LightReference.LightSource.Color.b.ToString(_numberFormat)); - _export.AppendLine(); + Export.Append(light.Position.x.ToString(NumberFormat)); + Export.Append(","); + Export.Append(light.Position.z.ToString(NumberFormat)); + Export.Append(","); + Export.Append(light.Position.y.ToString(NumberFormat)); + Export.Append(","); + Export.Append(light.Radius.ToString(NumberFormat)); + Export.Append(","); + Export.Append(light.LightReference.LightSource.Color.r.ToString(NumberFormat)); + Export.Append(","); + Export.Append(light.LightReference.LightSource.Color.g.ToString(NumberFormat)); + Export.Append(","); + Export.Append(light.LightReference.LightSource.Color.b.ToString(NumberFormat)); + Export.AppendLine(); } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Exporters/MeshExporter.cs b/LanternExtractor/EQ/Wld/Exporters/MeshExporter.cs index edc0edb..b9d2e69 100644 --- a/LanternExtractor/EQ/Wld/Exporters/MeshExporter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/MeshExporter.cs @@ -11,6 +11,17 @@ public static class MeshExporter { public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logger) { + var meshFolder = "Meshes/"; + var legacyMeshFolder = "AlternateMeshes/"; + + // FIXME: Surface this as a config? + // Some legacy meshes will be overwritten + var mergeMeshFolders = true; + if (mergeMeshFolders) + { + legacyMeshFolder = meshFolder; + } + List meshes = wldFile.GetFragmentsOfType(); List materialLists = wldFile.GetFragmentsOfType(); List legacyMeshes = wldFile.GetFragmentsOfType(); @@ -21,10 +32,11 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg } string exportFolder = wldFile.GetExportFolderForWldType() + "/"; - + var meshWriter = new MeshIntermediateAssetWriter(settings.ExportZoneMeshGroups, false); - var legacyMeshWriter = new LegacyMeshIntermediateAssetWriter(); + var legacyMeshWriter = new LegacyMeshIntermediateAssetWriter(settings.ExportZoneMeshGroups, false); var collisionMeshWriter = new MeshIntermediateAssetWriter(settings.ExportZoneMeshGroups, true); + var collisionLegacyMeshWriter = new LegacyMeshIntermediateAssetWriter(settings.ExportZoneMeshGroups, true); var materialListWriter = new MeshIntermediateMaterialsWriter(); bool exportEachPass = wldFile.WldType != WldType.Zone || settings.ExportZoneMeshGroups; @@ -32,34 +44,12 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg // If it's a zone mesh, we need to ensure we should export a collision mesh. // There are some zones with no non solid polygons (e.g. arena). No collision mesh is exported in this case. // For objects, it's done for each fragment - bool exportCollisionMesh = !exportEachPass && meshes.Where(m => m.ExportSeparateCollision).Any(); - /*if (!exportEachPass) + bool exportCollisionMesh = false; + if (!exportEachPass) { - foreach (Mesh mesh in meshes) - { - if (!mesh.ExportSeparateCollision) - { - continue; - } - - exportCollisionMesh = true; - break; - } - - if (legacyMeshes != null) - { - foreach (var alternateMesh in legacyMeshes) - { - // if (!mesh.ExportSeparateCollision) - // { - // continue; - // } - - exportCollisionMesh = true; - break; - } - } - }*/ + exportCollisionMesh = meshes.Where(m => m.ExportSeparateCollision).Any() || + legacyMeshes.Where(m => m.ExportSeparateCollision).Any(); + } // Export materials if (materialLists != null) @@ -76,7 +66,7 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg { continue; } - + if (settings.ExportCharactersToSingleFolder && wldFile.WldType == WldType.Characters) { if (File.Exists(filePath)) @@ -92,7 +82,7 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg } } } - + materialList.HasBeenExported = true; materialListWriter.WriteAssetToFile(filePath); materialListWriter.ClearExportData(); @@ -107,6 +97,44 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg materialListWriter.WriteAssetToFile(filePath); } + if (legacyMeshes != null) + { + foreach (var alternateMesh in legacyMeshes) + { + legacyMeshWriter.AddFragmentData(alternateMesh); + + // Determine if we need collision + if (exportEachPass) + { + exportCollisionMesh = alternateMesh.ExportSeparateCollision; + } + + if (exportCollisionMesh) + { + collisionLegacyMeshWriter.AddFragmentData(alternateMesh); + } + + if (exportEachPass) + { + var newExportFolder = wldFile.GetExportFolderForWldType() + legacyMeshFolder; + Directory.CreateDirectory(newExportFolder); + legacyMeshWriter.WriteAssetToFile(newExportFolder + + FragmentNameCleaner.CleanName(alternateMesh) + + ".txt"); + legacyMeshWriter.ClearExportData(); + + if (exportCollisionMesh) + { + collisionLegacyMeshWriter.WriteAssetToFile(exportFolder + legacyMeshFolder + + FragmentNameCleaner.CleanName(alternateMesh) + + "_collision" + + ".txt"); + collisionLegacyMeshWriter.ClearExportData(); + } + } + } + } + // Exporting meshes foreach (Mesh mesh in meshes) { @@ -114,7 +142,7 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg { continue; } - + meshWriter.AddFragmentData(mesh); // Determine if we need collision @@ -141,13 +169,13 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg } } - meshWriter.WriteAssetToFile(exportFolder + "Meshes/" + FragmentNameCleaner.CleanName(mesh) + + meshWriter.WriteAssetToFile(exportFolder + meshFolder + FragmentNameCleaner.CleanName(mesh) + ".txt"); meshWriter.ClearExportData(); if (exportCollisionMesh) { - collisionMeshWriter.WriteAssetToFile(exportFolder + "Meshes/" + + collisionMeshWriter.WriteAssetToFile(exportFolder + meshFolder + FragmentNameCleaner.CleanName(mesh) + "_collision" + ".txt"); @@ -156,36 +184,18 @@ public static void ExportMeshes(WldFile wldFile, Settings settings, ILogger logg } } - if (legacyMeshes != null) - { - foreach (var alternateMesh in legacyMeshes) - { - legacyMeshWriter.AddFragmentData(alternateMesh); - - if (exportEachPass) - { - var newExportFolder = wldFile.GetExportFolderForWldType() + "/AlternateMeshes/"; - Directory.CreateDirectory(newExportFolder); - legacyMeshWriter.WriteAssetToFile(newExportFolder + - FragmentNameCleaner.CleanName(alternateMesh) + - ".txt"); - legacyMeshWriter.ClearExportData(); - } - } - } - - if (!exportEachPass) { - meshWriter.WriteAssetToFile(exportFolder + "Meshes/" + wldFile.ZoneShortname + - ".txt"); + legacyMeshWriter.WriteAssetToFile(exportFolder + legacyMeshFolder + wldFile.ZoneShortname + ".txt"); + meshWriter.WriteAssetToFile(exportFolder + meshFolder + wldFile.ZoneShortname + ".txt"); if (exportCollisionMesh) { - collisionMeshWriter.WriteAssetToFile(exportFolder + "Meshes/" + wldFile.ZoneShortname + "_collision" + - ".txt"); + var collisionFileName = wldFile.ZoneShortname + "_collision" + ".txt"; + collisionLegacyMeshWriter.WriteAssetToFile(exportFolder + legacyMeshFolder + collisionFileName); + collisionMeshWriter.WriteAssetToFile(exportFolder + meshFolder + collisionFileName); } } } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateAssetWriter.cs b/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateAssetWriter.cs index ccffcce..950edee 100644 --- a/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateAssetWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateAssetWriter.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using GlmSharp; using LanternExtractor.EQ.Wld.DataTypes; @@ -24,9 +25,8 @@ public MeshIntermediateAssetWriter(bool useGroups, bool isCollisionMesh) { _useGroups = useGroups; _isCollisionMesh = isCollisionMesh; - _export.AppendLine("# Lantern Test Intermediate Format"); } - + public override void AddFragmentData(WldFragment data) { Mesh mesh = data as Mesh; @@ -35,7 +35,7 @@ public override void AddFragmentData(WldFragment data) { return; } - + HashSet usedVertices = new HashSet(); List newIndices = new List(); @@ -46,7 +46,7 @@ public override void AddFragmentData(WldFragment data) for (int i = 0; i < group.PolygonCount; ++i) { Polygon polygon = mesh.Indices[currentPolygon]; - + newIndices.Add(polygon.GetCopy()); currentPolygon++; @@ -54,7 +54,7 @@ public override void AddFragmentData(WldFragment data) { continue; } - + usedVertices.Add(polygon.Vertex1); usedVertices.Add(polygon.Vertex2); usedVertices.Add(polygon.Vertex3); @@ -81,7 +81,7 @@ public override void AddFragmentData(WldFragment data) } unusedVertices++; - + foreach (var polygon in newIndices) { if (polygon.Vertex1 >= i && polygon.Vertex1 != 0) @@ -101,10 +101,10 @@ public override void AddFragmentData(WldFragment data) if (!_isCollisionMesh && (_isFirstMesh || _useGroups)) { - _export.Append("ml"); - _export.Append(","); - _export.Append(FragmentNameCleaner.CleanName(mesh.MaterialList)); - _export.AppendLine(); + Export.Append("ml"); + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(mesh.MaterialList)); + Export.AppendLine(); _isFirstMesh = false; } @@ -114,16 +114,16 @@ public override void AddFragmentData(WldFragment data) { continue; } - + var vertex = mesh.Vertices[i]; - _export.Append("v"); - _export.Append(","); - _export.Append(vertex.x + mesh.Center.x); - _export.Append(","); - _export.Append(vertex.z + mesh.Center.z); - _export.Append(","); - _export.Append(vertex.y + mesh.Center.y); - _export.AppendLine(); + Export.Append("v"); + Export.Append(","); + Export.Append(vertex.x + mesh.Center.x); + Export.Append(","); + Export.Append(vertex.z + mesh.Center.z); + Export.Append(","); + Export.Append(vertex.y + mesh.Center.y); + Export.AppendLine(); } for (var i = 0; i < mesh.TextureUvCoordinates.Count; i++) @@ -132,14 +132,14 @@ public override void AddFragmentData(WldFragment data) { continue; } - + var textureUv = mesh.TextureUvCoordinates[i]; - _export.Append("uv"); - _export.Append(","); - _export.Append(textureUv.x); - _export.Append(","); - _export.Append(textureUv.y); - _export.AppendLine(); + Export.Append("uv"); + Export.Append(","); + Export.Append(textureUv.x); + Export.Append(","); + Export.Append(textureUv.y); + Export.AppendLine(); } for (var i = 0; i < mesh.Normals.Count; i++) @@ -148,16 +148,16 @@ public override void AddFragmentData(WldFragment data) { continue; } - + var normal = mesh.Normals[i]; - _export.Append("n"); - _export.Append(","); - _export.Append(normal.x); - _export.Append(","); - _export.Append(normal.y); - _export.Append(","); - _export.Append(normal.z); - _export.AppendLine(); + Export.Append("n"); + Export.Append(","); + Export.Append(normal.x); + Export.Append(","); + Export.Append(normal.y); + Export.Append(","); + Export.Append(normal.z); + Export.AppendLine(); } for (var i = 0; i < mesh.Colors.Count; i++) @@ -166,18 +166,18 @@ public override void AddFragmentData(WldFragment data) { continue; } - + var vertexColor = mesh.Colors[i]; - _export.Append("c"); - _export.Append(","); - _export.Append(vertexColor.B); - _export.Append(","); - _export.Append(vertexColor.G); - _export.Append(","); - _export.Append(vertexColor.R); - _export.Append(","); - _export.Append(vertexColor.A); - _export.AppendLine(); + Export.Append("c"); + Export.Append(","); + Export.Append(vertexColor.B); + Export.Append(","); + Export.Append(vertexColor.G); + Export.Append(","); + Export.Append(vertexColor.R); + Export.Append(","); + Export.Append(vertexColor.A); + Export.AppendLine(); } currentPolygon = 0; @@ -197,56 +197,57 @@ public override void AddFragmentData(WldFragment data) for (int i = 0; i < group.PolygonCount; ++i) { Polygon polygon = newIndices[currentPolygon]; - + currentPolygon++; - - _export.Append("i"); - _export.Append(","); - _export.Append(group.MaterialIndex); - _export.Append(","); - _export.Append(_currentBaseIndex + polygon.Vertex1); - _export.Append(","); - _export.Append(_currentBaseIndex + polygon.Vertex2); - _export.Append(","); - _export.Append(_currentBaseIndex + polygon.Vertex3); - _export.AppendLine(); + + Export.Append("i"); + Export.Append(","); + Export.Append(group.MaterialIndex); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex1); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex2); + Export.Append(","); + Export.Append(_currentBaseIndex + polygon.Vertex3); + Export.AppendLine(); } } - + foreach (var bone in mesh.MobPieces) { - _export.Append("b"); - _export.Append(","); - _export.Append(bone.Key); - _export.Append(","); - _export.Append(bone.Value.Start); - _export.Append(","); - _export.Append(bone.Value.Count); - _export.AppendLine(); + Export.Append("b"); + Export.Append(","); + Export.Append(bone.Key); + Export.Append(","); + Export.Append(bone.Value.Start); + Export.Append(","); + Export.Append(bone.Value.Count); + Export.AppendLine(); } - if (mesh.AnimatedVerticesReference != null && !_isCollisionMesh) + var animatedVertices = mesh.AnimatedVerticesReference?.GetAnimatedVertices(); + if (animatedVertices != null && !_isCollisionMesh) { - _export.Append("ad"); - _export.Append(","); - _export.Append(mesh.AnimatedVerticesReference.MeshAnimatedVertices.Delay); - _export.AppendLine(); + Export.Append("ad"); + Export.Append(","); + Export.Append(animatedVertices.Delay); + Export.AppendLine(); - for (var i = 0; i < mesh.AnimatedVerticesReference.MeshAnimatedVertices.Frames.Count; i++) + for (var i = 0; i < animatedVertices.Frames.Count; i++) { - List frame = mesh.AnimatedVerticesReference.MeshAnimatedVertices.Frames[i]; + List frame = animatedVertices.Frames[i]; foreach (vec3 position in frame) { - _export.Append("av"); - _export.Append(","); - _export.Append(i); - _export.Append(","); - _export.Append(position.x + mesh.Center.x); - _export.Append(","); - _export.Append(position.z + mesh.Center.z); - _export.Append(","); - _export.Append(position.y + mesh.Center.y); - _export.AppendLine(); + Export.Append("av"); + Export.Append(","); + Export.Append(i); + Export.Append(","); + Export.Append(position.x + mesh.Center.x); + Export.Append(","); + Export.Append(position.z + mesh.Center.z); + Export.Append(","); + Export.Append(position.y + mesh.Center.y); + Export.AppendLine(); } } } @@ -256,5 +257,17 @@ public override void AddFragmentData(WldFragment data) _currentBaseIndex += mesh.Vertices.Count - unusedVertices; } } + + public override void WriteAssetToFile(string fileName) + { + if (Export.Length == 0) + { + return; + } + + Export.Insert(0, LanternStrings.ExportHeaderTitle + "Mesh Intermediate Format" + Environment.NewLine); + + base.WriteAssetToFile(fileName); + } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateMaterialsExporter.cs b/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateMaterialsExporter.cs index 12e7e4f..e894dc4 100644 --- a/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateMaterialsExporter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/MeshIntermediateMaterialsExporter.cs @@ -14,14 +14,14 @@ public override void AddFragmentData(WldFragment data) return; } - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Material List Intermediate Format"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + "Index, MaterialName, AnimationTextures, AnimationDelayMs, SkinTextures"); + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Material List Intermediate Format"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "Index, MaterialName, AnimationTextures, AnimationDelayMs, SkinTextures"); for (int i = 0; i < list.Materials.Count; i++) { Material material = list.Materials[i]; - _export.Append(i); - _export.Append(","); + Export.Append(i); + Export.Append(","); List allMaterials = new List {material}; allMaterials.AddRange(list.GetMaterialVariants(material, null)); @@ -35,17 +35,17 @@ public override void AddFragmentData(WldFragment data) currentMaterial = allMaterials.First(); } - _export.Append(GetMaterialString(currentMaterial)); + Export.Append(GetMaterialString(currentMaterial)); if (j < list.VariantCount) { - _export.Append(";"); + Export.Append(";"); } } - _export.Append(","); - _export.Append(material.BitmapInfoReference?.BitmapInfo.AnimationDelayMs ?? 0); - _export.AppendLine(); + Export.Append(","); + Export.Append(material.BitmapInfoReference?.BitmapInfo.AnimationDelayMs ?? 0); + Export.AppendLine(); } } diff --git a/LanternExtractor/EQ/Wld/Exporters/MeshObjMtlWriter.cs b/LanternExtractor/EQ/Wld/Exporters/MeshObjMtlWriter.cs index c152d64..ee56690 100644 --- a/LanternExtractor/EQ/Wld/Exporters/MeshObjMtlWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/MeshObjMtlWriter.cs @@ -52,12 +52,12 @@ public override void AddFragmentData(WldFragment data) { if(!createdNullMaterial) { - _export.AppendLine(LanternStrings.ObjNewMaterialPrefix + " " + "null"); - _export.AppendLine("Ka 1.000 1.000 1.000"); - _export.AppendLine("Kd 1.000 1.000 1.000"); - _export.AppendLine("Ks 0.000 0.000 0.000"); - _export.AppendLine("d 1.0 "); - _export.AppendLine("illum 2"); + Export.AppendLine(LanternStrings.ObjNewMaterialPrefix + " " + "null"); + Export.AppendLine("Ka 1.000 1.000 1.000"); + Export.AppendLine("Kd 1.000 1.000 1.000"); + Export.AppendLine("Ks 0.000 0.000 0.000"); + Export.AppendLine("d 1.0 "); + Export.AppendLine("illum 2"); createdNullMaterial = true; } @@ -70,13 +70,13 @@ public override void AddFragmentData(WldFragment data) continue; } - _export.AppendLine(LanternStrings.ObjNewMaterialPrefix + " " + MaterialList.GetMaterialPrefix(material.ShaderType) + material.GetFirstBitmapNameWithoutExtension()); - _export.AppendLine("Ka 1.000 1.000 1.000"); - _export.AppendLine("Kd 1.000 1.000 1.000"); - _export.AppendLine("Ks 0.000 0.000 0.000"); - _export.AppendLine("d 1.0 "); - _export.AppendLine("illum 2"); - _export.AppendLine("map_Kd " + "Textures/" + skinMaterial.GetFirstBitmapExportFilename()); + Export.AppendLine(LanternStrings.ObjNewMaterialPrefix + " " + MaterialList.GetMaterialPrefix(material.ShaderType) + material.GetFirstBitmapNameWithoutExtension()); + Export.AppendLine("Ka 1.000 1.000 1.000"); + Export.AppendLine("Kd 1.000 1.000 1.000"); + Export.AppendLine("Ks 0.000 0.000 0.000"); + Export.AppendLine("d 1.0 "); + Export.AppendLine("illum 2"); + Export.AppendLine("map_Kd " + "Textures/" + skinMaterial.GetFirstBitmapExportFilename()); } } } diff --git a/LanternExtractor/EQ/Wld/Exporters/MeshObjWriter.cs b/LanternExtractor/EQ/Wld/Exporters/MeshObjWriter.cs index 38d651d..669ab95 100644 --- a/LanternExtractor/EQ/Wld/Exporters/MeshObjWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/MeshObjWriter.cs @@ -95,17 +95,17 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec var cosc = Math.Cos(roll); var sinc = Math.Sin(roll); - var Axx = cosa * cosb; - var Axy = cosa * sinb * sinc - sina * cosc; - var Axz = cosa * sinb * cosc + sina * sinc; + var axx = cosa * cosb; + var axy = cosa * sinb * sinc - sina * cosc; + var axz = cosa * sinb * cosc + sina * sinc; - var Ayx = sina * cosb; - var Ayy = sina * sinb * sinc + cosa * cosc; - var Ayz = sina * sinb * cosc - cosa * sinc; + var ayx = sina * cosb; + var ayy = sina * sinb * sinc + cosa * cosc; + var ayz = sina * sinb * cosc - cosa * sinc; - var Azx = -sinb; - var Azy = cosb * sinc; - var Azz = cosb * cosc; + var azx = -sinb; + var azy = cosb * sinc; + var azz = cosb * cosc; if (mesh == null) { @@ -125,13 +125,13 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec name = LanternStrings.ObjMaterialHeader + _forcedMeshList + ".mtl"; } - _export.AppendLine(name); + Export.AppendLine(name); _isFirstMesh = false; } if (_exportGroups) { - _export.AppendLine("g " + FragmentNameCleaner.CleanName(mesh)); + Export.AppendLine("g " + FragmentNameCleaner.CleanName(mesh)); } if (mesh.ExportSeparateCollision) @@ -268,11 +268,12 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec } int frameCount = 1; + var animatedVertices = mesh.AnimatedVerticesReference?.GetAnimatedVertices(); // We end up with OOM errors trying to concat frames of exported zones with objects, i.e. when we have associatedObject - if (associatedObject == null && mesh.AnimatedVerticesReference != null) + if (associatedObject == null && animatedVertices != null) { - frameCount += mesh.AnimatedVerticesReference.MeshAnimatedVertices.Frames.Count; + frameCount += animatedVertices.Frames.Count; } for (int i = 0; i < frameCount; ++i) @@ -294,12 +295,12 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec } else { - if (mesh.AnimatedVerticesReference == null) + if (mesh.AnimatedVerticesReference == null || animatedVertices == null) { continue; } - vertex = mesh.AnimatedVerticesReference.MeshAnimatedVertices.Frames[i - 1][usedVertex]; + vertex = animatedVertices.Frames[i - 1][usedVertex]; } // Apply transformation for scale @@ -316,14 +317,14 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec var py = vertex.y; var pz = vertex.z; - float x = (float)(Axx * px + Axy * py + Axz * pz); - float y = (float)(Ayx * px + Ayy * py + Ayz * pz); - float z = (float)(Azx * px + Azy * py + Azz * pz); + float x = (float)(axx * px + axy * py + axz * pz); + float y = (float)(ayx * px + ayy * py + ayz * pz); + float z = (float)(azx * px + azy * py + azz * pz); vertex = new vec3(x, y, z); } - vertexOutput.AppendLine("v " + (-(vertex.x + mesh.Center.x + offset.x)).ToString(_numberFormat) + " " + - (vertex.z + mesh.Center.z + offset.z).ToString(_numberFormat) + " " + - (vertex.y + mesh.Center.y + offset.y).ToString(_numberFormat)); + vertexOutput.AppendLine("v " + (-(vertex.x + mesh.Center.x + offset.x)).ToString(NumberFormat) + " " + + (vertex.z + mesh.Center.z + offset.z).ToString(NumberFormat) + " " + + (vertex.y + mesh.Center.y + offset.y).ToString(NumberFormat)); if (_objExportType == ObjExportType.Collision) { @@ -338,8 +339,8 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec } vec2 vertexUvs = mesh.TextureUvCoordinates[usedVertex]; - vertexOutput.AppendLine("vt " + vertexUvs.x.ToString(_numberFormat) + " " + - vertexUvs.y.ToString(_numberFormat)); + vertexOutput.AppendLine("vt " + vertexUvs.x.ToString(NumberFormat) + " " + + vertexUvs.y.ToString(NumberFormat)); } frames.Add(vertexOutput.ToString() + faceOutput); @@ -350,7 +351,7 @@ public void AddFragmentData(WldFragment fragment, ObjectInstance associatedObjec { if (i == 0) { - _export.Append(frames[i]); + Export.Append(frames[i]); } else { @@ -381,7 +382,7 @@ public void WriteAllFrames(string fileName) for (int i = 1; i < _frames.Count; ++i) { - _export = _frames[i]; + Export = _frames[i]; WriteAssetToFile(fileName.Replace(".obj", "") + "_frame" + i + ".obj"); } } @@ -405,4 +406,4 @@ public override void ClearExportData() _isFirstMesh = true; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/ObjectInstanceWriter.cs b/LanternExtractor/EQ/Wld/Exporters/ObjectInstanceWriter.cs index 881f461..8fc4942 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ObjectInstanceWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ObjectInstanceWriter.cs @@ -7,8 +7,8 @@ public class ObjectInstanceWriter : TextAssetWriter public ObjectInstanceWriter() { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Object Instances"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Object Instances"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "ModelName, PosX, PosY, PosZ, RotX, RotY, RotZ, ScaleX, ScaleY, ScaleZ, ColorIndex"); } @@ -21,29 +21,29 @@ public override void AddFragmentData(WldFragment data) return; } - _export.Append(instance.ObjectName); - _export.Append(","); - _export.Append(instance.Position.x.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Position.z.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Position.y.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Rotation.x.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Rotation.z.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Rotation.y.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Scale.x.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Scale.y.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Scale.z.ToString(_numberFormat)); - _export.Append(","); - _export.Append(instance.Colors == null ? -1 :instance.Colors.Index); + Export.Append(instance.ObjectName); + Export.Append(","); + Export.Append(instance.Position.x.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Position.z.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Position.y.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Rotation.x.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Rotation.z.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Rotation.y.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Scale.x.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Scale.y.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Scale.z.ToString(NumberFormat)); + Export.Append(","); + Export.Append(instance.Colors == null ? -1 :instance.Colors.Index); - _export.AppendLine(); + Export.AppendLine(); } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Exporters/ParticleSystemWriter.cs b/LanternExtractor/EQ/Wld/Exporters/ParticleSystemWriter.cs index 75f30a9..afb5f68 100644 --- a/LanternExtractor/EQ/Wld/Exporters/ParticleSystemWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/ParticleSystemWriter.cs @@ -7,7 +7,7 @@ public class ParticleSystemWriter : TextAssetWriter { public ParticleSystemWriter() { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Particle System"); + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Particle System"); } public override void AddFragmentData(WldFragment data) @@ -17,7 +17,7 @@ public override void AddFragmentData(WldFragment data) return; } - _export.AppendLine(FragmentNameCleaner.CleanName(data)); + Export.AppendLine(FragmentNameCleaner.CleanName(data)); } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Exporters/SkeletonHierarchyWriter.cs b/LanternExtractor/EQ/Wld/Exporters/SkeletonHierarchyWriter.cs index ead29f3..29d4527 100644 --- a/LanternExtractor/EQ/Wld/Exporters/SkeletonHierarchyWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/SkeletonHierarchyWriter.cs @@ -7,56 +7,68 @@ namespace LanternExtractor.EQ.Wld.Exporters public class SkeletonHierarchyWriter : TextAssetWriter { private bool _stripModelBase; - + public SkeletonHierarchyWriter(bool stripModelBase) { _stripModelBase = stripModelBase; } - + public override void AddFragmentData(WldFragment data) { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Skeleton Hierarchy"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + "BoneName, Children, Mesh, AlternateMesh, ParticleCloud"); - + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Skeleton Hierarchy"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "BoneName, Children, Mesh, AlternateMesh, ParticleCloud"); + SkeletonHierarchy skeleton = data as SkeletonHierarchy; if (skeleton == null) { return; } - + if (skeleton.Meshes != null && skeleton.Meshes.Count != 0) { - _export.Append("meshes"); + Export.Append("meshes"); foreach (var mesh in skeleton.Meshes) { - _export.Append(","); - _export.Append(FragmentNameCleaner.CleanName(mesh)); + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(mesh)); } + Export.AppendLine(); + + Export.Append("secondary_meshes"); foreach (var mesh in skeleton.SecondaryMeshes) { - _export.Append(","); - _export.Append(FragmentNameCleaner.CleanName(mesh)); + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(mesh)); } - _export.AppendLine(); + Export.AppendLine(); } if (skeleton.AlternateMeshes != null && skeleton.AlternateMeshes.Count != 0) { - _export.Append("meshes,"); + Export.Append("meshes"); foreach (var mesh in skeleton.AlternateMeshes) { - _export.Append(FragmentNameCleaner.CleanName(mesh)); + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(mesh)); + } + Export.AppendLine(); + + Export.Append("secondary_meshes"); + foreach (var mesh in skeleton.SecondaryAlternateMeshes) + { + Export.Append(","); + Export.Append(FragmentNameCleaner.CleanName(mesh)); } - - _export.AppendLine(); + + Export.AppendLine(); } foreach (var node in skeleton.Skeleton) { string childrenList = string.Empty; - + foreach (var children in node.Children) { childrenList += children; @@ -73,36 +85,36 @@ public override void AddFragmentData(WldFragment data) { boneName = StripModelBase(boneName, skeleton.ModelBase); } - - _export.Append(CleanSkeletonNodeName(boneName)); - _export.Append(","); - _export.Append(childrenList); - _export.Append(","); + Export.Append(CleanSkeletonNodeName(boneName)); + Export.Append(","); + Export.Append(childrenList); + + Export.Append(","); if (node.MeshReference?.Mesh != null) { - _export.Append(FragmentNameCleaner.CleanName(node.MeshReference.Mesh)); + Export.Append(FragmentNameCleaner.CleanName(node.MeshReference.Mesh)); } - - _export.Append(","); + + Export.Append(","); if (node.MeshReference?.LegacyMesh != null) { - _export.Append(FragmentNameCleaner.CleanName(node.MeshReference.LegacyMesh)); + Export.Append(FragmentNameCleaner.CleanName(node.MeshReference.LegacyMesh)); } - - _export.Append(","); + + Export.Append(","); if (node.ParticleCloud != null) { - _export.Append(FragmentNameCleaner.CleanName(node.ParticleCloud)); + Export.Append(FragmentNameCleaner.CleanName(node.ParticleCloud)); } - - _export.AppendLine(); + + Export.AppendLine(); } } - + private string CleanSkeletonNodeName(string name) { return name.Replace("_DAG", "").ToLower(); @@ -123,4 +135,4 @@ private string StripModelBase(string boneName, string modelBase) return boneName; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Exporters/TextAssetWriter.cs b/LanternExtractor/EQ/Wld/Exporters/TextAssetWriter.cs index c313af8..225753e 100644 --- a/LanternExtractor/EQ/Wld/Exporters/TextAssetWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/TextAssetWriter.cs @@ -7,8 +7,8 @@ namespace LanternExtractor.EQ.Wld.Exporters { public abstract class TextAssetWriter { - protected StringBuilder _export = new StringBuilder(); - protected NumberFormatInfo _numberFormat = new NumberFormatInfo {NumberDecimalSeparator = "."}; + protected StringBuilder Export = new StringBuilder(); + protected NumberFormatInfo NumberFormat = new NumberFormatInfo {NumberDecimalSeparator = "."}; public abstract void AddFragmentData(WldFragment data); @@ -21,24 +21,24 @@ public virtual void WriteAssetToFile(string fileName) return; } - if (_export.Length == 0) + if (Export.Length == 0) { return; } Directory.CreateDirectory(directory); - File.WriteAllText(fileName, _export.ToString()); + File.WriteAllText(fileName, Export.ToString()); } public virtual void ClearExportData() { - _export.Clear(); + Export.Clear(); } public int GetExportByteCount() { - return _export.ToString().Length; + return Export.ToString().Length; } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Exporters/VertexColorsWriter.cs b/LanternExtractor/EQ/Wld/Exporters/VertexColorsWriter.cs index 4b04041..a1646f4 100644 --- a/LanternExtractor/EQ/Wld/Exporters/VertexColorsWriter.cs +++ b/LanternExtractor/EQ/Wld/Exporters/VertexColorsWriter.cs @@ -12,8 +12,8 @@ public VertexColorsWriter() private void AddHeader() { - _export.AppendLine(LanternStrings.ExportHeaderTitle + "Vertex Colors"); - _export.AppendLine(LanternStrings.ExportHeaderFormat + + Export.AppendLine(LanternStrings.ExportHeaderTitle + "Vertex Colors"); + Export.AppendLine(LanternStrings.ExportHeaderFormat + "Red, Green, Blue, Sunlight"); } @@ -28,14 +28,14 @@ public override void AddFragmentData(WldFragment data) foreach (Color color in instance.Colors) { - _export.Append(color.R); - _export.Append(","); - _export.Append(color.G); - _export.Append(","); - _export.Append(color.B); - _export.Append(","); - _export.Append(color.A); - _export.AppendLine(); + Export.Append(color.R); + Export.Append(","); + Export.Append(color.G); + Export.Append(","); + Export.Append(color.B); + Export.Append(","); + Export.Append(color.A); + Export.AppendLine(); } } diff --git a/LanternExtractor/EQ/Wld/Fragments/Actor.cs b/LanternExtractor/EQ/Wld/Fragments/Actor.cs index 34a8026..213c45d 100644 --- a/LanternExtractor/EQ/Wld/Fragments/Actor.cs +++ b/LanternExtractor/EQ/Wld/Fragments/Actor.cs @@ -17,17 +17,17 @@ class Actor : WldFragment /// Mesh reference (optional) /// public MeshReference MeshReference { get; private set; } - + /// /// Skeleton track reference (optional) /// public SkeletonHierarchyReference SkeletonReference { get; private set; } - + /// /// Camera reference (optional) /// public CameraReference CameraReference { get; private set; } - + /// /// Camera reference (optional) /// @@ -36,9 +36,9 @@ class Actor : WldFragment public Fragment07 Fragment07; public ActorType ActorType; - + public string ReferenceName; - + public override void Initialize(int index, int size, byte[] data, List fragments, Dictionary stringHash, bool isNewWldFormat, ILogger logger) @@ -48,17 +48,17 @@ public override void Initialize(int index, int size, byte[] data, int flags = Reader.ReadInt32(); BitAnalyzer ba = new BitAnalyzer(flags); - + bool params1Exist = ba.IsBitSet(0); bool params2Exist = ba.IsBitSet(1); bool fragment2MustContainZero = ba.IsBitSet(7); - + // Is an index in the string hash int fragment1 = Reader.ReadInt32(); // For objects, SPRITECALLBACK - and it's the same reference value string stringValue = stringHash[-fragment1]; - + // 1 for both static and animated objects int size1 = Reader.ReadInt32(); @@ -78,7 +78,7 @@ public override void Initialize(int index, int size, byte[] data, { Reader.BaseStream.Position += 7 * sizeof(int); } - + // Size 1 entries for (int i = 0; i < size1; ++i) { @@ -86,7 +86,7 @@ public override void Initialize(int index, int size, byte[] data, int dataPairCount = Reader.ReadInt32(); // Unknown purpose - // Always 0 and 1.00000002E+30 + // Always 0 and 1.00000002E+30 for (int j = 0; j < dataPairCount; ++j) { int value = Reader.ReadInt32(); @@ -99,13 +99,14 @@ public override void Initialize(int index, int size, byte[] data, { logger.LogWarning("Actor: More than one component references"); } - + // Can contain either a skeleton reference (animated), mesh reference (static) or a camera reference for (int i = 0; i < componentCount; ++i) { int fragmentIndex = Reader.ReadInt32() - 1; - - SkeletonReference = fragments[fragmentIndex] as SkeletonHierarchyReference; + var fragment = fragments[fragmentIndex]; + + SkeletonReference = fragment as SkeletonHierarchyReference; if (SkeletonReference != null) { @@ -113,37 +114,41 @@ public override void Initialize(int index, int size, byte[] data, break; } - MeshReference = fragments[fragmentIndex] as MeshReference; + MeshReference = fragment as MeshReference; - // Why would the mesh reference be null? if (MeshReference != null && MeshReference.Mesh != null) { MeshReference.Mesh.IsHandled = true; break; } - + + if (MeshReference != null && MeshReference.LegacyMesh != null) + { + break; + } + // This only exists in the main zone WLD - CameraReference = fragments[fragmentIndex - 1] as CameraReference; + CameraReference = fragment as CameraReference; if (CameraReference != null) { break; } - - ParticleSpriteReference = fragments[fragmentIndex - 1] as ParticleSpriteReference; + + ParticleSpriteReference = fragment as ParticleSpriteReference; if (ParticleSpriteReference != null) { break; } - - Fragment07 = fragments[fragmentIndex - 1] as Fragment07; + + Fragment07 = fragment as Fragment07; if (Fragment07 != null) { break; } - + logger.LogError($"Actor: Cannot link fragment with index {fragmentIndex}"); } @@ -188,7 +193,7 @@ private void CalculateActorType(ILogger logger) logger.LogError("Cannot determine actor type!"); } } - + public override void OutputInfo(ILogger logger) { base.OutputInfo(logger); @@ -201,9 +206,9 @@ public void AssignSkeletonReference(SkeletonHierarchy skeleton, ILogger logger) { SkeletonHierarchy = skeleton }; - + CalculateActorType(logger); skeleton.IsAssigned = true; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/BspRegion.cs b/LanternExtractor/EQ/Wld/Fragments/BspRegion.cs index f46cfa4..4fe8b42 100644 --- a/LanternExtractor/EQ/Wld/Fragments/BspRegion.cs +++ b/LanternExtractor/EQ/Wld/Fragments/BspRegion.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +using GlmSharp; +using LanternExtractor.EQ.Wld.DataTypes; +using LanternExtractor.Infrastructure; using LanternExtractor.Infrastructure.Logger; namespace LanternExtractor.EQ.Wld.Fragments @@ -21,8 +24,12 @@ public class BspRegion : WldFragment /// public Mesh Mesh { get; private set; } + public LegacyMesh LegacyMesh { get; private set; } + public BspRegionType RegionType { get; private set; } + public List RegionVertices = new List(); + public override void Initialize(int index, int size, byte[] data, List fragments, Dictionary stringHash, bool isNewWldFormat, ILogger logger) @@ -31,68 +38,214 @@ public override void Initialize(int index, int size, byte[] data, Name = stringHash[-Reader.ReadInt32()]; // Flags - // 0x181 - Regions with polygons - // 0x81 - Regions without - // Bit 5 - PVS is WORDS - // Bit 7 - PVS is bytes int flags = Reader.ReadInt32(); - if (flags == 0x181) - { - ContainsPolygons = true; - } + BitAnalyzer ba = new BitAnalyzer(flags); + var hasSphere = ba.IsBitSet(0); + var hasReverbVolume = ba.IsBitSet(1); + var hasReverbOffset = ba.IsBitSet(2); + var regionFog = ba.IsBitSet(3); + var enableGoraud2 = ba.IsBitSet(4); + var encodedVisibility = ba.IsBitSet(5); + var hasLegacyMeshReference = ba.IsBitSet(6); + var hasByteEntries = ba.IsBitSet(7); + var hasMeshReference = ba.IsBitSet(8); + + ContainsPolygons = hasMeshReference || hasLegacyMeshReference; // Always 0 - int unknown1 = Reader.ReadInt32(); - int data1Size = Reader.ReadInt32(); - int data2Size = Reader.ReadInt32(); + int ambientLight = Reader.ReadInt32(); + int numRegionVertex = Reader.ReadInt32(); + int numProximalRegions = Reader.ReadInt32(); // Always 0 - int unknown2 = Reader.ReadInt32(); - int data3Size = Reader.ReadInt32(); - int data4Size = Reader.ReadInt32(); + int numRenderVertices = Reader.ReadInt32(); + int numWalls = Reader.ReadInt32(); + int numObstacles = Reader.ReadInt32(); // Always 0 - int unknown3 = Reader.ReadInt32(); - int data5Size = Reader.ReadInt32(); - int data6Size = Reader.ReadInt32(); + int numCuttingObstacles = Reader.ReadInt32(); + int numVisNode = Reader.ReadInt32(); + int numVisList = Reader.ReadInt32(); + + for (int i = 0; i < numRegionVertex; i++) + { + RegionVertices.Add(new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle())); + } + + var proximalRegions = new List<(int, float)>(); + for (int i = 0; i < numProximalRegions; i++) + { + proximalRegions.Add((Reader.ReadInt32(), Reader.ReadSingle())); + } + + var renderVertices = new List(); + for (int i = 0; i < numRenderVertices; i++) + { + renderVertices.Add(new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle())); + } + + var walls = new List(); + for (int i = 0; i < numWalls; i++) + { + var wall = new RegionWall(); + + wall.Flags = Reader.ReadInt32(); + var wallBa = new BitAnalyzer(wall.Flags); + var isFloor = wallBa.IsBitSet(0); + var isRenderable = wallBa.IsBitSet(1); + + wall.NumVertices = Reader.ReadInt32(); + wall.VertexList = new List(); + for (int v = 0; v < wall.NumVertices; v++) + { + wall.VertexList.Add(Reader.ReadInt32()); + } + + if (isRenderable) + { + wall.RenderMethod = new RenderMethod + { + Flags = Reader.ReadInt32() + }; + + wall.RenderInfo = RenderInfo.Parse(Reader, fragments); + wall.NormalAbcd = new vec4( + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle() + ); + } - // Move past data1 and 2 - Reader.BaseStream.Position += 12 * data1Size + 12 * data2Size; + walls.Add(wall); + } + + var obstacles = new List(); + for (int i = 0; i < numObstacles; i++) + { + var obstacle = new RegionObstacle(); + obstacle.Flags = Reader.ReadInt32(); + + var obstacleBa = new BitAnalyzer(obstacle.Flags); + var isFloor = obstacleBa.IsBitSet(0); + var isGeometryCutting = obstacleBa.IsBitSet(1); + var hasUserData = obstacleBa.IsBitSet(2); + + obstacle.NextRegion = Reader.ReadInt32(); + obstacle.ObstacleType = (RegionObstacleType) Reader.ReadInt32(); + + if (obstacle.ObstacleType == RegionObstacleType.EdgePolygon || + obstacle.ObstacleType == RegionObstacleType.EdgePolygonNormalAbcd) + { + obstacle.NumVertices = Reader.ReadInt32(); + } + + obstacle.VertextList = new List(); + for (int v = 0; v < obstacle.NumVertices; v++) + { + obstacle.VertextList.Add(Reader.ReadInt32()); + } + + if (obstacle.ObstacleType == RegionObstacleType.EdgePolygonNormalAbcd) + { + obstacle.NormalAbcd = new vec4( + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle() + ); + } + + if (obstacle.ObstacleType == RegionObstacleType.EdgeWall) + { + obstacle.EdgeWall = Reader.ReadInt32(); + } + + if (hasUserData) + { + obstacle.UserDataSize = Reader.ReadInt32(); + obstacle.UserData = Reader.ReadBytes(obstacle.UserDataSize); + } - // Move past data3 - for (int i = 0; i < data3Size; ++i) + obstacles.Add(obstacle); + } + + var visNodes = new List(); + for (int i = 0; i < numVisNode; i++) + { + var visNode = new RegionVisNode + { + NormalAbcd = new vec4( + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle() + ), + VisListIndex = Reader.ReadInt32(), + FrontTree = Reader.ReadInt32(), + BackTree = Reader.ReadInt32() + }; + visNodes.Add(visNode); + } + + var visLists = new List(); + for (int i = 0; i < numVisList; i++) { - int data3Flags = Reader.ReadInt32(); - int data3Size2 = Reader.ReadInt32(); - Reader.BaseStream.Position += data3Size2 * 4; + var visList = new RegionVisList + { + RangeCount = Reader.ReadInt16() + }; + + visList.Ranges = new List(); + for (int r = 0; r < visList.RangeCount; r++) + { + int range = hasByteEntries ? Reader.ReadByte() : Reader.ReadInt16(); + visList.Ranges.Add(range); + } + + visLists.Add(visList); } - // Move past the data 4 - for (int i = 0; i < data4Size; ++i) + vec4 sphere; + if (hasSphere) { - // Unhandled for now + sphere = new vec4( + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle() + ); } - // Move past the data5 - for (int i = 0; i < data5Size; i++) + float reverbVolume; + if (hasReverbVolume) { - Reader.BaseStream.Position += 7 * 4; + reverbVolume = Reader.ReadSingle(); } - // Get the size of the PVS and allocate memory - short pvsSize = Reader.ReadInt16(); - Reader.BaseStream.Position += pvsSize; + int reverbOffset; + if (hasReverbOffset) + { + reverbOffset = Reader.ReadInt32(); + } - // Move past the unknowns - uint bytes = Reader.ReadUInt32(); - Reader.BaseStream.Position += 16; + var userDataSize = Reader.ReadInt32(); + var userData = Reader.ReadBytes(userDataSize); // Get the mesh reference index and link to it if (ContainsPolygons) { int meshReference = Reader.ReadInt32() - 1; - Mesh = fragments[meshReference] as Mesh; + + if (hasMeshReference) + { + Mesh = fragments[meshReference] as Mesh; + } + else if (hasLegacyMeshReference) + { + LegacyMesh = fragments[meshReference] as LegacyMesh; + } } } @@ -109,8 +262,9 @@ public override void OutputInfo(ILogger logger) if (ContainsPolygons) { - logger.LogInfo("BspRegion: Mesh index: " + Mesh.Index); + int meshIndex = Mesh?.Index ?? LegacyMesh?.Index ?? 0; + logger.LogInfo("BspRegion: Mesh index: " + meshIndex); } } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/BspRegionType.cs b/LanternExtractor/EQ/Wld/Fragments/BspRegionType.cs index efeed23..4cb3ab8 100644 --- a/LanternExtractor/EQ/Wld/Fragments/BspRegionType.cs +++ b/LanternExtractor/EQ/Wld/Fragments/BspRegionType.cs @@ -94,7 +94,7 @@ public override void Initialize(int index, int size, byte[] data, else if (regionTypeString.StartsWith("sln_")) { // gukbottom, cazicthule (gumdrop), runnyeye, velketor - RegionTypes.Add(RegionType.WaterBlockLOS); + RegionTypes.Add(RegionType.WaterBlockLos); } else if (regionTypeString.StartsWith("vwn_")) { diff --git a/LanternExtractor/EQ/Wld/Fragments/Fragment07.cs b/LanternExtractor/EQ/Wld/Fragments/Fragment07.cs index fcb3cde..7a98e7b 100644 --- a/LanternExtractor/EQ/Wld/Fragments/Fragment07.cs +++ b/LanternExtractor/EQ/Wld/Fragments/Fragment07.cs @@ -11,15 +11,15 @@ namespace LanternExtractor.EQ.Wld.Fragments /// public class Fragment07 : WldFragment { - private Fragment06 Fragment06; + private Fragment06 _fragment06; public override void Initialize(int index, int size, byte[] data, List fragments, Dictionary stringHash, bool isNewWldFormat, ILogger logger) { base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); Name = stringHash[-Reader.ReadInt32()]; - Fragment06 = fragments[Reader.ReadInt32() - 1] as Fragment06; - int value_08 = Reader.ReadInt32(); // always 0 + _fragment06 = fragments[Reader.ReadInt32() - 1] as Fragment06; + int value08 = Reader.ReadInt32(); // always 0 } } } \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Fragments/Fragment17.cs b/LanternExtractor/EQ/Wld/Fragments/Fragment17.cs deleted file mode 100644 index 1b9d441..0000000 --- a/LanternExtractor/EQ/Wld/Fragments/Fragment17.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using LanternExtractor.Infrastructure.Logger; - -namespace LanternExtractor.EQ.Wld.Fragments -{ - /// - /// Fragment17 (0x17) - PolygonAnimation? - /// Internal Name: _POLYHDEF - /// Need to figure this fragment out. - /// - public class Fragment17 : WldFragment - { - public override void Initialize(int index, int size, byte[] data, - List fragments, - Dictionary stringHash, bool isNewWldFormat, ILogger logger) - { - base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); - Name = stringHash[-Reader.ReadInt32()]; - int flags = Reader.ReadInt32(); - int size1 = Reader.ReadInt32(); - int size2 = Reader.ReadInt32(); - float unknown = Reader.ReadSingle(); - - for (int i = 0; i < size1; ++i) - { - float x = Reader.ReadSingle(); - float y = Reader.ReadSingle(); - float z = Reader.ReadSingle(); - } - } - } -} \ No newline at end of file diff --git a/LanternExtractor/EQ/Wld/Fragments/IAnimatedVertices.cs b/LanternExtractor/EQ/Wld/Fragments/IAnimatedVertices.cs new file mode 100644 index 0000000..da97345 --- /dev/null +++ b/LanternExtractor/EQ/Wld/Fragments/IAnimatedVertices.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using GlmSharp; + +namespace LanternExtractor.EQ.Wld.Fragments +{ + public interface IAnimatedVertices + { + List> Frames { get; set; } + int Delay { get; set; } + } +} diff --git a/LanternExtractor/EQ/Wld/Fragments/LegacyMesh.cs b/LanternExtractor/EQ/Wld/Fragments/LegacyMesh.cs index e3003da..afd736b 100644 --- a/LanternExtractor/EQ/Wld/Fragments/LegacyMesh.cs +++ b/LanternExtractor/EQ/Wld/Fragments/LegacyMesh.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using GlmSharp; using LanternExtractor.EQ.Wld.DataTypes; using LanternExtractor.Infrastructure; @@ -14,61 +15,126 @@ namespace LanternExtractor.EQ.Wld.Fragments /// public class LegacyMesh : WldFragment { + public vec3 Center { get; private set; } public List Vertices = new List(); public List TexCoords = new List(); public List Normals = new List(); public List Polygons = new List(); public List VertexTex = new List(); + public List Colors = new List(); public List RenderGroups = new List(); public MaterialList MaterialList; + public PolyhedronReference PolyhedronReference; public Dictionary MobPieces { get; private set; } + /// + /// The animated vertex fragment (0x2E or 0x37) reference + /// + public MeshAnimatedVerticesReference AnimatedVerticesReference { get; private set; } + + /// + /// Set to true if there are non solid polygons in the mesh + /// This means we export collision separately (e.g. trees, fire) + /// + public bool ExportSeparateCollision { get; private set; } public override void Initialize(int index, int size, byte[] data, List fragments, Dictionary stringHash, bool isNewWldFormat, ILogger logger) { base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); Name = stringHash[-Reader.ReadInt32()]; + + // TODO: investigate flags further + // looks like some flags will zero and 1.0 fields if they are missing. + // 0x1 (bit0) center offset + // 0x2 (bit1) bounding radius? + // 0x200 (bit9) + // 0x400 (bit10) colors? + // 0x800 (bit11) RenderGroups + // 0x1000 (bit12) VertexTex + // 0x2000 (bit13) + // 0x4000 (bit14) shown in ghidra as 0x40 bounding box? + // 0x8000 (bit15) shown in ghidra as 0x80 int flags = Reader.ReadInt32(); + BitAnalyzer ba = new BitAnalyzer(flags); + int vertexCount = Reader.ReadInt32(); int texCoordCount = Reader.ReadInt32(); int normalsCount = Reader.ReadInt32(); int colorsCount = Reader.ReadInt32(); // size4 int polygonCount = Reader.ReadInt32(); int size6 = Reader.ReadInt16(); - int fragment1maybe = Reader.ReadInt16(); + int fragment1Maybe = Reader.ReadInt16(); int vertexPieceCount = Reader.ReadInt32(); // -1 MaterialList = fragments[Reader.ReadInt32() - 1] as MaterialList; - int fragment3 = Reader.ReadInt32(); - float centerX = Reader.ReadSingle(); - float centerY = Reader.ReadSingle(); - float centerZ = Reader.ReadSingle(); - int params2 = Reader.ReadInt32(); - int something2 = Reader.ReadInt32(); - float something3 = Reader.ReadInt32(); - + int meshAnimation = Reader.ReadInt32(); + + // Vertex animation only + if (meshAnimation != 0) + { + AnimatedVerticesReference = fragments[meshAnimation - 1] as MeshAnimatedVerticesReference; + } + + float something1 = Reader.ReadSingle(); + + // This might also be able to take a sphere (0x16) or sphere list (0x1a) collision volume + var polyhedronReference = Reader.ReadInt32(); + if (polyhedronReference > 0) + { + PolyhedronReference = fragments[polyhedronReference - 1] as PolyhedronReference; + var sphereFragment = fragments[polyhedronReference - 1] as Fragment16; + if (sphereFragment != null) + { + System.Console.WriteLine(sphereFragment.Name); + } + ExportSeparateCollision = true; + } + + Center = new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle()); + if (!ba.IsBitSet(0)) + { + Center = vec3.Zero; + } + + float boundingRadiusMaybe = Reader.ReadSingle(); + if (!ba.IsBitSet(1)) + { + boundingRadiusMaybe = 1.0f; + } + for (int i = 0; i < vertexCount; ++i) { Vertices.Add(new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle())); } - + for (int i = 0; i < texCoordCount; ++i) { TexCoords.Add(new vec2(Reader.ReadSingle(), Reader.ReadSingle())); } - + for (int i = 0; i < normalsCount; ++i) { Normals.Add(new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle())); } - Reader.BaseStream.Position += colorsCount * sizeof(int); - + // I don't think this is colors + // count seems to match the vertexCount when present + // found in gequip.t3d + for (int i = 0; i < colorsCount; ++i) + { + // byte 0 seems to always be 1 + // byte 1 seems to always be 0,1,2 + // byte 2 seems to be between 0 and polygonCount - 1 + // byte 3 seems to always be 0 + var unkBytes = Reader.ReadBytes(4); + } + + // faces for (int i = 0; i < polygonCount; ++i) { int flag = Reader.ReadInt16(); - + int unk1 = Reader.ReadInt16(); - int unk2 = Reader.ReadInt16(); + int materialIndex = Reader.ReadInt16(); int unk3 = Reader.ReadInt16(); int unk4 = Reader.ReadInt16(); @@ -77,18 +143,19 @@ public override void Initialize(int index, int size, byte[] data, List(); int mobStart = 0; for (int i = 0; i < vertexPieceCount; ++i) @@ -116,8 +183,6 @@ public override void Initialize(int index, int size, byte[] data, List + /// LegactMeshAnimatedVertices (0x2E) + /// Internal name: _DMTRACKDEF + /// Contains a list of frames each containing a position for each vertex. + /// The frame vertices are cycled through, animating the model. + /// + public class LegacyMeshAnimatedVertices : WldFragment, IAnimatedVertices + { + /// + /// The model frames + /// + public List> Frames { get; set; } + + /// + /// The delay between the vertex swaps + /// + public int Delay { get; set; } + + public override void Initialize(int index, int size, byte[] data, + List fragments, + Dictionary stringHash, bool isNewWldFormat, ILogger logger) + { + base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); + + Name = stringHash[-Reader.ReadInt32()]; + int flags = Reader.ReadInt32(); + int vertexCount = Reader.ReadInt32(); + int frameCount = Reader.ReadInt32(); + Delay = Reader.ReadInt32(); + int param1 = Reader.ReadInt32(); + + Frames = new List>(); + for (var i = 0; i < frameCount; i++) + { + var positions = new List(); + + for (var v = 0; v < vertexCount; v++) + { + positions.Add( + new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle()) + ); + } + + Frames.Add(positions); + } + } + + public override void OutputInfo(ILogger logger) + { + base.OutputInfo(logger); + logger.LogInfo("-----"); + logger.LogInfo("LegacyMeshAnimatedVertices: Frame count: " + Frames.Count); + } + } +} diff --git a/LanternExtractor/EQ/Wld/Fragments/Mesh.cs b/LanternExtractor/EQ/Wld/Fragments/Mesh.cs index f8d7494..669ba5b 100644 --- a/LanternExtractor/EQ/Wld/Fragments/Mesh.cs +++ b/LanternExtractor/EQ/Wld/Fragments/Mesh.cs @@ -45,7 +45,7 @@ public class Mesh : WldFragment /// The vertices of the mesh /// public List Vertices { get; set; } - + /// /// The normals of the mesh /// @@ -55,7 +55,7 @@ public class Mesh : WldFragment /// The polygon indices of the mesh /// public List Indices { get; private set; } - + public List Colors { get; set; } /// @@ -123,9 +123,9 @@ public override void Initialize(int index, int size, byte[] data, // Seems to be related to lighting models? (torches, etc.) if (unknownDword1 != 0 || unknownDword2 != 0 || unknownDword3 != 0) { - + } - + MaxDistance = Reader.ReadSingle(); MinPosition = new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle()); MaxPosition = new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle()); @@ -145,7 +145,7 @@ public override void Initialize(int index, int size, byte[] data, short vertexTextureCount = Reader.ReadInt16(); short size9 = Reader.ReadInt16(); float scale = 1.0f / (1 << Reader.ReadInt16()); - + for (int i = 0; i < vertexCount; ++i) { Vertices.Add(new vec3(Reader.ReadInt16() * scale, Reader.ReadInt16() * scale, @@ -156,7 +156,7 @@ public override void Initialize(int index, int size, byte[] data, { if (isNewWldFormat) { - TextureUvCoordinates.Add(new vec2(Reader.ReadInt32() / 256.0f, Reader.ReadInt32() / 256.0f)); + TextureUvCoordinates.Add(new vec2(Reader.ReadSingle(), Reader.ReadSingle())); } else { @@ -179,7 +179,7 @@ public override void Initialize(int index, int size, byte[] data, int g = colorBytes[1]; int r = colorBytes[2]; int a = colorBytes[3]; - + Colors.Add(new Color( r, g, b, a)); } @@ -202,7 +202,7 @@ public override void Initialize(int index, int size, byte[] data, Vertex3 = Reader.ReadInt16(), }); } - + MobPieces = new Dictionary(); int mobStart = 0; @@ -224,7 +224,7 @@ public override void Initialize(int index, int size, byte[] data, MaterialGroups = new List(); StartTextureIndex = Int32.MaxValue; - + for (int i = 0; i < polygonTextureCount; ++i) { var group = new RenderGroup(); @@ -247,7 +247,7 @@ public override void Initialize(int index, int size, byte[] data, { Reader.BaseStream.Position += 12; } - + // In some rare cases, the number of uvs does not match the number of vertices if (Vertices.Count != TextureUvCoordinates.Count) { @@ -291,4 +291,4 @@ public void ClearCollision() ExportSeparateCollision = true; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVertices.cs b/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVertices.cs index 7222e47..cab95a6 100644 --- a/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVertices.cs +++ b/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVertices.cs @@ -10,17 +10,17 @@ namespace LanternExtractor.EQ.Wld.Fragments /// Contains a list of frames each containing a position for each vertex. /// The frame vertices are cycled through, animating the model. /// - public class MeshAnimatedVertices : WldFragment + public class MeshAnimatedVertices : WldFragment, IAnimatedVertices { /// /// The model frames /// - public List> Frames { get; private set; } + public List> Frames { get; set; } /// /// The delay between the vertex swaps /// - public int Delay { get; private set; } + public int Delay { get; set; } public override void Initialize(int index, int size, byte[] data, List fragments, @@ -62,4 +62,4 @@ public override void OutputInfo(ILogger logger) logger.LogInfo("MeshAnimatedVertices: Frame count: " + Frames.Count); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVerticesReference.cs b/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVerticesReference.cs index bb41dad..c682d53 100644 --- a/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVerticesReference.cs +++ b/LanternExtractor/EQ/Wld/Fragments/MeshAnimatedVerticesReference.cs @@ -6,27 +6,36 @@ namespace LanternExtractor.EQ.Wld.Fragments /// /// MeshAnimatedVerticesReference (0x2F) /// Internal name: None - /// References a MeshAnimatedVertices fragment. + /// References a LegacyMeshAnimatedVertices or MeshAnimatedVertices fragment. /// This fragment is referenced from the Mesh fragment, if it's animated. /// public class MeshAnimatedVerticesReference : WldFragment { + public LegacyMeshAnimatedVertices LegacyMeshAnimatedVertices { get; set; } public MeshAnimatedVertices MeshAnimatedVertices { get; set; } - + public override void Initialize(int index, int size, byte[] data, List fragments, Dictionary stringHash, bool isNewWldFormat, ILogger logger) { base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); Name = stringHash[-Reader.ReadInt32()]; - MeshAnimatedVertices = fragments[Reader.ReadInt32() -1] as MeshAnimatedVertices; + + var fragmentId = Reader.ReadInt32() - 1; + MeshAnimatedVertices = fragments[fragmentId] as MeshAnimatedVertices; + LegacyMeshAnimatedVertices = fragments[fragmentId] as LegacyMeshAnimatedVertices; int flags = Reader.ReadInt32(); } - + + public IAnimatedVertices GetAnimatedVertices() + { + return MeshAnimatedVertices as IAnimatedVertices ?? LegacyMeshAnimatedVertices; + } + public override void OutputInfo(ILogger logger) { base.OutputInfo(logger); logger.LogInfo("-----"); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/ObjectInstance.cs b/LanternExtractor/EQ/Wld/Fragments/ObjectInstance.cs index b0c6e35..60b0ff0 100644 --- a/LanternExtractor/EQ/Wld/Fragments/ObjectInstance.cs +++ b/LanternExtractor/EQ/Wld/Fragments/ObjectInstance.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using GlmSharp; using LanternExtractor.Infrastructure.Logger; @@ -95,4 +94,4 @@ public override void OutputInfo(ILogger logger) logger.LogInfo($"{GetType()}: Scale: " + Scale); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/ParticleCloud.cs b/LanternExtractor/EQ/Wld/Fragments/ParticleCloud.cs index fc98d53..67a602c 100644 --- a/LanternExtractor/EQ/Wld/Fragments/ParticleCloud.cs +++ b/LanternExtractor/EQ/Wld/Fragments/ParticleCloud.cs @@ -22,28 +22,28 @@ public override void Initialize(int index, int size, byte[] data, List + /// Polyhedron (0x17) + /// Internal Name: _POLYHDEF + /// + public class Polyhedron : WldFragment + { + // POLYHEDRONDEFINITION + // BOUNDINGRADIUS %f + // SCALEFACTOR %f + // NUMVERTICES %d + // XYZ %f %f %f + // NUMFACES %d + // FACE + // NORMALABCD %f %f %f %f + // NUMVERTICES %d + // VERTEXLIST %d ...%d + // ENDFACE + // ENDPOLYHEDRONDEFINITION + + public float BoundingRadius { get; set; } + public float ScaleFactor { get; set; } + public List Vertices { get; set; } + public List Faces { get; set; } + + public override void Initialize(int index, int size, byte[] data, + List fragments, + Dictionary stringHash, bool isNewWldFormat, ILogger logger) + { + base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); + Name = stringHash[-Reader.ReadInt32()]; + int flags = Reader.ReadInt32(); + + var ba = new BitAnalyzer(flags); + var hasScaleFactor = ba.IsBitSet(0); + var hasNormalAbcd = ba.IsBitSet(1); + + int vertexCount = Reader.ReadInt32(); + int faceCount = Reader.ReadInt32(); + float boundingRadius = Reader.ReadSingle(); + + if (hasScaleFactor) + { + ScaleFactor = Reader.ReadSingle(); + } + else + { + ScaleFactor = 1.0f; + } + + Vertices = new List(); + for (var i = 0; i < vertexCount; i++) + { + var vertex = new vec3(Reader.ReadSingle(), Reader.ReadSingle(), Reader.ReadSingle()); + Vertices.Add(vertex); + } + + Faces = new List(); + for (var i = 0; i < faceCount; i++) + { + var faceVertexCount = Reader.ReadInt32(); + var faceVertices = new List(); + for (var v = 0; v < faceVertexCount; v++) + { + faceVertices.Add(Reader.ReadInt32()); + } + + if (hasNormalAbcd) + { + var normalAbcd = new vec4( + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle(), + Reader.ReadSingle() + ); + } + + // 4 vertices will result in 2 triangles + var polygonCount = faceVertexCount - 2; + for (var f = 0; f < polygonCount; f++) + { + var polygon = new Polygon + { + IsSolid = true, + Vertex1 = faceVertices[0], + Vertex2 = faceVertices[f + 1], + Vertex3 = faceVertices[f + 2], + }; + Faces.Add(polygon); + } + } + } + } +} diff --git a/LanternExtractor/EQ/Wld/Fragments/Fragment18.cs b/LanternExtractor/EQ/Wld/Fragments/PolyhedronReference.cs similarity index 74% rename from LanternExtractor/EQ/Wld/Fragments/Fragment18.cs rename to LanternExtractor/EQ/Wld/Fragments/PolyhedronReference.cs index 41efada..f3106c8 100644 --- a/LanternExtractor/EQ/Wld/Fragments/Fragment18.cs +++ b/LanternExtractor/EQ/Wld/Fragments/PolyhedronReference.cs @@ -1,31 +1,31 @@ -using System.Collections.Generic; +using System.Collections.Generic; using LanternExtractor.Infrastructure.Logger; namespace LanternExtractor.EQ.Wld.Fragments { /// - /// Fragment18 (0x18) - PolygonAnimationReference? + /// PolyhedronReference (0x18) /// Internal Name: None /// Need to figure this fragment out. /// - public class Fragment18 : WldFragment + public class PolyhedronReference : WldFragment { - public Fragment17 Fragment17; - + public Polyhedron Polyhedron; + public override void Initialize(int index, int size, byte[] data, List fragments, Dictionary stringHash, bool isNewWldFormat, ILogger logger) { base.Initialize(index, size, data, fragments, stringHash, isNewWldFormat, logger); Name = stringHash[-Reader.ReadInt32()]; - Fragment17 = fragments[Reader.ReadInt32() - 1] as Fragment17; + Polyhedron = fragments[Reader.ReadInt32() - 1] as Polyhedron; float params1 = Reader.ReadSingle(); } - + public override void OutputInfo(ILogger logger) { base.OutputInfo(logger); logger.LogInfo("-----"); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/SkeletonHierarchy.cs b/LanternExtractor/EQ/Wld/Fragments/SkeletonHierarchy.cs index 781f44b..6ff962f 100644 --- a/LanternExtractor/EQ/Wld/Fragments/SkeletonHierarchy.cs +++ b/LanternExtractor/EQ/Wld/Fragments/SkeletonHierarchy.cs @@ -20,7 +20,7 @@ public class SkeletonHierarchy : WldFragment public List AlternateMeshes { get; private set; } public List Skeleton { get; set; } - private Fragment18 _fragment18Reference; + private PolyhedronReference _fragment18Reference; public string ModelBase { get; set; } public bool IsAssigned { get; set; } @@ -34,6 +34,7 @@ public class SkeletonHierarchy : WldFragment public float BoundingRadius; public List SecondaryMeshes = new List(); + public List SecondaryAlternateMeshes = new List(); private bool _hasBuiltData; @@ -71,7 +72,7 @@ public override void Initialize(int index, int size, byte[] data, if (fragment18Reference > 0) { - _fragment18Reference = fragments[fragment18Reference] as Fragment18; + _fragment18Reference = fragments[fragment18Reference] as PolyhedronReference; } // Three sequential DWORDs @@ -92,7 +93,7 @@ public override void Initialize(int index, int size, byte[] data, // An index into the string has to get this bone's name int boneNameIndex = Reader.ReadInt32(); string boneName = string.Empty; - + if (stringHash.ContainsKey(-boneNameIndex)) { boneName = stringHash[-boneNameIndex]; @@ -111,17 +112,17 @@ public override void Initialize(int index, int size, byte[] data, var pieceNew = new SkeletonBone { - Index = i, - Track = track, + Index = i, + Track = track, Name = boneName }; - + pieceNew.Track.IsPoseAnimation = true; pieceNew.AnimationTracks = new Dictionary(); - + BoneMappingClean[i] = Animation.CleanBoneAndStripBase(boneName, ModelBase); BoneMapping[i] = boneName; - + if (pieceNew.Track == null) { logger.LogError("Unable to link track reference!"); @@ -216,8 +217,8 @@ public void BuildSkeletonData(bool stripModelBase) { return; } - - BuildSkeletonTreeData(0, Skeleton, null, string.Empty, + + BuildSkeletonTreeData(0, Skeleton, null, string.Empty, string.Empty, string.Empty, stripModelBase); _hasBuiltData = true; } @@ -375,14 +376,14 @@ public void AddTrackData(TrackFragment track, bool isDefault = false) track.IsProcessed = true; } - private void BuildSkeletonTreeData(int index, List treeNodes, SkeletonBone parent, + private void BuildSkeletonTreeData(int index, List treeNodes, SkeletonBone parent, string runningName, string runningNameCleaned, string runningIndex, bool stripModelBase) { SkeletonBone bone = treeNodes[index]; bone.Parent = parent; bone.CleanedName = CleanBoneName(bone.Name, stripModelBase); BoneMappingClean[index] = bone.CleanedName; - + if (bone.Name != string.Empty) { runningIndex += bone.Index + "/"; @@ -424,12 +425,12 @@ private string CleanBoneName(string nodeName, bool stripModelBase) public void AddAdditionalMesh(Mesh mesh) { - if (Meshes.Any(x => x.Name == mesh.Name) + if (Meshes.Any(x => x.Name == mesh.Name) || SecondaryMeshes.Any(x => x.Name == mesh.Name)) { return; } - + if (mesh.MobPieces.Count == 0) { return; @@ -439,6 +440,23 @@ public void AddAdditionalMesh(Mesh mesh) SecondaryMeshes = SecondaryMeshes.OrderBy(x => x.Name).ToList(); } + public void AddAdditionalAlternateMesh(LegacyMesh mesh) + { + if (AlternateMeshes.Any(x => x.Name == mesh.Name) + || SecondaryAlternateMeshes.Any(x => x.Name == mesh.Name)) + { + return; + } + + if (mesh.MobPieces.Count == 0) + { + return; + } + + SecondaryAlternateMeshes.Add(mesh); + SecondaryAlternateMeshes = SecondaryAlternateMeshes.OrderBy(x => x.Name).ToList(); + } + public bool IsValidSkeleton(string trackName, out string boneName) { string track = trackName.Substring(3); @@ -476,7 +494,7 @@ public mat4 GetBoneMatrix(int boneIndex, string animName, int frame) } var currentBone = Skeleton[boneIndex]; - + mat4 boneMatrix = mat4.Identity; while (currentBone != null) @@ -485,16 +503,16 @@ public mat4 GetBoneMatrix(int boneIndex, string animName, int frame) { break; } - + var track = Animations[animName].TracksCleanedStripped[currentBone.CleanedName].TrackDefFragment; int realFrame = frame >= track.Frames.Count ? 0 : frame; currentBone = Skeleton[boneIndex].Parent; - + float scaleValue = track.Frames[realFrame].Scale; var scaleMat = mat4.Scale(scaleValue, scaleValue, scaleValue); - + var rotationMatrix = new mat4(track.Frames[realFrame].Rotation); - + var translation = track.Frames[realFrame].Translation; var translateMat = mat4.Translate(translation); @@ -529,4 +547,4 @@ public void RenameNodeBase(string newBase) ModelBase = newBase; } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Fragments/TrackDefFragment.cs b/LanternExtractor/EQ/Wld/Fragments/TrackDefFragment.cs index a05dc34..42d4621 100644 --- a/LanternExtractor/EQ/Wld/Fragments/TrackDefFragment.cs +++ b/LanternExtractor/EQ/Wld/Fragments/TrackDefFragment.cs @@ -19,7 +19,7 @@ public class TrackDefFragment : WldFragment /// A list of bone positions for each frame /// public List Frames { get; set; } - + public bool IsAssigned; public override void Initialize(int index, int size, byte[] data, @@ -36,46 +36,72 @@ public override void Initialize(int index, int size, byte[] data, // Flags are always 8 when dealing with object animations if (flags != 8) { - + } BitAnalyzer bitAnalyzer = new BitAnalyzer(flags); - bool hasData2Values = bitAnalyzer.IsBitSet(3); - + bool isS3dTrack2 = bitAnalyzer.IsBitSet(3); + int frameCount = Reader.ReadInt32(); - + Frames = new List(); - for (int i = 0; i < frameCount; ++i) + if (isS3dTrack2) { - Int16 rotDenominator = Reader.ReadInt16(); - Int16 rotX = Reader.ReadInt16(); - Int16 rotY = Reader.ReadInt16(); - Int16 rotZ = Reader.ReadInt16(); - Int16 shiftX = Reader.ReadInt16(); - Int16 shiftY = Reader.ReadInt16(); - Int16 shiftZ = Reader.ReadInt16(); - Int16 shiftDenominator = Reader.ReadInt16(); - - BoneTransform frameTransform = new BoneTransform(); - - if (shiftDenominator != 0) + for (int i = 0; i < frameCount; ++i) { - float x = shiftX / 256f; - float y = shiftY / 256f; - float z = shiftZ / 256f; + Int16 rotDenominator = Reader.ReadInt16(); + Int16 rotX = Reader.ReadInt16(); + Int16 rotY = Reader.ReadInt16(); + Int16 rotZ = Reader.ReadInt16(); + Int16 shiftX = Reader.ReadInt16(); + Int16 shiftY = Reader.ReadInt16(); + Int16 shiftZ = Reader.ReadInt16(); + Int16 shiftDenominator = Reader.ReadInt16(); + + BoneTransform frameTransform = new BoneTransform(); + + if (shiftDenominator != 0) + { + float x = shiftX / 256f; + float y = shiftY / 256f; + float z = shiftZ / 256f; - frameTransform.Scale = shiftDenominator / 256f; - frameTransform.Translation = new vec3(x, y, z); + frameTransform.Scale = shiftDenominator / 256f; + frameTransform.Translation = new vec3(x, y, z); + } + else + { + frameTransform.Translation = vec3.Zero; + } + + frameTransform.Rotation = new quat(rotX, rotY, rotZ, rotDenominator).Normalized; + Frames.Add(frameTransform); } - else + } + else + { + for (int i = 0; i < frameCount; ++i) { - frameTransform.Translation = vec3.Zero; + var shiftDenominator = Reader.ReadSingle(); + var shiftX = Reader.ReadSingle(); + var shiftY = Reader.ReadSingle(); + var shiftZ = Reader.ReadSingle(); + var rotW = Reader.ReadSingle(); + var rotX = Reader.ReadSingle(); + var rotY = Reader.ReadSingle(); + var rotZ = Reader.ReadSingle(); + + var frameTransform = new BoneTransform() + { + Scale = shiftDenominator, + Translation = new vec3(shiftX, shiftY, shiftZ), + Rotation = new quat(rotX, rotY, rotZ, rotW).Normalized, + }; + + Frames.Add(frameTransform); } - - frameTransform.Rotation = new quat(rotX, rotY, rotZ, rotDenominator).Normalized; - Frames.Add(frameTransform); } } @@ -86,4 +112,4 @@ public override void OutputInfo(ILogger logger) logger.LogInfo("0x12: Bone frame count: " + Frames.Count); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Helpers/CharacterFixer.cs b/LanternExtractor/EQ/Wld/Helpers/CharacterFixer.cs index 5f63067..06d471b 100644 --- a/LanternExtractor/EQ/Wld/Helpers/CharacterFixer.cs +++ b/LanternExtractor/EQ/Wld/Helpers/CharacterFixer.cs @@ -29,6 +29,7 @@ public void Fix(WldFileCharacters wld) FixBlackAndWhiteDragon(); FixGhoulTextures(); FixHalasFemale(); + FixBetaBeetle(); FixHighpassMale(); } @@ -292,7 +293,7 @@ private void FixShipNames() actor?.MeshReference?.Mesh?.MaterialList?.Name?.Replace("GHOSTSHIP", "GSP") ?? "GSP"; } - if (actor.Name.StartsWith("LAUNCH")) + if (actor.Name.StartsWith("LAUNCH") && (actor?.MeshReference?.Mesh?.Name ?? null) != null) { actor.Name = actor.MeshReference.Mesh.Name.Replace("DMSPRITEDEF", "ACTORDEF"); } @@ -384,13 +385,32 @@ private void FixHalasFemale() return; } - skeleton.BoneMappingClean[7] = "bi_l"; - skeleton.BoneMappingClean[10] = "l_point"; - skeleton.BoneMappingClean[15] = "head_point"; + RenameBone(skeleton, 7, "bi_l"); + RenameBone(skeleton, 10, "l_point"); + RenameBone(skeleton, 15, "head_point"); + } + + /// + /// Early beta beetles have SPI named bones + /// + private void FixBetaBeetle() + { + var skeleton = _wld.GetFragmentByName("BET_HS_DEF"); - skeleton.Skeleton[7].CleanedName = "bi_l"; - skeleton.Skeleton[10].CleanedName = "l_point"; - skeleton.Skeleton[15].CleanedName = "head_point"; + if (skeleton == null) + { + return; + } + + for (var i = 0; i < skeleton.Skeleton.Count; i++) + { + var bone = skeleton.Skeleton[i]; + var boneName = bone.CleanedName; + if (boneName.StartsWith("spi")) + { + RenameBone(skeleton, i, boneName.Substring(3)); + } + } } /// @@ -406,6 +426,11 @@ private void FixHighpassMale() return; } + if (skeleton.Skeleton.Count < 25) + { + return; + } + skeleton.Skeleton[0].Children = new List { 1 @@ -413,5 +438,24 @@ private void FixHighpassMale() skeleton.Skeleton.RemoveAt(24); } + + private void RenameBone(SkeletonHierarchy skeleton, int index, string newBoneName) + { + var oldBoneName = skeleton.BoneMappingClean[index]; + + skeleton.BoneMappingClean[index] = newBoneName; + skeleton.Skeleton[index].CleanedName = newBoneName; + + for (var i = 0; i < skeleton.Skeleton.Count; i++) + { + var fullPath = skeleton.Skeleton[i].CleanedFullPath; + if (!fullPath.Contains(oldBoneName)) + { + continue; + } + + skeleton.Skeleton[i].CleanedFullPath = fullPath.Replace(oldBoneName, newBoneName); + } + } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Helpers/FragmentNameCleaner.cs b/LanternExtractor/EQ/Wld/Helpers/FragmentNameCleaner.cs index 8df4857..ac2cf36 100644 --- a/LanternExtractor/EQ/Wld/Helpers/FragmentNameCleaner.cs +++ b/LanternExtractor/EQ/Wld/Helpers/FragmentNameCleaner.cs @@ -28,13 +28,13 @@ public static string CleanName(WldFragment fragment, bool toLower = true) { cleanedName = cleanedName.Replace(_prefixes[fragment.GetType()], string.Empty); } - + if(toLower) { cleanedName = cleanedName.ToLower(); } - - return cleanedName; + + return cleanedName.Trim(); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/Helpers/MaterialFixer.cs b/LanternExtractor/EQ/Wld/Helpers/MaterialFixer.cs index 6bfffdf..48fd64a 100644 --- a/LanternExtractor/EQ/Wld/Helpers/MaterialFixer.cs +++ b/LanternExtractor/EQ/Wld/Helpers/MaterialFixer.cs @@ -11,24 +11,47 @@ public static class MaterialFixer { public static void Fix(WldFile wld) { - FixShaderAssignment(wld, "TREE20_MDF", ShaderType.TransparentMasked); - FixShaderAssignment(wld, "TOP_MDF", ShaderType.TransparentMasked); - FixShaderAssignment(wld, "FURPILE1_MDF", ShaderType.TransparentMasked); - FixShaderAssignment(wld, "BEARRUG_MDF", ShaderType.TransparentMasked); - FixShaderAssignment(wld, "FIRE1_MDF", ShaderType.TransparentAdditiveUnlit); - FixShaderAssignment(wld, "ICE1_MDF", ShaderType.Invisible); - FixShaderAssignment(wld, "AIRCLOUD_MDF", ShaderType.TransparentSkydome); - FixShaderAssignment(wld, "NORMALCLOUD_MDF", ShaderType.TransparentSkydome); - } - - private static void FixShaderAssignment(WldFile wld, string materialName, ShaderType shader) - { - var material = wld.GetFragmentByName(materialName); + var materials = wld.GetFragmentsOfType(); - if (material != null) + foreach (var material in materials) { - material.ShaderType = shader; + switch (material.Name) + { + case "TREE7_MDF": + case "TREE9B1_MDF": + case "TREE16_MDF": + case "TREE16B1_MDF": + case "TREE17_MDF": + case "TREE18_MDF": + case "TREE18B1_MDF": + case "TREE20_MDF": + case "TREE20B1_MDF": + case "TREE21_MDF": + case "TREE22_MDF": + case "TREE22B1_MDF": + case "TOP_MDF": + case "TOPB_MDF": + case "FURPILE1_MDF": + case "BEARRUG_MDF": + FixShaderAssignment(material, ShaderType.TransparentMasked); + break; + case "FIRE1_MDF": + FixShaderAssignment(material, ShaderType.TransparentAdditiveUnlit); + break; + case "ICE1_MDF": + FixShaderAssignment(material, ShaderType.Invisible); + break; + case "AIRCLOUD_MDF": + case "NORMALCLOUD_MDF": + FixShaderAssignment(material, ShaderType.TransparentSkydome); + break; + } } } + + private static void FixShaderAssignment(Material material, ShaderType shader) + { + material.ShaderType = shader; + } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/WldFile.cs b/LanternExtractor/EQ/Wld/WldFile.cs index 98394b2..2966080 100644 --- a/LanternExtractor/EQ/Wld/WldFile.cs +++ b/LanternExtractor/EQ/Wld/WldFile.cs @@ -2,13 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.DataTypes; using LanternExtractor.EQ.Wld.Exporters; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.EQ.Wld.Helpers; using LanternExtractor.Infrastructure.Logger; -using LanternExtractor.EQ; namespace LanternExtractor.EQ.Wld { @@ -18,20 +17,17 @@ namespace LanternExtractor.EQ.Wld public abstract class WldFile { public string RootExportFolder; - public string ZoneShortname => _zoneName; - - - public WldType WldType => _wldType; + public string ZoneShortname => ZoneName; /// - /// The link between fragment types and fragment classes + /// The type of WLD file this is /// - private Dictionary> _fragmentBuilder; + public WldType WldType { get; } /// /// A link of indices to fragments /// - protected List _fragments; + protected List Fragments; /// /// The string has containing the index in the hash and the decoded string that is there @@ -42,46 +38,41 @@ public abstract class WldFile /// A collection of fragment lists that can be referenced by a fragment type /// //protected Dictionary> _fragmentTypeDictionary; - protected Dictionary> _fragmentTypeDictionary; + protected Dictionary> FragmentTypeDictionary; /// /// A collection of fragment lists that can be referenced by a fragment type /// - protected Dictionary _fragmentNameDictionary; + protected Dictionary FragmentNameDictionary; - protected List _bspRegions; + protected List BspRegions; /// /// The shortname of the zone this WLD is from /// - protected readonly string _zoneName; + protected readonly string ZoneName; /// /// The logger to use to output WLD information /// - protected readonly ILogger _logger; - - /// - /// The type of WLD file this is - /// - protected readonly WldType _wldType; + protected readonly ILogger Logger; /// - /// The WLD file found in the PFS archive + /// The WLD file found in the archive /// - private readonly PfsFile _wldFile; + private readonly ArchiveFile _wldFile; /// /// Cached settings /// - protected readonly Settings _settings; + protected readonly Settings Settings; /// /// Is this the new WLD format? Some data types are different /// private bool _isNewWldFormat; - protected readonly WldFile _wldToInject; + protected readonly WldFile WldToInject; public Dictionary FilenameChanges = new Dictionary(); @@ -93,15 +84,15 @@ public abstract class WldFile /// The shortname of the zone /// The type of WLD - used to determine what to extract /// The logger used for debug output - protected WldFile(PfsFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, + protected WldFile(ArchiveFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile fileToInject) { _wldFile = wldFile; - _zoneName = zoneName.ToLower(); - _wldType = type; - _logger = logger; - _settings = settings; - _wldToInject = fileToInject; + ZoneName = zoneName.ToLower(); + WldType = type; + Logger = logger; + Settings = settings; + WldToInject = fileToInject; } /// @@ -110,14 +101,14 @@ protected WldFile(PfsFile wldFile, string zoneName, WldType type, ILogger logger public virtual bool Initialize(string rootFolder, bool exportData = true) { RootExportFolder = rootFolder; - _logger.LogInfo("Extracting WLD archive: " + _wldFile.Name); - _logger.LogInfo("-----------------------------------"); - _logger.LogInfo("WLD type: " + _wldType); + Logger.LogInfo("Extracting WLD archive: " + _wldFile.Name); + Logger.LogInfo("-----------------------------------"); + Logger.LogInfo("WLD type: " + WldType); - _fragments = new List(); - _fragmentTypeDictionary = new Dictionary>(); - _fragmentNameDictionary = new Dictionary(); - _bspRegions = new List(); + Fragments = new List(); + FragmentTypeDictionary = new Dictionary>(); + FragmentNameDictionary = new Dictionary(); + BspRegions = new List(); var reader = new BinaryReader(new MemoryStream(_wldFile.Bytes)); @@ -129,7 +120,7 @@ public virtual bool Initialize(string rootFolder, bool exportData = true) if (identifier != WldIdentifier.WldFileIdentifier) { - _logger.LogError("Not a valid WLD file!"); + Logger.LogError("Not a valid WLD file!"); return false; } @@ -141,10 +132,10 @@ public virtual bool Initialize(string rootFolder, bool exportData = true) break; case WldIdentifier.WldFormatNewIdentifier: _isNewWldFormat = true; - _logger.LogWarning("New WLD format not fully supported."); + Logger.LogWarning("New WLD format not fully supported."); break; default: - _logger.LogError("Unrecognized WLD format."); + Logger.LogError("Unrecognized WLD format."); return false; } @@ -159,8 +150,6 @@ public virtual bool Initialize(string rootFolder, bool exportData = true) ParseStringHash(WldStringDecoder.DecodeString(stringHash)); - long readPosition = 0; - for (int i = 0; i < fragmentCount; ++i) { uint fragSize = reader.ReadUInt32(); @@ -172,31 +161,31 @@ public virtual bool Initialize(string rootFolder, bool exportData = true) if (newFragment is Generic) { - _logger.LogWarning($"WldFile: Unhandled fragment type: {fragId:x}"); + Logger.LogWarning($"WldFile: Unhandled fragment type: {fragId:x}"); } - newFragment.Initialize(i, (int)fragSize, reader.ReadBytes((int)fragSize), _fragments, _stringHash, + newFragment.Initialize(i, (int)fragSize, reader.ReadBytes((int)fragSize), Fragments, _stringHash, _isNewWldFormat, - _logger); - newFragment.OutputInfo(_logger); + Logger); + newFragment.OutputInfo(Logger); - _fragments.Add(newFragment); + Fragments.Add(newFragment); - if (!_fragmentTypeDictionary.ContainsKey(newFragment.GetType())) + if (!FragmentTypeDictionary.ContainsKey(newFragment.GetType())) { - _fragmentTypeDictionary[newFragment.GetType()] = new List(); + FragmentTypeDictionary[newFragment.GetType()] = new List(); } - if (!string.IsNullOrEmpty(newFragment.Name) && !_fragmentNameDictionary.ContainsKey(newFragment.Name)) + if (!string.IsNullOrEmpty(newFragment.Name) && !FragmentNameDictionary.ContainsKey(newFragment.Name)) { - _fragmentNameDictionary[newFragment.Name] = newFragment; + FragmentNameDictionary[newFragment.Name] = newFragment; } - _fragmentTypeDictionary[newFragment.GetType()].Add(newFragment); + FragmentTypeDictionary[newFragment.GetType()].Add(newFragment); } - _logger.LogInfo("-----------------------------------"); - _logger.LogInfo("WLD extraction complete"); + Logger.LogInfo("-----------------------------------"); + Logger.LogInfo("WLD extraction complete"); ProcessData(); @@ -210,22 +199,22 @@ public virtual bool Initialize(string rootFolder, bool exportData = true) public List GetFragmentsOfType() where T : WldFragment { - if (!_fragmentTypeDictionary.ContainsKey(typeof(T))) + if (!FragmentTypeDictionary.ContainsKey(typeof(T))) { return new List(); } - return _fragmentTypeDictionary[typeof(T)].Cast().ToList(); + return FragmentTypeDictionary[typeof(T)].Cast().ToList(); } public T GetFragmentByName(string fragmentName) where T : WldFragment { - if (!_fragmentNameDictionary.ContainsKey(fragmentName)) + if (!FragmentNameDictionary.ContainsKey(fragmentName)) { return default(T); } - return _fragmentNameDictionary[fragmentName] as T; + return FragmentNameDictionary[fragmentName] as T; } protected virtual void ProcessData() @@ -257,7 +246,7 @@ public virtual void ExportData() { ExportMeshes(); - if (_settings.ModelExportFormat == ModelExportFormat.Intermediate) + if (Settings.ModelExportFormat == ModelExportFormat.Intermediate) { ExportActors(); ExportSkeletonAndAnimations(); @@ -274,7 +263,7 @@ public List GetMaskedBitmaps() if (materialLists.Count == 0) { - _logger.LogWarning("Cannot get material types. No texture list found."); + Logger.LogWarning("Cannot get material types. No texture list found."); return null; } @@ -311,37 +300,37 @@ public List GetMaskedBitmaps() private void ExportMeshes() { - if (_settings.ModelExportFormat == ModelExportFormat.Intermediate) + if (Settings.ModelExportFormat == ModelExportFormat.Intermediate) { - MeshExporter.ExportMeshes(this, _settings, _logger); + MeshExporter.ExportMeshes(this, Settings, Logger); } - else if (_settings.ModelExportFormat == ModelExportFormat.Obj) + else if (Settings.ModelExportFormat == ModelExportFormat.Obj) { - ActorObjExporter.ExportActors(this, _settings, _logger); + ActorObjExporter.ExportActors(this, Settings, Logger); } else { - ActorGltfExporter.ExportActors(this, _settings, _logger); + ActorGltfExporter.ExportActors(this, Settings, Logger); } } public string GetExportFolderForWldType() { - switch (_wldType) + switch (WldType) { - case WldType.Zone: - case WldType.Lights: - case WldType.ZoneObjects: + case Wld.WldType.Zone: + case Wld.WldType.Lights: + case Wld.WldType.ZoneObjects: return GetRootExportFolder() + "/Zone/"; - case WldType.Equipment: + case Wld.WldType.Equipment: return GetRootExportFolder(); - case WldType.Objects: + case Wld.WldType.Objects: return GetRootExportFolder() + "Objects/"; - case WldType.Sky: + case Wld.WldType.Sky: return GetRootExportFolder(); - case WldType.Characters: - if (_settings.ExportCharactersToSingleFolder && - _settings.ModelExportFormat == ModelExportFormat.Intermediate) + case Wld.WldType.Characters: + if (Settings.ExportCharactersToSingleFolder && + Settings.ModelExportFormat == ModelExportFormat.Intermediate) { return GetRootExportFolder(); } @@ -356,16 +345,16 @@ public string GetExportFolderForWldType() protected string GetRootExportFolder() { - switch (_wldType) + switch (WldType) { - case WldType.Equipment when _settings.ExportEquipmentToSingleFolder && - _settings.ModelExportFormat == ModelExportFormat.Intermediate: + case Wld.WldType.Equipment when Settings.ExportEquipmentToSingleFolder && + Settings.ModelExportFormat == ModelExportFormat.Intermediate: return RootExportFolder + "equipment/"; - case WldType.Characters when (_settings.ExportCharactersToSingleFolder && - _settings.ModelExportFormat == ModelExportFormat.Intermediate): + case Wld.WldType.Characters when (Settings.ExportCharactersToSingleFolder && + Settings.ModelExportFormat == ModelExportFormat.Intermediate): return RootExportFolder + "characters/"; default: - return RootExportFolder + ShortnameHelper.GetCorrectZoneShortname(_zoneName) + "/"; + return RootExportFolder + ShortnameHelper.GetCorrectZoneShortname(ZoneName) + "/"; } } @@ -373,9 +362,9 @@ private void ExportActors() { var actors = GetFragmentsOfType(); - if (_wldToInject != null) + if (WldToInject != null) { - actors.AddRange(_wldToInject.GetFragmentsOfType()); + actors.AddRange(WldToInject.GetFragmentsOfType()); } if (actors.Count == 0) @@ -385,9 +374,9 @@ private void ExportActors() TextAssetWriter actorWriterStatic, actorWriterSkeletal, actorWriterParticle, actorWriterSprite2d; - if (_wldType == WldType.Equipment && _settings.ExportEquipmentToSingleFolder || _wldType == WldType.Characters) + if (WldType == Wld.WldType.Equipment && Settings.ExportEquipmentToSingleFolder || WldType == Wld.WldType.Characters) { - bool isCharacters = _wldType == WldType.Characters; + bool isCharacters = WldType == Wld.WldType.Characters; actorWriterStatic = new ActorWriterNewGlobal(ActorType.Static, GetExportFolderForWldType()); actorWriterSkeletal = new ActorWriterNewGlobal(ActorType.Skeletal, GetExportFolderForWldType()); actorWriterParticle = new ActorWriterNewGlobal(ActorType.Particle, GetExportFolderForWldType()); @@ -425,23 +414,23 @@ protected void ExportSkeletonAndAnimations() if (skeletons.Count == 0) { - if (_wldToInject == null) + if (WldToInject == null) { - _logger.LogWarning("Cannot export animations. No model references."); + Logger.LogWarning("Cannot export animations. No model references."); return; } - skeletons = _wldToInject.GetFragmentsOfType(); + skeletons = WldToInject.GetFragmentsOfType(); if (skeletons == null) { - _logger.LogWarning("Cannot export animations. No model references."); + Logger.LogWarning("Cannot export animations 2. No model references."); return; } } - SkeletonHierarchyWriter skeletonWriter = new SkeletonHierarchyWriter(_wldType == WldType.Characters); - AnimationWriter animationWriter = new AnimationWriter(_wldType == WldType.Characters); + SkeletonHierarchyWriter skeletonWriter = new SkeletonHierarchyWriter(WldType == Wld.WldType.Characters); + AnimationWriter animationWriter = new AnimationWriter(WldType == Wld.WldType.Characters); foreach (var skeleton in skeletons) { @@ -450,7 +439,7 @@ protected void ExportSkeletonAndAnimations() skeletonWriter.AddFragmentData(skeleton); // TODO: Put this elsewhere - what does this even do? - if (_wldType == WldType.Characters && _settings.ExportCharactersToSingleFolder) + if (WldType == Wld.WldType.Characters && Settings.ExportCharactersToSingleFolder) { if (File.Exists(filePath)) { @@ -510,10 +499,10 @@ private void BuildSkeletonData() foreach (var skeleton in skeletons) { - skeleton.BuildSkeletonData(_wldType == WldType.Characters || _settings.ModelExportFormat == ModelExportFormat.Intermediate); + skeleton.BuildSkeletonData(WldType == Wld.WldType.Characters || Settings.ModelExportFormat == ModelExportFormat.Intermediate); } - (_wldToInject as WldFileCharacters)?.BuildSkeletonData(); + (WldToInject as WldFileCharacters)?.BuildSkeletonData(); } } } diff --git a/LanternExtractor/EQ/Wld/WldFileCharacters.cs b/LanternExtractor/EQ/Wld/WldFileCharacters.cs index 48cb1f2..e7c8b28 100644 --- a/LanternExtractor/EQ/Wld/WldFileCharacters.cs +++ b/LanternExtractor/EQ/Wld/WldFileCharacters.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.EQ.Wld.Helpers; using LanternExtractor.Infrastructure; @@ -11,9 +11,9 @@ namespace LanternExtractor.EQ.Wld { public class WldFileCharacters : WldFile { - public Dictionary AnimationSources = new Dictionary(); - - public WldFileCharacters(PfsFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, + private readonly Dictionary _animationSources = new Dictionary(); + + public WldFileCharacters(ArchiveFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile wldToInject = null) : base(wldFile, zoneName, type, logger, settings, wldToInject) { ParseAnimationSources(); @@ -24,10 +24,10 @@ private void ParseAnimationSources() string filename = "ClientData/animationsources.txt"; if (!File.Exists(filename)) { - _logger.LogError("WldFileCharacters: No animationsources.txt file found."); + Logger.LogError("WldFileCharacters: No animationsources.txt file found."); return; } - + string fileText = File.ReadAllText(filename); List> parsedText = TextParser.ParseTextByDelimitedLines(fileText, ',', '#'); @@ -37,14 +37,14 @@ private void ParseAnimationSources() { continue; } - - AnimationSources[line[0].ToLower()] = line[1].ToLower(); - } + + _animationSources[line[0].ToLower()] = line[1].ToLower(); + } } - + private string GetAnimationModelLink(string modelName) { - return !AnimationSources.ContainsKey(modelName) ? modelName : AnimationSources[modelName]; + return !_animationSources.ContainsKey(modelName) ? modelName : _animationSources[modelName]; } protected override void ProcessData() @@ -54,25 +54,25 @@ protected override void ProcessData() BuildSlotMapping(); FindMaterialVariants(); - if (_settings.ExportCharactersToSingleFolder) + if (Settings.ExportCharactersToSingleFolder) { var characterFixer = new CharacterFixer(); characterFixer.Fix(this); } - + foreach (var skeleton in GetFragmentsOfType()) { - skeleton.BuildSkeletonData(_wldType == WldType.Characters); + skeleton.BuildSkeletonData(WldType == Wld.WldType.Characters); } } - + private void BuildSlotMapping() { var materialLists = GetFragmentsOfType(); foreach (var list in materialLists) { - list.BuildSlotMapping(_logger); + list.BuildSlotMapping(Logger); } } @@ -95,7 +95,7 @@ private void FindMaterialVariants() if (materialName.StartsWith(materialListModelName)) { - list.AddVariant(material, _logger); + list.AddVariant(material, Logger); } } } @@ -106,8 +106,8 @@ private void FindMaterialVariants() { continue; } - - _logger.LogWarning("WldFileCharacters: Material not assigned: " + material.Name); + + Logger.LogWarning("WldFileCharacters: Material not assigned: " + material.Name); } } @@ -122,14 +122,14 @@ private void FindAdditionalAnimationsAndMeshes() if (skeletons.Count == 0) { - if (_wldToInject == null) + if (WldToInject == null) { return; } - - skeletons = _wldToInject.GetFragmentsOfType(); + + skeletons = WldToInject.GetFragmentsOfType(); } - + if (skeletons.Count == 0) { return; @@ -146,7 +146,7 @@ private void FindAdditionalAnimationsAndMeshes() string modelBase = skeleton.ModelBase; string alternateModel = GetAnimationModelLink(modelBase); - + // TODO: Alternate model bases foreach (var track in GetFragmentsOfType()) { @@ -157,11 +157,11 @@ private void FindAdditionalAnimationsAndMeshes() if (!track.IsNameParsed) { - track.ParseTrackData(_logger); + track.ParseTrackData(Logger); } - + string trackModelBase = track.ModelName; - + if (trackModelBase != modelBase && alternateModel != trackModelBase) { continue; @@ -169,7 +169,7 @@ private void FindAdditionalAnimationsAndMeshes() skeleton.AddTrackData(track); } - + // TODO: Split to another function if(GetFragmentsOfType().Count != 0) { @@ -185,7 +185,7 @@ private void FindAdditionalAnimationsAndMeshes() string basename = cleanedName; bool endsWithNumber = char.IsDigit(cleanedName[cleanedName.Length - 1]); - + if (endsWithNumber) { int id = Convert.ToInt32(cleanedName.Substring(cleanedName.Length - 2)); @@ -199,7 +199,7 @@ private void FindAdditionalAnimationsAndMeshes() basename = cleanedName; } - + if (basename == modelBase) { skeleton.AddAdditionalMesh(mesh); @@ -215,8 +215,8 @@ private void FindAdditionalAnimationsAndMeshes() continue; } - _logger.LogWarning("WldFileCharacters: Track not assigned: " + track.Name); + Logger.LogWarning("WldFileCharacters: Track not assigned: " + track.Name); } } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/WldFileEquipment.cs b/LanternExtractor/EQ/Wld/WldFileEquipment.cs index 82cb248..db9ec11 100644 --- a/LanternExtractor/EQ/Wld/WldFileEquipment.cs +++ b/LanternExtractor/EQ/Wld/WldFileEquipment.cs @@ -1,4 +1,4 @@ -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.Exporters; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.EQ.Wld.Helpers; @@ -8,7 +8,7 @@ namespace LanternExtractor.EQ.Wld { public class WldFileEquipment : WldFile { - public WldFileEquipment(PfsFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, + public WldFileEquipment(ArchiveFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile wldToInject = null) : base(wldFile, zoneName, type, logger, settings, wldToInject) { } @@ -41,7 +41,7 @@ private void ExportParticleSystems() private void FindUnhandledSkeletons() { var skeletons = GetFragmentsOfType(); - + if (skeletons == null) { return; @@ -56,13 +56,13 @@ private void FindUnhandledSkeletons() string cleanedName = FragmentNameCleaner.CleanName(skeleton, false); string actorName = cleanedName + "_ACTORDEF"; - - if (!_fragmentNameDictionary.ContainsKey(actorName)) + + if (!FragmentNameDictionary.ContainsKey(actorName)) { continue; } - (_fragmentNameDictionary[actorName] as Actor)?.AssignSkeletonReference(skeleton, _logger); + (FragmentNameDictionary[actorName] as Actor)?.AssignSkeletonReference(skeleton, Logger); } } @@ -87,13 +87,13 @@ private void FindAdditionalAnimations() { continue; } - + foreach (var skeleton in skeletons) { string boneName = string.Empty; if (skeleton.IsValidSkeleton(FragmentNameCleaner.CleanName(track), out boneName)) { - _logger.LogError($"Assigning {track.Name} to {skeleton.Name}"); + Logger.LogError($"Assigning {track.Name} to {skeleton.Name}"); track.IsProcessed = true; skeleton.AddTrackDataEquipment(track, boneName.ToLower()); } @@ -107,8 +107,8 @@ private void FindAdditionalAnimations() continue; } - _logger.LogError("WldFileCharacters: Track not assigned: " + track.Name); + Logger.LogError("WldFileCharacters: Track not assigned: " + track.Name); } } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/WldFileLights.cs b/LanternExtractor/EQ/Wld/WldFileLights.cs index c118f1e..5da2775 100644 --- a/LanternExtractor/EQ/Wld/WldFileLights.cs +++ b/LanternExtractor/EQ/Wld/WldFileLights.cs @@ -1,4 +1,4 @@ -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.Exporters; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.Infrastructure.Logger; @@ -7,7 +7,7 @@ namespace LanternExtractor.EQ.Wld { public class WldFileLights : WldFile { - public WldFileLights(PfsFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, + public WldFileLights(ArchiveFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile wldToInject = null) : base(wldFile, zoneName, type, logger, settings, wldToInject) { } @@ -19,7 +19,7 @@ public override void ExportData() { ExportLightInstanceList(); } - + /// /// Exports the list of light instances (contains position, colors, radius) /// @@ -29,7 +29,7 @@ private void ExportLightInstanceList() if (lightInstances.Count == 0) { - _logger.LogWarning("Unable to export light instance list. No instances found."); + Logger.LogWarning("Unable to export light instance list. No instances found."); return; } @@ -39,8 +39,8 @@ private void ExportLightInstanceList() { writer.AddFragmentData(light); } - + writer.WriteAssetToFile(GetExportFolderForWldType() + "light_instances.txt"); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/WldFileZone.cs b/LanternExtractor/EQ/Wld/WldFileZone.cs index 334f0ae..95cecf4 100644 --- a/LanternExtractor/EQ/Wld/WldFileZone.cs +++ b/LanternExtractor/EQ/Wld/WldFileZone.cs @@ -1,4 +1,4 @@ -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.DataTypes; using LanternExtractor.EQ.Wld.Exporters; using LanternExtractor.EQ.Wld.Fragments; @@ -8,7 +8,7 @@ namespace LanternExtractor.EQ.Wld { public class WldFileZone : WldFile { - public WldFileZone(PfsFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, + public WldFileZone(ArchiveFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile wldToInject = null) : base(wldFile, zoneName, type, logger, settings, wldToInject) { } @@ -16,7 +16,7 @@ public WldFileZone(PfsFile wldFile, string zoneName, WldType type, ILogger logge public string BasePath { get; set; } = ""; public string RootFolder { get; set; } = ""; public string ShortName { get; set; } = ""; - public PfsArchive BaseS3DArchive { get; set; } = null; + public ArchiveBase BaseS3DArchive { get; set; } = null; public WldFile WldFileToInject { get; set; } = null; public override void ExportData() @@ -31,12 +31,12 @@ protected override void ProcessData() base.ProcessData(); LinkBspReferences(); - if (_wldToInject != null) + if (WldToInject != null) { ImportVertexColors(); } - if (_wldType == WldType.Objects) + if (WldType == Wld.WldType.Objects) { FixSkeletalObjectCollision(); } @@ -67,7 +67,7 @@ private void FixSkeletalObjectCollision() private void ImportVertexColors() { - var colors = _wldToInject.GetFragmentsOfType(); + var colors = WldToInject.GetFragmentsOfType(); if (colors.Count == 0) { @@ -137,4 +137,4 @@ private void ExportBspTree() writer.WriteAssetToFile(GetExportFolderForWldType() + "/bsp_tree.txt"); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/WldFileZoneObjects.cs b/LanternExtractor/EQ/Wld/WldFileZoneObjects.cs index 0511e2f..742ce28 100644 --- a/LanternExtractor/EQ/Wld/WldFileZoneObjects.cs +++ b/LanternExtractor/EQ/Wld/WldFileZoneObjects.cs @@ -1,4 +1,4 @@ -using LanternExtractor.EQ.Pfs; +using LanternExtractor.EQ.Archive; using LanternExtractor.EQ.Wld.Exporters; using LanternExtractor.EQ.Wld.Fragments; using LanternExtractor.Infrastructure.Logger; @@ -7,16 +7,16 @@ namespace LanternExtractor.EQ.Wld { public class WldFileZoneObjects : WldFile { - public WldFileZoneObjects(PfsFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile wldToInject = null) : base( + public WldFileZoneObjects(ArchiveFile wldFile, string zoneName, WldType type, ILogger logger, Settings settings, WldFile wldToInject = null) : base( wldFile, zoneName, type, logger, settings, wldToInject) { } - + public override void ExportData() { ExportObjectInstanceAndVertexColorList(); } - + /// /// Exports the objects instance list (one file per zone) and vertex color lists (one file per object) /// @@ -26,13 +26,13 @@ private void ExportObjectInstanceAndVertexColorList() if (instanceList.Count == 0) { - _logger.LogWarning("Cannot export object instance list. No object instances found."); + Logger.LogWarning("Cannot export object instance list. No object instances found."); return; } - + ObjectInstanceWriter instanceWriter = new ObjectInstanceWriter(); VertexColorsWriter colorWriter = new VertexColorsWriter(); - + string colorsExportFolder = GetRootExportFolder() + "Objects/VertexColors/"; foreach (var instance in instanceList) @@ -43,15 +43,15 @@ private void ExportObjectInstanceAndVertexColorList() { continue; } - + colorWriter.AddFragmentData(instance.Colors); colorWriter.WriteAssetToFile(colorsExportFolder + "vc_" + instance.Colors.Index + ".txt"); colorWriter.ClearExportData(); } - - if (_wldToInject != null) + + if (WldToInject != null) { - instanceList = _wldToInject.GetFragmentsOfType(); + instanceList = WldToInject.GetFragmentsOfType(); foreach (var instance in instanceList) { @@ -61,14 +61,14 @@ private void ExportObjectInstanceAndVertexColorList() { continue; } - + colorWriter.AddFragmentData(instance.Colors); colorWriter.WriteAssetToFile(colorsExportFolder + "vc_" + instance.Colors.Index + ".txt"); colorWriter.ClearExportData(); } } - + instanceWriter.WriteAssetToFile(GetExportFolderForWldType() + "object_instances.txt"); } } -} \ No newline at end of file +} diff --git a/LanternExtractor/EQ/Wld/WldFragmentBuilder.cs b/LanternExtractor/EQ/Wld/WldFragmentBuilder.cs index ab842b0..57008dc 100644 --- a/LanternExtractor/EQ/Wld/WldFragmentBuilder.cs +++ b/LanternExtractor/EQ/Wld/WldFragmentBuilder.cs @@ -23,6 +23,7 @@ public static class WldFragmentBuilder // Meshes {0x36, () => new Mesh()}, {0x37, () => new MeshAnimatedVertices()}, + {0x2E, () => new LegacyMeshAnimatedVertices()}, {0x2F, () => new MeshAnimatedVerticesReference()}, {0x2D, () => new MeshReference()}, {0x2C, () => new LegacyMesh()}, @@ -57,10 +58,10 @@ public static class WldFragmentBuilder {0x08, () => new Camera()}, {0x09, () => new CameraReference()}, {0x16, () => new Fragment16()}, - {0x17, () => new Fragment17()}, - {0x18, () => new Fragment18()}, + {0x17, () => new Polyhedron()}, + {0x18, () => new PolyhedronReference()}, {0x06, () => new Fragment06()}, {0x07, () => new Fragment07()}, }; } -} \ No newline at end of file +} diff --git a/LanternExtractor/EqFileHelper.cs b/LanternExtractor/EqFileHelper.cs index f802037..e5e1e4c 100644 --- a/LanternExtractor/EqFileHelper.cs +++ b/LanternExtractor/EqFileHelper.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -6,57 +6,76 @@ namespace LanternExtractor { public static class EqFileHelper { - public static bool IsEquipmentArchive(string archiveName) + public static List GetValidEqFilePaths(string directory, string archiveName) { - return archiveName.StartsWith("gequip"); + archiveName = archiveName.ToLower(); + + if (!Directory.Exists(directory)) + { + return new List(); + } + + var eqFiles = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories); + List validFiles; + + switch (archiveName) + { + case "all": + validFiles = GetAllValidFiles(eqFiles); + break; + case "zones": + validFiles = GetValidZoneFiles(eqFiles); + break; + case "characters": + validFiles = GetValidCharacterFiles(eqFiles); + break; + case "equipment": + validFiles = GetValidEquipmentFiles(eqFiles); + break; + case "sounds": + validFiles = GetValidSoundFiles(eqFiles); + break; + default: + { + validFiles = GetValidFiles(archiveName, directory); + break; + } + } + + return validFiles; } - public static bool IsCharactersArchive(string archiveName) + private static List GetValidEquipmentFiles(string[] eqFiles) { - return archiveName.Contains("_chr") || archiveName.StartsWith("chequip") || archiveName.Contains("_amr"); + return eqFiles.Where(x => IsEquipmentArchive(Path.GetFileName(x))).ToList(); } - public static bool IsObjectsArchive(string archiveName) + private static List GetAllValidFiles(string[] eqFiles) { - return archiveName.Contains("_obj"); + return eqFiles.Where(x => IsValidArchive(Path.GetFileName(x))).ToList(); } - public static bool IsSkyArchive(string archiveName) + private static List GetValidZoneFiles(string[] eqFiles) { - return archiveName == "sky"; + return eqFiles.Where(x => IsZoneArchive(Path.GetFileName(x))).ToList(); } - public static bool IsSoundArchive(string archiveName) + private static List GetValidCharacterFiles(string[] eqFiles) { - return archiveName.StartsWith("snd"); + return eqFiles.Where(x => IsCharacterArchive(Path.GetFileName(x))).ToList(); } - public static bool IsClientDataFile(string archiveName) + private static List GetValidSoundFiles(string[] eqFiles) { - return archiveName == "clientdata"; + return eqFiles.Where(x => IsSoundArchive(Path.GetFileName(x))).ToList(); } - public static List GetValidEqFilePaths(string directory, string archiveName) + private static List GetValidFiles(string archiveName, string directory) { - archiveName = archiveName.ToLower(); - var validFiles = new List(); - - if (archiveName == "all") - { - validFiles = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories) - .Where(s => (s.EndsWith(".s3d") || s.EndsWith(".pfs")) && !s.Contains("chequip") && - !s.EndsWith("_lit.s3d")).ToList(); - } - else if (archiveName == "equipment") + if (archiveName.EndsWith(".s3d") || archiveName.EndsWith(".pfs") || archiveName.EndsWith(".t3d")) { - validFiles = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories) - .Where(s => (s.EndsWith(".s3d") || s.EndsWith(".pfs")) && s.Contains("gequip")).ToList(); - } - else if (archiveName.EndsWith(".s3d") || archiveName.EndsWith(".pfs")) - { - string archivePath = directory + archiveName; - + string archivePath = Path.Combine(directory, archiveName); if (File.Exists(archivePath)) { validFiles.Add(archivePath); @@ -64,38 +83,47 @@ public static List GetValidEqFilePaths(string directory, string archiveN } else { - // If it's the shortname of a PFS file, we assume it's standalone - used for sound files - string archivePath = directory + archiveName + ".pfs"; + string archivePath = + Path.Combine(directory, string.Concat(archiveName, LanternStrings.PfsFormatExtension)); if (File.Exists(archivePath)) { validFiles.Add(archivePath); - return validFiles; } - // Try and find all associated files with the shortname - can theoretically be a non zone file - string mainArchivePath = directory + archiveName + ".s3d"; + var archiveExtension = LanternStrings.S3dFormatExtension; + if (Directory.EnumerateFiles(directory, $"*{LanternStrings.T3dFormatExtension}", + SearchOption.AllDirectories).Any()) + { + archiveExtension = LanternStrings.T3dFormatExtension; + } + + // Try and find all associated files with the shortname - can theoretically be a non-zone file + string mainArchivePath = Path.Combine(directory, string.Concat(archiveName, archiveExtension)); if (File.Exists(mainArchivePath)) { validFiles.Add(mainArchivePath); } // Some zones have additional object archives for things added past their initial release - // None of them contain fragments that are linked to other related archives. - string extensionObjectsArchivePath = directory + archiveName + "_2_obj.s3d"; + // No archives contain cross archive fragment references + string extensionObjectsArchivePath = + Path.Combine(directory, string.Concat($"{archiveName}_2_obj", archiveExtension)); if (File.Exists(extensionObjectsArchivePath)) { validFiles.Add(extensionObjectsArchivePath); } - string objectsArchivePath = directory + archiveName + "_obj.s3d"; + string objectsArchivePath = + Path.Combine(directory, string.Concat($"{archiveName}_obj", archiveExtension)); if (File.Exists(objectsArchivePath)) { validFiles.Add(objectsArchivePath); } - string charactersArchivePath = directory + archiveName + "_chr.s3d"; + string charactersArchivePath = + Path.Combine(directory, string.Concat($"{archiveName}_chr", archiveExtension)); if (File.Exists(charactersArchivePath)) { validFiles.Add(charactersArchivePath); @@ -104,7 +132,8 @@ public static List GetValidEqFilePaths(string directory, string archiveN // Some zones have additional character archives for things added past their initial release // None of them contain fragments that are linked to other related archives. // "qeynos" must be excluded because both qeynos and qeynos2 are used as shortnames - string extensionCharactersArchivePath = directory + archiveName + "2_chr.s3d"; + string extensionCharactersArchivePath = + Path.Combine(directory, string.Concat($"{archiveName}2_chr", archiveExtension)); if (File.Exists(extensionCharactersArchivePath) && archiveName != "qeynos") { validFiles.Add(extensionCharactersArchivePath); @@ -113,5 +142,104 @@ public static List GetValidEqFilePaths(string directory, string archiveN return validFiles; } + + public static string ObjArchivePath(string archivePath) + { + var baseExt = Path.GetExtension(archivePath); + var lastDotIndex = archivePath.LastIndexOf('.'); + + if (lastDotIndex >= 0) + { + return $"{archivePath.Substring(0, lastDotIndex)}_obj{baseExt}"; + } + + return archivePath; + } + + private static bool IsValidArchive(string archiveName) + { + // chequip contains broken/conflicting data. + // _lit archives get injected later during archive extraction + if (archiveName.Contains("chequip") || archiveName.EndsWith("_lit.s3d")) + { + return false; + } + + return archiveName.EndsWith(".s3d") || archiveName.EndsWith(".t3d") || archiveName.EndsWith(".pfs"); + } + + private static bool IsZoneArchive(string archiveName) + { + return IsValidArchive(archiveName) && !IsEquipmentArchive(archiveName) && !IsSkyArchive(archiveName) && + !IsBitmapArchive(archiveName) && !IsCharacterArchive(archiveName); + } + + public static bool IsEquipmentArchive(string archiveName) + { + return archiveName.StartsWith("gequip"); + } + + public static bool IsCharacterArchive(string archiveName) + { + // chequip contains broken/conflicting data. + if (archiveName.Contains("chequip")) + { + return false; + } + + return archiveName.Contains("_chr") || archiveName.StartsWith("chequip") || + archiveName.Contains("_amr"); + } + + public static bool IsObjectsArchive(string archiveName) + { + return archiveName.Contains("_obj"); + } + + public static bool IsSkyArchive(string archiveName) + { + return archiveName == "sky"; + } + + public static bool IsBitmapArchive(string archiveName) + { + return archiveName.StartsWith("bmpwad"); + } + + public static bool IsSoundArchive(string archiveName) + { + return archiveName.StartsWith("snd"); + } + + public static bool IsClientDataFile(string archiveName) + { + return archiveName == "clientdata"; + } + + public static bool IsMusicFile(string filename) + { + return filename.EndsWith(".xmi"); + } + + public static bool IsSpecialCaseExtraction(string archiveName) + { + return archiveName == "clientdata" || archiveName == "music"; + } + + public static bool IsUsedSoundArchive(string archiveName) + { + if (!IsSoundArchive(archiveName)) + { + return false; + } + + // Trilogy client does not use archives higher than snd9 + if (int.TryParse(archiveName.Substring(archiveName.Length - 2), out _)) + { + return false; + } + + return true; + } } } diff --git a/LanternExtractor/FodyWeavers.xml b/LanternExtractor/FodyWeavers.xml deleted file mode 100644 index a5dcf04..0000000 --- a/LanternExtractor/FodyWeavers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/LanternExtractor/FodyWeavers.xsd b/LanternExtractor/FodyWeavers.xsd deleted file mode 100644 index 05e92c1..0000000 --- a/LanternExtractor/FodyWeavers.xsd +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of unmanaged 32 bit assembly names to include, delimited with line breaks. - - - - - A list of unmanaged 64 bit assembly names to include, delimited with line breaks. - - - - - The order of preloaded assemblies, delimited with line breaks. - - - - - - This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. - - - - - Controls if .pdbs for reference assemblies are also embedded. - - - - - Controls if runtime assemblies are also embedded. - - - - - Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. - - - - - Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. - - - - - As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. - - - - - Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. - - - - - Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of unmanaged 32 bit assembly names to include, delimited with |. - - - - - A list of unmanaged 64 bit assembly names to include, delimited with |. - - - - - The order of preloaded assemblies, delimited with |. - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/LanternExtractor/Infrastructure/EqBmp.cs b/LanternExtractor/Infrastructure/EqBmp.cs new file mode 100644 index 0000000..8b734ba --- /dev/null +++ b/LanternExtractor/Infrastructure/EqBmp.cs @@ -0,0 +1,121 @@ +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; + +namespace LanternExtractor.Infrastructure +{ + /// + /// Wrapper over Bitmap with various hacks to make libgdiplus + /// consistently create transparent pngs on MacOS & Linux + /// + public class EqBmp + { + private static readonly bool NeedsGdipHacks = Environment.OSVersion.Platform == PlatformID.MacOSX || + Environment.OSVersion.Platform == PlatformID.Unix; + private static bool _hasCheckedForPaletteFlagsField; + private static System.Reflection.FieldInfo _paletteFlagsField = null; + + public PixelFormat PixelFormat => _bitmap.PixelFormat; + + private readonly Bitmap _bitmap; + private readonly ColorPalette _palette; + + public EqBmp(Stream stream) + { + SetPaletteFlagsField(); + + _bitmap = new Bitmap(stream); + _palette = _bitmap.Palette; + } + + public void WritePng(string outputFilePath) + { + _bitmap.Save(outputFilePath, ImageFormat.Png); + } + + public void MakeMagentaTransparent() + { + _bitmap.MakeTransparent(Color.Magenta); + if (NeedsGdipHacks) + { + // https://github.com/mono/libgdiplus/commit/bf9a1440b7bfea704bf2cb771f5c2b5c09e7bcfa + _bitmap.MakeTransparent(Color.FromArgb(0, Color.Magenta)); + } + } + + public void MakePaletteTransparent(int transparentIndex) + { + if (NeedsGdipHacks) + { + // https://github.com/mono/libgdiplus/issues/702 + _paletteFlagsField?.SetValue(_palette, _palette.Flags | (int)PaletteFlags.HasAlpha); + } + + var transparentColor = GetTransparentPaletteColor(); + _palette.Entries[transparentIndex] = transparentColor; + _bitmap.Palette = _palette; + + if (NeedsGdipHacks) + { + // Due to a bug with the libgdiplus implementation of System.Drawing, setting a color palette + // entry to transparent does not work. The workaround is to ensure that the transparent + // key is unique and then use MakeTransparent() + _bitmap.MakeTransparent(transparentColor); + } + } + + private Color GetTransparentPaletteColor() + { + var transparencyColor = Color.FromArgb(0, 0, 0, 0); + + if (!NeedsGdipHacks) + { + return transparencyColor; + } + + var random = new Random(); + var foundUnique = false; + + while (!foundUnique) + { + foundUnique = _palette.Entries.All(e => e != transparencyColor); + transparencyColor = Color.FromArgb(0, random.Next(256), random.Next(256), random.Next(256)); + } + + return transparencyColor; + } + + // https://github.com/Robmaister/SharpFont/blob/422bdab059dd8e594b4b061a3b53152e71342ce2/Source/SharpFont.GDI/FTBitmapExtensions.cs + // https://github.com/Robmaister/SharpFont/pull/136 + private static void SetPaletteFlagsField() + { + if (!NeedsGdipHacks || _hasCheckedForPaletteFlagsField) + { + return; + } + + _hasCheckedForPaletteFlagsField = true; + + // The field needed may be named "flags" or "_flags", depending on the version of Mono. To be thorough, check for the first Name that contains "lags". + var fields = typeof(ColorPalette).GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + foreach (var t in fields) + { + if (t.Name.Contains("lags")) + { + _paletteFlagsField = t; + break; + } + } + } + + enum PaletteFlags + { + HasAlpha = 0x0001, + GrayScale = 0x0002, + HalfTone = 0x0004, + } + } +} diff --git a/LanternExtractor/Infrastructure/ExtensionMethods.cs b/LanternExtractor/Infrastructure/ExtensionMethods.cs new file mode 100644 index 0000000..24cf630 --- /dev/null +++ b/LanternExtractor/Infrastructure/ExtensionMethods.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Text; + +namespace LanternExtractor.Infrastructure +{ + public static class ExtensionMethods + { + public static string ReadNullTerminatedString(this BinaryReader reader) + { + StringBuilder builder = new StringBuilder(); + char nextCharacter; + while ((nextCharacter = reader.ReadChar()) != 0) + { + builder.Append(nextCharacter); + } + return builder.ToString(); + } + } +} diff --git a/LanternExtractor/Infrastructure/ImageWriter.cs b/LanternExtractor/Infrastructure/ImageWriter.cs index ac9471f..e3d09ed 100644 --- a/LanternExtractor/Infrastructure/ImageWriter.cs +++ b/LanternExtractor/Infrastructure/ImageWriter.cs @@ -31,8 +31,6 @@ public static void WriteImageAsPng(byte[] bytes, string filePath, string fileNam private static void WriteBmpAsPng(byte[] bytes, string filePath, string fileName, bool isMasked, bool rotate, ILogger logger) { - var byteStream = new MemoryStream(bytes); - if (string.IsNullOrEmpty(filePath)) { return; @@ -40,11 +38,12 @@ private static void WriteBmpAsPng(byte[] bytes, string filePath, string fileName Directory.CreateDirectory(filePath); - Bitmap image; + EqBmp image; + var byteStream = new MemoryStream(bytes); try { - image = new Bitmap(byteStream); + image = new EqBmp(byteStream); } catch (Exception e) { @@ -60,76 +59,17 @@ private static void WriteBmpAsPng(byte[] bytes, string filePath, string fileName fileName = "canwall1.png"; } - Bitmap cloneBitmap; - - if (isMasked) + if (image.PixelFormat == PixelFormat.Format8bppIndexed && isMasked) { - cloneBitmap = image.Clone(new Rectangle(0, 0, image.Width, image.Height), - PixelFormat.Format8bppIndexed); - - int paletteIndex = GetPaletteIndex(fileName); - var palette = cloneBitmap.Palette; - - if (Environment.OSVersion.Platform != PlatformID.MacOSX && - Environment.OSVersion.Platform != PlatformID.Unix) - { - palette.Entries[paletteIndex] = Color.FromArgb(0, 0, 0, 0); - cloneBitmap.Palette = palette; - } - else - { - // Due to a bug with the MacOS implementation of System.Drawing, setting a color palette value to - // transparent does not work. The workaround is to ensure that the first palette value (the transparent - // key) is unique and then use MakeTransparent() - Color transparencyColor = palette.Entries[paletteIndex]; - bool isUnique = false; - - while (!isUnique) - { - isUnique = true; - - for (var i = 1; i < cloneBitmap.Palette.Entries.Length; i++) - { - Color paletteValue = cloneBitmap.Palette.Entries[i]; - - if (paletteValue == transparencyColor) - { - Random random = new Random(); - transparencyColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256)); - isUnique = false; - break; - } - } - } - - palette.Entries[paletteIndex] = transparencyColor; - cloneBitmap.Palette = palette; - cloneBitmap.MakeTransparent(transparencyColor); - - // For some reason, this now has to be done to ensure the pixels are actually set to transparent - // Another head scratching MacOS bug - for (int i = 0; i < cloneBitmap.Width; ++i) - { - for (int j = 0; j < cloneBitmap.Height; ++j) - { - if (cloneBitmap.GetPixel(i, j) == transparencyColor) - { - cloneBitmap.SetPixel(i, j, Color.FromArgb(0, 0, 0, 0)); - } - } - } - } + var paletteIndex = GetPaletteIndex(fileName); + image.MakePaletteTransparent(paletteIndex); } else { - cloneBitmap = image.Clone(new Rectangle(0, 0, image.Width, image.Height), PixelFormat.Format32bppArgb); - if (image.PixelFormat != PixelFormat.Format8bppIndexed) - { - cloneBitmap.MakeTransparent(Color.Magenta); - } + image.MakeMagentaTransparent(); } - cloneBitmap.Save(Path.Combine(filePath, fileName), ImageFormat.Png); + image.WritePng(Path.Combine(filePath, fileName)); } private static void WriteDdsAsPng(byte[] bytes, string filePath, string fileName) diff --git a/LanternExtractor/LanternExtractor.cs b/LanternExtractor/LanternExtractor.cs index 8afceb7..4dacf7b 100644 --- a/LanternExtractor/LanternExtractor.cs +++ b/LanternExtractor/LanternExtractor.cs @@ -53,11 +53,10 @@ private static void Main(string[] args) } var archiveName = args[0]; - List eqFiles = EqFileHelper.GetValidEqFilePaths(_settings.EverQuestDirectory, archiveName); eqFiles.Sort(); - if (eqFiles.Count == 0 && !EqFileHelper.IsClientDataFile(archiveName)) + if (eqFiles.Count == 0 && !EqFileHelper.IsSpecialCaseExtraction(archiveName)) { Console.WriteLine("No valid EQ files found for: '" + archiveName + "' at path: " + _settings.EverQuestDirectory); @@ -89,7 +88,9 @@ private static void Main(string[] args) ArchiveExtractor.Extract(file, "Exports/", _logger, _settings); } } + ClientDataCopier.Copy(archiveName, "Exports/", _logger, _settings); + MusicCopier.Copy(archiveName, _logger, _settings); Console.WriteLine($"Extraction complete ({(DateTime.Now - start).TotalSeconds:.00}s)"); } diff --git a/LanternExtractor/LanternExtractor.csproj b/LanternExtractor/LanternExtractor.csproj index e3380e1..67a1e45 100644 --- a/LanternExtractor/LanternExtractor.csproj +++ b/LanternExtractor/LanternExtractor.csproj @@ -1,205 +1,46 @@ - - - + - Debug - AnyCPU - {00229605-0107-4EEE-B523-9982A7738572} Exe - LanternExtractor - LanternExtractor - v4.8 - 512 - true - - + net6.0 + true + true + true 7 + false + CA1416 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 + + true - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + + $([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) + win-$(Arch.ToLower()) + linux-$(Arch.ToLower()) + osx-$(Arch.ToLower()) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - PreserveNewest - PreserveNewest - - 5.1.0 - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - 1.10.1 - + 0.9.8 + 0.10.0 - - 1.0.0-alpha0025 - 1.0.0-alpha0025 + + + + - - \ No newline at end of file + diff --git a/LanternExtractor/LanternStrings.cs b/LanternExtractor/LanternStrings.cs index b700550..0517317 100644 --- a/LanternExtractor/LanternStrings.cs +++ b/LanternExtractor/LanternStrings.cs @@ -5,9 +5,9 @@ namespace LanternExtractor /// public static class LanternStrings { - public const string ExportHeaderTitle = "# Lantern Extractor 0.2 - "; + public const string ExportHeaderTitle = "# Lantern Extractor 0.1.7 - "; public const string ExportHeaderFormat = "# Format: "; - + public const string ObjMaterialHeader = "mtllib "; public const string ObjUseMtlPrefix = "usemtl "; public const string ObjNewMaterialPrefix = "newmtl"; @@ -16,7 +16,9 @@ public static class LanternStrings public const string WldFormatExtension = ".wld"; public const string S3dFormatExtension = ".s3d"; - public const string PfsFormatExtension = ".s3d"; + public const string PfsFormatExtension = ".pfs"; + public const string PakFormatExtension = ".pak"; + public const string T3dFormatExtension = ".t3d"; public const string SoundFormatExtension = ".eff"; } -} \ No newline at end of file +} diff --git a/LanternExtractor/ModelExportFormat.cs b/LanternExtractor/ModelExportFormat.cs new file mode 100644 index 0000000..c13527c --- /dev/null +++ b/LanternExtractor/ModelExportFormat.cs @@ -0,0 +1,9 @@ +namespace LanternExtractor +{ + public enum ModelExportFormat + { + Intermediate = 0, + Obj = 1, + GlTF = 2 + } +} \ No newline at end of file diff --git a/LanternExtractor/Settings.cs b/LanternExtractor/Settings.cs index 7c94159..5aac280 100644 --- a/LanternExtractor/Settings.cs +++ b/LanternExtractor/Settings.cs @@ -1,25 +1,15 @@ using System; -using System.Collections.Generic; using System.IO; using LanternExtractor.Infrastructure; using LanternExtractor.Infrastructure.Logger; - namespace LanternExtractor { - public enum ModelExportFormat - { - Intermediate = 0, - Obj = 1, - GlTF = 2 - } - /// /// Simple class that parses settings for the extractor /// public class Settings { - /// /// The logger reference for debug output /// @@ -55,20 +45,11 @@ public class Settings /// Sets the desired model export format /// public ModelExportFormat ModelExportFormat { get; private set; } - - /// - /// Sets the desired model export format - /// + public bool ExportCharactersToSingleFolder { get; private set; } - - /// - /// Sets the desired model export format - /// + public bool ExportEquipmentToSingleFolder { get; private set; } - - /// - /// Export all sound files to a single folder - /// + public bool ExportSoundsToSingleFolder { get; private set; } /// @@ -101,6 +82,11 @@ public class Settings /// Additional files that should be copied when extracting with `all` or `clientdata` /// public string ClientDataToCopy { get; private set; } + + /// + /// If enabled, XMI files will be copied to the 'Exports/Music' folder + /// + public bool CopyMusic { get; private set; } /// /// The verbosity of the logger @@ -147,78 +133,88 @@ public void Initialize() return; } - if (parsedSettings.ContainsKey("EverQuestDirectory")) + if (parsedSettings.TryGetValue("EverQuestDirectory", out var setting)) { - EverQuestDirectory = parsedSettings["EverQuestDirectory"]; + EverQuestDirectory = setting; // Ensure the path ends with a / EverQuestDirectory = Path.GetFullPath(EverQuestDirectory + "/"); } - if (parsedSettings.ContainsKey("RawS3DExtract")) + if (parsedSettings.TryGetValue("RawS3DExtract", out var parsedSetting)) { - RawS3dExtract = Convert.ToBoolean(parsedSettings["RawS3DExtract"]); + RawS3dExtract = Convert.ToBoolean(parsedSetting); } - if (parsedSettings.ContainsKey("ExportZoneMeshGroups")) + if (parsedSettings.TryGetValue("ExportZoneMeshGroups", out var setting1)) { - ExportZoneMeshGroups = Convert.ToBoolean(parsedSettings["ExportZoneMeshGroups"]); + ExportZoneMeshGroups = Convert.ToBoolean(setting1); } - if (parsedSettings.ContainsKey("ExportHiddenGeometry")) + if (parsedSettings.TryGetValue("ExportHiddenGeometry", out var parsedSetting1)) { - ExportHiddenGeometry = Convert.ToBoolean(parsedSettings["ExportHiddenGeometry"]); + ExportHiddenGeometry = Convert.ToBoolean(parsedSetting1); } - if (parsedSettings.ContainsKey("ExportZoneWithObjects")) + if (parsedSettings.TryGetValue("ExportZoneWithObjects", out var setting2)) { - ExportZoneWithObjects = Convert.ToBoolean(parsedSettings["ExportZoneWithObjects"]); + ExportZoneWithObjects = Convert.ToBoolean(setting2); } - if (parsedSettings.ContainsKey("ModelExportFormat")) + if (parsedSettings.TryGetValue("ModelExportFormat", out var parsedSetting2)) { - var exportFormatSetting = (ModelExportFormat)Convert.ToInt32(parsedSettings["ModelExportFormat"]); + var exportFormatSetting = (ModelExportFormat)Convert.ToInt32(parsedSetting2); ModelExportFormat = exportFormatSetting; } - if (parsedSettings.ContainsKey("ExportCharacterToSingleFolder")) + if (parsedSettings.TryGetValue("ExportCharacterToSingleFolder", out var setting3)) { - ExportCharactersToSingleFolder = Convert.ToBoolean(parsedSettings["ExportCharacterToSingleFolder"]); + ExportCharactersToSingleFolder = Convert.ToBoolean(setting3); } - if (parsedSettings.ContainsKey("ExportEquipmentToSingleFolder")) + if (parsedSettings.TryGetValue("ExportEquipmentToSingleFolder", out var parsedSetting3)) { - ExportEquipmentToSingleFolder = Convert.ToBoolean(parsedSettings["ExportEquipmentToSingleFolder"]); + ExportEquipmentToSingleFolder = Convert.ToBoolean(parsedSetting3); } - - if (parsedSettings.ContainsKey("ExportSoundsToSingleFolder")) + + if (parsedSettings.TryGetValue("ExportSoundsToSingleFolder", out var setting4)) { - ExportSoundsToSingleFolder = Convert.ToBoolean(parsedSettings["ExportSoundsToSingleFolder"]); + ExportSoundsToSingleFolder = Convert.ToBoolean(setting4); } - if (parsedSettings.ContainsKey("ExportAllAnimationFrames")) + if (parsedSettings.TryGetValue("ExportAllAnimationFrames", out var parsedSetting4)) { - ExportAllAnimationFrames = Convert.ToBoolean(parsedSettings["ExportAllAnimationFrames"]); + ExportAllAnimationFrames = Convert.ToBoolean(parsedSetting4); } - if (parsedSettings.ContainsKey("ExportGltfVertexColors")) + if (parsedSettings.TryGetValue("ExportGltfVertexColors", out var setting5)) { - ExportGltfVertexColors = Convert.ToBoolean(parsedSettings["ExportGltfVertexColors"]); + ExportGltfVertexColors = Convert.ToBoolean(setting5); } - if (parsedSettings.ContainsKey("ExportGltfInGlbFormat")) + if (parsedSettings.TryGetValue("ExportGltfInGlbFormat", out var parsedSetting5)) { - ExportGltfInGlbFormat = Convert.ToBoolean(parsedSettings["ExportGltfInGlbFormat"]); + ExportGltfInGlbFormat = Convert.ToBoolean(parsedSetting5); } - if (parsedSettings.ContainsKey("ClientDataToCopy")) + if (parsedSettings.TryGetValue("ClientDataToCopy", out var setting6)) + { + ClientDataToCopy = setting6; + } + + if (parsedSettings.TryGetValue("ClientDataToCopy", out var parsedSetting6)) + { + ClientDataToCopy = parsedSetting6; + } + + if (parsedSettings.TryGetValue("CopyMusic", out var setting7)) { - ClientDataToCopy = parsedSettings["ClientDataToCopy"]; + CopyMusic = Convert.ToBoolean(setting7); } - if (parsedSettings.ContainsKey("LoggerVerbosity")) + if (parsedSettings.TryGetValue("LoggerVerbosity", out var parsedSetting7)) { - LoggerVerbosity = Convert.ToInt32(parsedSettings["LoggerVerbosity"]); + LoggerVerbosity = Convert.ToInt32(parsedSetting7); } } } diff --git a/LanternExtractor/runtimeconfig.template.json b/LanternExtractor/runtimeconfig.template.json new file mode 100644 index 0000000..5c61d62 --- /dev/null +++ b/LanternExtractor/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.Drawing.EnableUnixSupport": true + } +} diff --git a/LanternExtractor/settings.txt b/LanternExtractor/settings.txt index 776054a..4de0147 100644 --- a/LanternExtractor/settings.txt +++ b/LanternExtractor/settings.txt @@ -1,13 +1,13 @@ # Lantern Extractor Settings # EverQuest Installation Path -# Window example: C:/EverQuest +# Windows example: C:/EverQuest # MacOS example: /Users/your.name/EQ/ EverQuestDirectory = C:/EverQuest # Extract the contents of the S3D archive as is? -# If true, this will export all contents as they exist in the archives to their original formats. -# If false, this will extract contents, process WLD files and convert images to .png +# If true, this will export all contents as they exist in the archives in their original formats. +# If false, this will extract contents, process WLD files and convert images to PNG. RawS3DExtract = false # Sets the desired export format for models @@ -37,7 +37,7 @@ ExportCharacterToSingleFolder = true ExportEquipmentToSingleFolder = true # If true, will export all sounds to the 'sounds' folder -# Note, some sounds with conflicting names will be overwritten +# Some sounds with conflicting names will be overwritten ExportSoundsToSingleFolder = true # If true and using OBJ export format, this will dump an OBJ for each animation frame. @@ -69,6 +69,10 @@ ExportGltfInGlbFormat = false # (Intermediate) ClientDataToCopy = spells.eff,spdat.eff +# If enabled, client XMI files will be copied to the 'Exports/Music' folder +# XMI files can be used in the Lantern client +CopyMusic = true + # The minimum verbosity of the logger # 0 = info, 1 = warnings, 2 = errors LoggerVerbosity = 2