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/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/PfsArchive.cs b/LanternExtractor/EQ/Archive/PfsArchive.cs
index d9cf23d..a073f00 100644
--- a/LanternExtractor/EQ/Archive/PfsArchive.cs
+++ b/LanternExtractor/EQ/Archive/PfsArchive.cs
@@ -79,7 +79,9 @@ public override 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);
diff --git a/LanternExtractor/EQ/ArchiveExtractor.cs b/LanternExtractor/EQ/ArchiveExtractor.cs
index 5b5a7d5..45166be 100644
--- a/LanternExtractor/EQ/ArchiveExtractor.cs
+++ b/LanternExtractor/EQ/ArchiveExtractor.cs
@@ -30,21 +30,21 @@ public static void Extract(string path, string rootFolder, ILogger logger, Setti
if (settings.RawS3dExtract)
{
- archive.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
+ // The difference between this and the raw export is that it will convert images to PNG
if (!archive.IsWldArchive)
{
WriteS3dTextures(archive, rootFolder + shortName, logger);
- if (EqFileHelper.IsSoundArchive(archiveName))
+ if (EqFileHelper.IsUsedSoundArchive(archiveName))
{
- var soundFolder = settings.ExportSoundsToSingleFolder ? "sounds" : shortName;
- WriteS3dSounds(archive, rootFolder + soundFolder, logger);
+ WriteS3dSounds(archive,
+ Path.Combine(rootFolder, settings.ExportSoundsToSingleFolder ? "sounds" : shortName), logger);
}
return;
@@ -68,9 +68,10 @@ public static void Extract(string path, string rootFolder, ILogger logger, Setti
{
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, archive);
+ ExtractArchiveCharacters(path, rootFolder, logger, settings, archiveName, wldFileInArchive, shortName,
+ archive);
}
else if (EqFileHelper.IsObjectsArchive(archiveName))
{
@@ -82,7 +83,6 @@ public static void Extract(string path, string rootFolder, ILogger logger, Setti
}
MissingTextureFixer.Fix(archiveName);
-
}
private static void ExtractArchiveZone(string path, string rootFolder, ILogger logger, Settings settings,
@@ -104,7 +104,8 @@ private static void ExtractArchiveZone(string path, string rootFolder, ILogger l
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);
}
}
@@ -120,6 +121,7 @@ private static void ExtractArchiveZone(string path, string rootFolder, ILogger l
wldFile.RootFolder = rootFolder;
wldFile.ShortName = shortName;
}
+
InitializeWldAndWriteTextures(wldFile, rootFolder, rootFolder + shortName + "/Zone/Textures/",
archive, settings, logger);
@@ -141,7 +143,7 @@ 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,
@@ -200,8 +202,8 @@ private static void ExtractArchiveCharacters(string path, string rootFolder, ILo
archive, settings, logger);
}
- private static void ExtractArchiveSky(string rootFolder, ILogger logger, Settings settings, ArchiveFile wldFileInArchive,
- string shortName, ArchiveBase archive)
+ 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/",
@@ -213,10 +215,10 @@ private static void ExtractArchiveEquipment(string rootFolder, ILogger logger, S
{
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, archive, settings, logger);
}
@@ -307,15 +309,24 @@ public static void WriteWldTextures(ArchiveBase archive, WldFile wldFile, string
}
}
- 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..7b1bbd8
--- /dev/null
+++ b/LanternExtractor/EQ/Sound/AudioInstance.cs
@@ -0,0 +1,85 @@
+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; }
+ }
+
+ 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; }
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+}
\ No newline at end of file
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..a058fbc
--- /dev/null
+++ b/LanternExtractor/EQ/Sound/EnvAudio.cs
@@ -0,0 +1,88 @@
+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 => _instance;
+ private static readonly EnvAudio _instance = 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/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/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/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/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/WldFile.cs b/LanternExtractor/EQ/Wld/WldFile.cs
index c116d7d..fd32127 100644
--- a/LanternExtractor/EQ/Wld/WldFile.cs
+++ b/LanternExtractor/EQ/Wld/WldFile.cs
@@ -23,11 +23,6 @@ public abstract class WldFile
public WldType WldType => _wldType;
- ///
- /// The link between fragment types and fragment classes
- ///
- private Dictionary> _fragmentBuilder;
-
///
/// A link of indices to fragments
///
@@ -159,8 +154,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();
diff --git a/LanternExtractor/EqFileHelper.cs b/LanternExtractor/EqFileHelper.cs
index 0fb62c9..6cd482c 100644
--- a/LanternExtractor/EqFileHelper.cs
+++ b/LanternExtractor/EqFileHelper.cs
@@ -6,57 +6,70 @@ 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();
+ 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.EndsWith(".t3d")) && !s.Contains("chequip") &&
- !s.EndsWith("_lit.s3d")).ToList();
- }
- else if (archiveName == "equipment")
- {
- validFiles = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories)
- .Where(s => (s.EndsWith(".s3d") || s.EndsWith(".pfs") || s.EndsWith(".t3d")) && s.Contains("gequip")).ToList();
- }
- else if (archiveName.EndsWith(".s3d") || archiveName.EndsWith(".pfs") || archiveName.EndsWith(".t3d"))
+ if (archiveName.EndsWith(".s3d") || archiveName.EndsWith(".pfs") || archiveName.EndsWith(".t3d"))
{
- string archivePath = directory + archiveName;
-
+ string archivePath = Path.Combine(directory, archiveName);
if (File.Exists(archivePath))
{
validFiles.Add(archivePath);
@@ -64,13 +77,11 @@ 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 + LanternStrings.PfsFormatExtension;
+ string archivePath = Path.Combine(directory, archiveName, LanternStrings.PfsFormatExtension);
if (File.Exists(archivePath))
{
validFiles.Add(archivePath);
-
return validFiles;
}
@@ -80,28 +91,28 @@ public static List GetValidEqFilePaths(string directory, string archiveN
archiveExtension = LanternStrings.T3dFormatExtension;
}
- // Try and find all associated files with the shortname - can theoretically be a non zone file
- string mainArchivePath = directory + archiveName + archiveExtension;
+ // Try and find all associated files with the shortname - can theoretically be a non-zone file
+ string mainArchivePath = Path.Combine(directory, 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" + archiveExtension;
+ // No archives contain cross archive fragment references
+ string extensionObjectsArchivePath = Path.Combine(directory, $"{archiveName}_2_obj", archiveExtension);
if (File.Exists(extensionObjectsArchivePath))
{
validFiles.Add(extensionObjectsArchivePath);
}
- string objectsArchivePath = directory + archiveName + "_obj" + archiveExtension;
+ string objectsArchivePath = Path.Combine(directory, $"{archiveName}_obj", archiveExtension);
if (File.Exists(objectsArchivePath))
{
validFiles.Add(objectsArchivePath);
}
- string charactersArchivePath = directory + archiveName + "_chr" + archiveExtension;
+ string charactersArchivePath = Path.Combine(directory, $"{archiveName}_chr", archiveExtension);
if (File.Exists(charactersArchivePath))
{
validFiles.Add(charactersArchivePath);
@@ -110,7 +121,7 @@ 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" + archiveExtension;
+ string extensionCharactersArchivePath = Path.Combine(directory, $"{archiveName}2_chr", archiveExtension);
if (File.Exists(extensionCharactersArchivePath) && archiveName != "qeynos")
{
validFiles.Add(extensionCharactersArchivePath);
@@ -132,5 +143,79 @@ public static string ObjArchivePath(string archivePath)
return archivePath;
}
+
+ private static bool IsValidArchive(string archiveName)
+ {
+ return archiveName.EndsWith(".s3d") || archiveName.EndsWith(".t3d") || archiveName.EndsWith(".pfs")
+ && !archiveName.Contains("chequip") && !archiveName.EndsWith("_lit.s3d");
+ }
+
+ 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)
+ {
+ 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/Infrastructure/EqBmp.cs b/LanternExtractor/Infrastructure/EqBmp.cs
new file mode 100644
index 0000000..cc043ae
--- /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", dependin 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);
+
+ for (int i = 0; i < fields.Length; i++)
+ {
+ if (fields[i].Name.Contains("lags"))
+ {
+ _paletteFlagsField = fields[i];
+ break;
+ }
+ }
+ }
+
+ enum PaletteFlags
+ {
+ HasAlpha = 0x0001,
+ GrayScale = 0x0002,
+ HalfTone = 0x0004,
+ }
+ }
+}
diff --git a/LanternExtractor/Infrastructure/ImageWriter.cs b/LanternExtractor/Infrastructure/ImageWriter.cs
index ac9471f..2625509 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,21 @@ private static void WriteBmpAsPng(byte[] bytes, string filePath, string fileName
fileName = "canwall1.png";
}
- Bitmap cloneBitmap;
-
- if (isMasked)
+ switch (image.PixelFormat)
{
- 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)
+ case PixelFormat.Format8bppIndexed:
+ if (isMasked)
{
- 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);
- }
+ break;
+ default:
+ image.MakeMagentaTransparent();
+ break;
}
- 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 c41337e..67a1e45 100644
--- a/LanternExtractor/LanternExtractor.csproj
+++ b/LanternExtractor/LanternExtractor.csproj
@@ -1,4 +1,4 @@
-
+
Exe
net6.0
@@ -22,14 +22,12 @@
PreserveNewest
-
- PreserveNewest
-
PreserveNewest
+
0.9.8
diff --git a/LanternExtractor/LanternStrings.cs b/LanternExtractor/LanternStrings.cs
index bdab7f9..0517317 100644
--- a/LanternExtractor/LanternStrings.cs
+++ b/LanternExtractor/LanternStrings.cs
@@ -5,7 +5,7 @@ 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 ";
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/settings.txt b/LanternExtractor/settings.txt
index 9e40c22..4de0147 100644
--- a/LanternExtractor/settings.txt
+++ b/LanternExtractor/settings.txt
@@ -6,8 +6,8 @@
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