diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj
index 85bdb8db..094b6beb 100644
--- a/FModel/FModel.csproj
+++ b/FModel/FModel.csproj
@@ -110,6 +110,7 @@
+
True
@@ -120,6 +121,7 @@
+
diff --git a/FModel/PakReader/BnkReader.cs b/FModel/PakReader/BnkReader.cs
new file mode 100644
index 00000000..539bec20
--- /dev/null
+++ b/FModel/PakReader/BnkReader.cs
@@ -0,0 +1,322 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace FModel.PakReader
+{
+ ///
+ /// http://wiki.xentax.com/index.php/Wwise_SoundBank_(*.bnk)
+ /// don't copy paste pls, try to understand what you write from this
+ /// DATASection holds the .wem files
+ /// STIDSection holds the .wem files name in a dictionary where the key is the .wem file id from DATASection
+ /// i see no use (for FModel) in other sections atm
+ ///
+ public class BnkReader
+ {
+ private const uint _BKHD_ID = 0x44484B42;
+ private const uint _DIDX_ID = 0x58444944;
+ private const uint _DATA_ID = 0x41544144;
+ private const uint _HIRC_ID = 0x43524948;
+ private const uint _STID_ID = 0x44495453;
+ private const uint _STMG_ID = 0x474D5453;
+ public Dictionary AudioFiles;
+
+ public BnkReader(BinaryReader reader)
+ {
+ DIDXSection didxSection = null;
+ DATASection dataSection = null;
+ HIRCSection hircSection = null;
+ STIDSection stidSection = null;
+ STMGSection stmgSection = null;
+ AudioFiles = new Dictionary();
+
+ while (reader.BaseStream.Position < reader.BaseStream.Length)
+ {
+ uint SectionIdentifier = reader.ReadUInt32();
+ uint SectionLength = reader.ReadUInt32();
+ long Position = reader.BaseStream.Position;
+ switch (SectionIdentifier)
+ {
+ case _BKHD_ID:
+ BKHDSection _ = new BKHDSection(reader);
+ break;
+ case _DIDX_ID:
+ didxSection = new DIDXSection(reader, Position + SectionLength);
+ break;
+ case _DATA_ID:
+ if (didxSection != null) dataSection = new DATASection(reader, Position, didxSection);
+ break;
+ case _HIRC_ID:
+ hircSection = new HIRCSection(reader);
+ break;
+ case _STID_ID:
+ stidSection = new STIDSection(reader);
+ break;
+ case _STMG_ID:
+ stmgSection = new STMGSection(reader);
+ break;
+ }
+
+ reader.BaseStream.Seek(Position + SectionLength, SeekOrigin.Begin);
+ }
+
+ if (didxSection != null && dataSection != null && didxSection.WemFilesRef.Count == dataSection.WemFiles.Count)
+ {
+ for (int i = 0; i < didxSection.WemFilesRef.Count; i++)
+ {
+ string key = $"{didxSection.WemFilesRef[i].Id}.wem";
+ if (stidSection != null && stidSection.SoundBanks.TryGetValue(didxSection.WemFilesRef[i].Id, out string name))
+ key = name;
+
+ AudioFiles[key] = dataSection.WemFiles[i];
+ }
+ }
+ }
+
+ public class BKHDSection
+ {
+ public uint Version;
+ public uint Id;
+
+ public BKHDSection(BinaryReader reader)
+ {
+ Version = reader.ReadUInt32();
+ Id = reader.ReadUInt32();
+ }
+ }
+
+ public class DIDXSection
+ {
+ public List WemFilesRef;
+
+ public DIDXSection(BinaryReader reader, long length)
+ {
+ WemFilesRef = new List();
+ while (reader.BaseStream.Position < length)
+ {
+ WemFilesRef.Add(new WemObject(reader));
+ }
+ }
+
+ public class WemObject
+ {
+ public uint Id;
+ public uint Offset;
+ public uint Length;
+
+ public WemObject(BinaryReader reader)
+ {
+ Id = reader.ReadUInt32();
+ Offset = reader.ReadUInt32();
+ Length = reader.ReadUInt32();
+ }
+ }
+ }
+
+ public class DATASection
+ {
+ public List WemFiles;
+
+ public DATASection(BinaryReader reader, long position, DIDXSection didxSection)
+ {
+ WemFiles = new List(didxSection.WemFilesRef.Count);
+ foreach (var fileRef in didxSection.WemFilesRef)
+ {
+ reader.BaseStream.Seek(position + fileRef.Offset, SeekOrigin.Begin);
+ WemFiles.Add(reader.ReadBytes(Convert.ToInt32(fileRef.Length)));
+ }
+ }
+ }
+
+ public class HIRCSection
+ {
+ public uint ObjectNumber;
+ public WwiseObject[] Objects;
+
+ public HIRCSection(BinaryReader reader)
+ {
+ ObjectNumber = reader.ReadUInt32();
+ Objects = new WwiseObject[ObjectNumber];
+ for (int i = 0; i < Objects.Length; i++)
+ {
+ Objects[i] = new WwiseObject(reader);
+ }
+ }
+
+ public class WwiseObject
+ {
+ public WwiseObjectType Type;
+ public uint Length;
+ public uint Id;
+ public byte[] AdditionalData;
+
+ public WwiseObject(BinaryReader reader)
+ {
+ Type = (WwiseObjectType)reader.ReadByte();
+ Length = reader.ReadUInt32();
+ Id = reader.ReadUInt32();
+
+ AdditionalData = Type switch
+ {
+ _ => reader.ReadBytes(Convert.ToInt32(Length - sizeof(uint))),
+ };
+ }
+
+ public enum WwiseObjectType : byte
+ {
+ Settings,
+ SoundSFXVoice,
+ EventAction,
+ Event,
+ SequenceContainer,
+ SwitchContainer,
+ AudioBus,
+ BlendContainer,
+ MusicSegment,
+ MusicTrack,
+ MusicSwitchContainer,
+ MusicPlaylistContainer,
+ Attenuation,
+ DialogueEvent,
+ MotionBus,
+ MotionFX,
+ Effect,
+ AuxiliaryBus
+ }
+ }
+ }
+
+ public class STIDSection
+ {
+ public uint SoundBankNumber;
+ public Dictionary SoundBanks;
+
+ public STIDSection(BinaryReader reader)
+ {
+ reader.ReadUInt32();
+ SoundBankNumber = reader.ReadUInt32();
+ SoundBanks = new Dictionary(Convert.ToInt32(SoundBankNumber));
+ for (int i = 0; i < SoundBankNumber; i++)
+ {
+ SoundBanks[reader.ReadUInt32()] = reader.ReadString();
+ }
+ }
+ }
+
+ public class STMGSection
+ {
+ public float VolumeThreshold;
+ public ushort MaxVoiceInstances;
+ public uint StateGroupNumber;
+ public StateGroupObject[] StateGroups;
+ public uint SwitchGroupNumber;
+ public SwitchGroupObject[] SwitchGroups;
+ public uint GameParameterNumber;
+ public GameParameterObject[] GameParameters;
+
+ public STMGSection(BinaryReader reader)
+ {
+ VolumeThreshold = reader.ReadSingle();
+ MaxVoiceInstances = reader.ReadUInt16();
+ StateGroupNumber = reader.ReadUInt32();
+ StateGroups = new StateGroupObject[Convert.ToInt32(StateGroups)];
+ for (int i = 0; i < StateGroups.Length; i++)
+ {
+ StateGroups[i] = new StateGroupObject(reader);
+ }
+ SwitchGroupNumber = reader.ReadUInt32();
+ SwitchGroups = new SwitchGroupObject[SwitchGroupNumber];
+ for (int i = 0; i < SwitchGroups.Length; i++)
+ {
+ SwitchGroups[i] = new SwitchGroupObject(reader);
+ }
+ GameParameterNumber = reader.ReadUInt32();
+ GameParameters = new GameParameterObject[GameParameterNumber];
+ for (int i = 0; i < GameParameters.Length; i++)
+ {
+ GameParameters[i] = new GameParameterObject(reader);
+ }
+ }
+
+ public class StateGroupObject
+ {
+ public uint StateId;
+ public uint DefaultTransitionTime;
+ public uint CustomTransitionTimeNumber;
+ public CustomTransitionTimeObject[] CustomTransitionTimes;
+
+ public StateGroupObject(BinaryReader reader)
+ {
+ StateId = reader.ReadUInt32();
+ DefaultTransitionTime = reader.ReadUInt32();
+ CustomTransitionTimeNumber = reader.ReadUInt32();
+ CustomTransitionTimes = new CustomTransitionTimeObject[CustomTransitionTimeNumber];
+ for (int i = 0; i < CustomTransitionTimes.Length; i++)
+ {
+ CustomTransitionTimes[i] = new CustomTransitionTimeObject(reader);
+ }
+ }
+
+ public class CustomTransitionTimeObject
+ {
+ public uint FromId;
+ public uint ToId;
+ public uint TransitionTime;
+
+ public CustomTransitionTimeObject(BinaryReader reader)
+ {
+ FromId = reader.ReadUInt32();
+ ToId = reader.ReadUInt32();
+ TransitionTime = reader.ReadUInt32();
+ }
+ }
+ }
+
+ public class SwitchGroupObject
+ {
+ public uint SwitchId;
+ public uint GameParameterId;
+ public uint PointNumber;
+ public PointObject[] Points;
+
+ public SwitchGroupObject(BinaryReader reader)
+ {
+ SwitchId = reader.ReadUInt32();
+ GameParameterId = reader.ReadUInt32();
+ PointNumber = reader.ReadUInt32();
+ Points = new PointObject[PointNumber];
+ for (int i = 0; i < Points.Length; i++)
+ {
+ Points[i] = new PointObject(reader);
+ }
+ }
+
+ public class PointObject
+ {
+ public float GameParameterValue;
+ public uint SwitchId;
+ public uint CurveShape;
+
+ public PointObject(BinaryReader reader)
+ {
+ GameParameterValue = reader.ReadSingle();
+ SwitchId = reader.ReadUInt32();
+ CurveShape = reader.ReadUInt32();
+ }
+ }
+ }
+
+ public class GameParameterObject
+ {
+ public uint Id;
+ public float DefaultValue;
+
+ public GameParameterObject(BinaryReader reader)
+ {
+ Id = reader.ReadUInt32();
+ DefaultValue = reader.ReadSingle();
+ }
+ }
+ }
+ }
+}
diff --git a/FModel/PakReader/Parsers/Class/UCurveTable.cs b/FModel/PakReader/Parsers/Class/UCurveTable.cs
index a0b9c7ed..3d81466a 100644
--- a/FModel/PakReader/Parsers/Class/UCurveTable.cs
+++ b/FModel/PakReader/Parsers/Class/UCurveTable.cs
@@ -20,7 +20,7 @@ namespace PakReader.Parsers.Class
for (int i = 0; i < NumRows; i++)
{
int num = 1;
- string RowName = reader.ReadFName().String;
+ string RowName = reader.ReadFName().String ?? "";
string baseName = RowName;
while (RowMap.ContainsKey(RowName))
{
diff --git a/FModel/PakReader/Parsers/Class/UDataTable.cs b/FModel/PakReader/Parsers/Class/UDataTable.cs
index 7bfc0f4d..377d7f4e 100644
--- a/FModel/PakReader/Parsers/Class/UDataTable.cs
+++ b/FModel/PakReader/Parsers/Class/UDataTable.cs
@@ -18,7 +18,7 @@ namespace PakReader.Parsers.Class
for (int i = 0; i < NumRows; i++)
{
int num = 1;
- string RowName = reader.ReadFName().String;
+ string RowName = reader.ReadFName().String ?? "";
string baseName = RowName;
while (RowMap.ContainsKey(RowName))
{
diff --git a/FModel/PakReader/Parsers/Class/USoundWave.cs b/FModel/PakReader/Parsers/Class/USoundWave.cs
index 1d25622c..d457bca8 100644
--- a/FModel/PakReader/Parsers/Class/USoundWave.cs
+++ b/FModel/PakReader/Parsers/Class/USoundWave.cs
@@ -54,7 +54,7 @@ namespace PakReader.Parsers.Class
internal USoundWave(PackageReader reader, Stream ubulk, long ubulkOffset) : base(reader)
{
// if UE4.25+ && Windows -> True
- bStreaming = FModel.Globals.Game.Version >= EPakVersion.PATH_HASH_INDEX ? true : false;
+ bStreaming = FModel.Globals.Game.Version >= EPakVersion.PATH_HASH_INDEX;
bCooked = reader.ReadInt32() != 0;
if (this.TryGetValue("bStreaming", out var v) && v is BoolProperty b)
diff --git a/FModel/PakReader/Parsers/Class/UTexture2D.cs b/FModel/PakReader/Parsers/Class/UTexture2D.cs
index f0ed7a2d..5e353216 100644
--- a/FModel/PakReader/Parsers/Class/UTexture2D.cs
+++ b/FModel/PakReader/Parsers/Class/UTexture2D.cs
@@ -32,11 +32,20 @@ namespace PakReader.Parsers.Class
{
var data = new List(1); // Probably gonna be only one texture anyway
var PixelFormatName = reader.ReadFName();
- while (!PixelFormatName.IsNone)
+ if (FModel.Globals.Game.Version < EPakVersion.INDEX_ENCRYPTION)
{
- _ = reader.ReadInt64(); // SkipOffset
+ _ = reader.ReadInt32(); // SkipOffset
data.Add(new FTexturePlatformData(reader, ubulk, bulkOffset));
- PixelFormatName = reader.ReadFName();
+ reader.ReadFName();
+ }
+ else
+ {
+ while (!PixelFormatName.IsNone)
+ {
+ _ = reader.ReadInt64(); // SkipOffset
+ data.Add(new FTexturePlatformData(reader, ubulk, bulkOffset));
+ PixelFormatName = reader.ReadFName();
+ }
}
PlatformDatas = data.ToArray();
}
diff --git a/FModel/PakReader/Parsers/Objects/UScriptStruct.cs b/FModel/PakReader/Parsers/Objects/UScriptStruct.cs
index 47c9caaa..144e49e0 100644
--- a/FModel/PakReader/Parsers/Objects/UScriptStruct.cs
+++ b/FModel/PakReader/Parsers/Objects/UScriptStruct.cs
@@ -50,7 +50,7 @@ namespace PakReader.Parsers.Objects
"MovieSceneFloatValue" => new FRichCurveKey(reader),
"MovieSceneFloatChannel" => new FMovieSceneFloatChannel(reader),
"MovieSceneEvaluationTemplate" => new FMovieSceneEvaluationTemplate(reader),
- "SkeletalMeshSamplingLODBuiltData" => new FSkeletalMeshSamplingLODBuiltData(reader),
+ //"SkeletalMeshSamplingLODBuiltData" => new FSkeletalMeshSamplingLODBuiltData(reader),
"VectorMaterialInput" => new FVectorMaterialInput(reader),
"ColorMaterialInput" => new FColorMaterialInput(reader),
"ExpressionInput" => new FMaterialInput(reader),
diff --git a/FModel/PakReader/Parsers/PropertyTagData/BaseProperty.cs b/FModel/PakReader/Parsers/PropertyTagData/BaseProperty.cs
index f55d4eed..52127d02 100644
--- a/FModel/PakReader/Parsers/PropertyTagData/BaseProperty.cs
+++ b/FModel/PakReader/Parsers/PropertyTagData/BaseProperty.cs
@@ -21,8 +21,8 @@ namespace PakReader.Parsers.PropertyTagData
"StrProperty" => new StrProperty(reader),
"TextProperty" => new TextProperty(reader),
"InterfaceProperty" => new InterfaceProperty(reader),
- "MulticastDelegateProperty" => new MulticastDelegateProperty(reader, tag),
- "LazyObjectProperty" => new LazyObjectProperty(reader, tag),
+ //"MulticastDelegateProperty" => new MulticastDelegateProperty(reader, tag),
+ //"LazyObjectProperty" => new LazyObjectProperty(reader, tag),
"SoftObjectProperty" => new SoftObjectProperty(reader, readType),
"UInt64Property" => new UInt64Property(reader),
"UInt32Property" => new UInt32Property(reader),
@@ -55,8 +55,8 @@ namespace PakReader.Parsers.PropertyTagData
"StrProperty" => new StrProperty(reader).Value,
"TextProperty" => new TextProperty(reader).Value,
"InterfaceProperty" => new InterfaceProperty(reader).Value,
- "MulticastDelegateProperty" => new MulticastDelegateProperty(reader, tag).Value,
- "LazyObjectProperty" => new LazyObjectProperty(reader, tag).Value,
+ //"MulticastDelegateProperty" => new MulticastDelegateProperty(reader, tag).Value,
+ //"LazyObjectProperty" => new LazyObjectProperty(reader, tag).Value,
"SoftObjectProperty" => new SoftObjectProperty(reader, readType).Value,
"UInt64Property" => new UInt64Property(reader).Value,
"UInt32Property" => new UInt32Property(reader).Value,
diff --git a/FModel/Properties/Resources.Designer.cs b/FModel/Properties/Resources.Designer.cs
index 1d643752..5f6c5535 100644
--- a/FModel/Properties/Resources.Designer.cs
+++ b/FModel/Properties/Resources.Designer.cs
@@ -2780,6 +2780,16 @@ namespace FModel.Properties {
}
}
+ ///
+ /// Recherche une ressource localisée de type System.Byte[].
+ ///
+ public static byte[] Xml {
+ get {
+ object obj = ResourceManager.GetObject("Xml", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
///
/// Recherche une chaîne localisée semblable à Yes.
///
diff --git a/FModel/Properties/Resources.resx b/FModel/Properties/Resources.resx
index 5b4f05c6..333ea28b 100644
--- a/FModel/Properties/Resources.resx
+++ b/FModel/Properties/Resources.resx
@@ -1063,4 +1063,7 @@ It's now the most used free software to leak on Fortnite.
Position / Value
+
+ ..\Resources\Xml.xshd;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
\ No newline at end of file
diff --git a/FModel/Resources/Xml.xshd b/FModel/Resources/Xml.xshd
new file mode 100644
index 00000000..2c0db25b
--- /dev/null
+++ b/FModel/Resources/Xml.xshd
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <!--
+ -->
+
+
+ <!\[CDATA\[
+ ]]>
+
+
+ <!DOCTYPE
+ >
+
+
+ <\?
+ \?>
+
+
+ <
+ >
+
+
+
+ "
+ "|(?=<)
+
+
+ '
+ '|(?=<)
+
+ [\d\w_\-\.]+(?=(\s*=))
+ =
+
+
+
+
+
+
+
+ &
+ [\w\d\#]+
+ ;
+
+
+
+ &
+ [\w\d\#]*
+ #missing ;
+
+
+
\ No newline at end of file
diff --git a/FModel/Utils/Assets.cs b/FModel/Utils/Assets.cs
index ebd80b7b..2f327247 100644
--- a/FModel/Utils/Assets.cs
+++ b/FModel/Utils/Assets.cs
@@ -25,6 +25,7 @@ using System.Text;
using FModel.ViewModels.DataGrid;
using static FModel.Creator.FortniteCreator;
using static FModel.Creator.ValorantCreator;
+using FModel.PakReader;
namespace FModel.Utils
{
@@ -72,6 +73,7 @@ namespace FModel.Utils
switch (selected.PakEntry.GetExtension())
{
case ".ini":
+ case ".txt":
{
using var asset = GetMemoryStream(selected.PakEntry.PakFileName, mount + selected.PakEntry.GetPathWithoutExtension());
asset.Position = 0;
@@ -79,6 +81,14 @@ namespace FModel.Utils
AvalonEditVm.avalonEditViewModel.Set(reader.ReadToEnd(), mount + selected.PakEntry.Name, AvalonEditVm.IniHighlighter);
break;
}
+ case ".xml":
+ {
+ using var asset = GetMemoryStream(selected.PakEntry.PakFileName, mount + selected.PakEntry.GetPathWithoutExtension());
+ asset.Position = 0;
+ using var reader = new StreamReader(asset);
+ AvalonEditVm.avalonEditViewModel.Set(reader.ReadToEnd(), mount + selected.PakEntry.Name, AvalonEditVm.XmlHighlighter);
+ break;
+ }
case ".uproject":
case ".uplugin":
case ".upluginmanifest":
@@ -132,6 +142,21 @@ namespace FModel.Utils
case ".ushaderbytecode":
case ".pck":
break;
+ case ".bnk":
+ {
+ using var asset = GetMemoryStream(selected.PakEntry.PakFileName, mount + selected.PakEntry.GetPathWithoutExtension());
+ asset.Position = 0;
+ BnkReader bnk = new BnkReader(new BinaryReader(asset));
+ Application.Current.Dispatcher.Invoke(delegate
+ {
+ DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Window]", $"Opening Audio Player for {selected.PakEntry.GetNameWithExtension()}");
+ if (!FWindows.IsWindowOpen(Properties.Resources.AudioPlayer))
+ new AudioPlayer().LoadFiles(bnk.AudioFiles, selected.PakEntry.GetPathWithoutFile());
+ else
+ ((AudioPlayer)FWindows.GetOpenedWindow(Properties.Resources.AudioPlayer)).LoadFiles(bnk.AudioFiles, selected.PakEntry.GetPathWithoutFile());
+ });
+ break;
+ }
default:
AvalonEditVm.avalonEditViewModel.Set(GetJsonProperties(selected.PakEntry, mount, true), mount + selected.PakEntry.Name);
break;
diff --git a/FModel/ViewModels/AvalonEdit/AvalonEditViewModel.cs b/FModel/ViewModels/AvalonEdit/AvalonEditViewModel.cs
index f93383f5..f0b95251 100644
--- a/FModel/ViewModels/AvalonEdit/AvalonEditViewModel.cs
+++ b/FModel/ViewModels/AvalonEdit/AvalonEditViewModel.cs
@@ -87,6 +87,7 @@ namespace FModel.ViewModels.AvalonEdit
public static readonly IHighlightingDefinition JsonHighlighter = LoadHighlighter("Json.xshd");
public static readonly IHighlightingDefinition IniHighlighter = LoadHighlighter("Ini.xshd");
+ public static readonly IHighlightingDefinition XmlHighlighter = LoadHighlighter("Xml.xshd");
public static IHighlightingDefinition LoadHighlighter(string resourceName)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
diff --git a/FModel/ViewModels/ListBox/ListBoxViewModel.cs b/FModel/ViewModels/ListBox/ListBoxViewModel.cs
index b1a1506d..9cfb3e01 100644
--- a/FModel/ViewModels/ListBox/ListBoxViewModel.cs
+++ b/FModel/ViewModels/ListBox/ListBoxViewModel.cs
@@ -1,12 +1,14 @@
using FModel.Utils;
using PakReader.Parsers.Objects;
using System;
+using System.Collections.ObjectModel;
namespace FModel.ViewModels.ListBox
{
static class ListBoxVm
{
public static ObservableSortedList gameFiles = new ObservableSortedList();
+ public static ObservableCollection soundFiles = new ObservableCollection();
}
public class ListBoxViewModel : PropertyChangedBase, IComparable
@@ -34,4 +36,39 @@ namespace FModel.ViewModels.ListBox
set { this.SetProperty(ref this._pakEntry, value); }
}
}
+
+ public class ListBoxViewModel2 : PropertyChangedBase
+ {
+ private string _content;
+ public string Content
+ {
+ get { return _content; }
+
+ set { this.SetProperty(ref this._content, value); }
+ }
+
+ private string _fullPath;
+ public string FullPath
+ {
+ get { return _fullPath; }
+
+ set { this.SetProperty(ref this._fullPath, value); }
+ }
+
+ private string _folder;
+ public string Folder
+ {
+ get { return _folder; }
+
+ set { this.SetProperty(ref this._folder, value); }
+ }
+
+ private byte[] _data;
+ public byte[] Data
+ {
+ get { return _data; }
+
+ set { this.SetProperty(ref this._data, value); }
+ }
+ }
}
diff --git a/FModel/Windows/Launcher/FLauncher.xaml.cs b/FModel/Windows/Launcher/FLauncher.xaml.cs
index 7075534f..59251109 100644
--- a/FModel/Windows/Launcher/FLauncher.xaml.cs
+++ b/FModel/Windows/Launcher/FLauncher.xaml.cs
@@ -80,7 +80,7 @@ namespace FModel.Windows.Launcher
}
string spellbreakerFilesPath = Paks.GetSpellbreakPakFilesPath();
- if (!string.IsNullOrEmpty(battlebreakersFilesPath))
+ if (!string.IsNullOrEmpty(spellbreakerFilesPath))
{
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[LauncherInstalled.dat]", $"Spellbreak found at {spellbreakerFilesPath}");
Globals.gNotifier.ShowCustomMessage("Spellbreak", Properties.Resources.PathAutoDetected, "/FModel;component/Resources/spellbreak.ico");
diff --git a/FModel/Windows/SoundPlayer/AudioPlayer.xaml b/FModel/Windows/SoundPlayer/AudioPlayer.xaml
index 0f8f8148..3a4e69ae 100644
--- a/FModel/Windows/SoundPlayer/AudioPlayer.xaml
+++ b/FModel/Windows/SoundPlayer/AudioPlayer.xaml
@@ -4,9 +4,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:properties="clr-namespace:FModel.Properties"
+ xmlns:utils="clr-namespace:FModel.Utils"
mc:Ignorable="d"
Style="{StaticResource {x:Type Window}}"
- Title="{x:Static properties:Resources.AudioPlayer}" Height="300" MinHeight="300" Width="1000" MinWidth="1000"
+ Title="{x:Static properties:Resources.AudioPlayer}"
+ Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={utils:Screens}, ConverterParameter='0.50' }"
+ Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={utils:Screens}, ConverterParameter='0.50' }"
WindowStartupLocation="CenterScreen" Icon="/FModel;component/FModel.ico"
Closed="OnClosed">
@@ -19,85 +22,116 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FModel/Windows/SoundPlayer/AudioPlayer.xaml.cs b/FModel/Windows/SoundPlayer/AudioPlayer.xaml.cs
index 8913e2db..b644f549 100644
--- a/FModel/Windows/SoundPlayer/AudioPlayer.xaml.cs
+++ b/FModel/Windows/SoundPlayer/AudioPlayer.xaml.cs
@@ -1,8 +1,11 @@
using FModel.Discord;
+using FModel.ViewModels.ListBox;
using FModel.ViewModels.SoundPlayer;
using FModel.Windows.SoundPlayer.Visualization;
using Microsoft.Win32;
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
@@ -20,6 +23,7 @@ namespace FModel.Windows.SoundPlayer
private UserControls.SpectrumAnalyzer spectrumAnalyzer;
private UserControls.Timeline timeline;
private UserControls.Timeclock timeclock;
+ private string _oldPlayedSound = string.Empty;
public AudioPlayer()
{
@@ -31,12 +35,14 @@ namespace FModel.Windows.SoundPlayer
private void OnClosed(object sender, EventArgs e)
{
output.Stop();
+ ListBoxVm.soundFiles.Clear();
DiscordIntegration.Restore();
InputFileVm.inputFileViewModel.Reset();
}
private void Startup()
{
DiscordIntegration.SaveCurrentPresence();
+ Sound_LstBox.ItemsSource = ListBoxVm.soundFiles;
AudioPlayer_TabItm.DataContext = InputFileVm.inputFileViewModel;
AudioDevices_CmbBox.ItemsSource = InputFileVm.inputFileViewModel.Devices;
AudioDevices_CmbBox.SelectedItem = InputFileVm.inputFileViewModel.Devices.Where(x => x.DeviceId == Properties.Settings.Default.AudioPlayerDevice).FirstOrDefault();
@@ -54,30 +60,69 @@ namespace FModel.Windows.SoundPlayer
Time.Content = timeline;
}
- private void OnOpenClick(object sender, RoutedEventArgs e)
+ private void OnAddClick(object sender, RoutedEventArgs e)
{
var ofd = new OpenFileDialog
{
Title = Properties.Resources.SelectFile,
Filter = Properties.Resources.OggFilter,
+ Multiselect = true,
InitialDirectory = Properties.Settings.Default.OutputPath + "\\Sounds\\"
};
if ((bool)ofd.ShowDialog())
- LoadFile(ofd.FileName);
+ {
+ foreach (string file in ofd.FileNames)
+ LoadFile(file);
+ }
+ }
+
+ public void LoadFiles(Dictionary files, string gameFolder)
+ {
+ Focus();
+
+ ListBoxVm.soundFiles.Clear();
+ foreach (var (key, value) in files)
+ {
+ ListBoxVm.soundFiles.Add(new ListBoxViewModel2
+ {
+ Content = key,
+ Data = value,
+ FullPath = string.Empty,
+ Folder = gameFolder
+ });
+ }
}
public void LoadFile(string filepath)
{
Focus();
- output.Stop();
- output.Load(filepath);
- output.Play();
+ var item = new ListBoxViewModel2
+ {
+ Content = Path.GetFileName(filepath),
+ Data = null,
+ FullPath = filepath,
+ Folder = string.Empty
+ };
+ ListBoxVm.soundFiles.Add(item);
- string name = Path.GetFileName(filepath);
- InputFileVm.inputFileViewModel.Set(name, output);
- DiscordIntegration.Update(string.Empty, string.Format(Properties.Resources.Listening, name));
- PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/pause.png"));
+ if (ListBoxVm.soundFiles.Count == 1) // auto play if one in queue
+ {
+ output.Stop();
+ output.Load(filepath);
+ output.Play();
+ Sound_LstBox.SelectedIndex = 0;
+
+ string name = Path.GetFileName(filepath);
+ InputFileVm.inputFileViewModel.Set(name, output);
+ DiscordIntegration.Update(string.Empty, string.Format(Properties.Resources.Listening, name));
+ PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/pause.png"));
+ }
+ else
+ {
+ Sound_LstBox.SelectedIndex = ListBoxVm.soundFiles.IndexOf(item);
+ Sound_LstBox.ScrollIntoView(item);
+ }
}
private void UpdateVolume(object sender, RoutedEventArgs e)
@@ -89,7 +134,18 @@ namespace FModel.Windows.SoundPlayer
{
if (output.HasMedia)
{
- if (output.IsPlaying)
+ if (!output.FileName.Equals(_oldPlayedSound))
+ {
+ output.Stop();
+ output.Load(_oldPlayedSound);
+ output.Play();
+
+ string name = Path.GetFileName(_oldPlayedSound);
+ InputFileVm.inputFileViewModel.Set(name, output);
+ DiscordIntegration.Update(string.Empty, string.Format(Properties.Resources.Listening, name));
+ PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/pause.png"));
+ }
+ else if (output.IsPlaying)
{
output.Pause();
PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/play.png"));
@@ -100,6 +156,19 @@ namespace FModel.Windows.SoundPlayer
PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/pause.png"));
}
}
+ else
+ {
+ if (Sound_LstBox.SelectedIndex > -1 && Sound_LstBox.SelectedItem is ListBoxViewModel2 selected)
+ {
+ output.Stop();
+ output.Load(selected.FullPath);
+ output.Play();
+
+ InputFileVm.inputFileViewModel.Set(selected.Content, output);
+ DiscordIntegration.Update(string.Empty, string.Format(Properties.Resources.Listening, selected.Content));
+ PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/pause.png"));
+ }
+ }
}
private void OnStopClick(object sender, RoutedEventArgs e)
@@ -125,5 +194,46 @@ namespace FModel.Windows.SoundPlayer
output.SwapDevice(d);
}
}
+
+ private void OnSelectedItemChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (sender is ListBox listBox && listBox.SelectedItem is ListBoxViewModel2 selectedItem)
+ {
+ // vgmstream convert on select
+ if (string.IsNullOrEmpty(selectedItem.FullPath) && selectedItem.Data != null)
+ {
+ string file = Properties.Settings.Default.OutputPath + "\\vgmstream\\test.exe";
+ if (File.Exists(file))
+ {
+ string folder = Properties.Settings.Default.OutputPath + "\\Sounds\\" + selectedItem.Folder + "\\";
+ Directory.CreateDirectory(folder);
+ File.WriteAllBytes(folder + selectedItem.Content, selectedItem.Data);
+ string newFile = Path.ChangeExtension(folder + selectedItem.Content, ".wav");
+ var vgmstream = Process.Start(new ProcessStartInfo
+ {
+ FileName = file,
+ Arguments = $"-o \"{newFile}\" \"{folder + selectedItem.Content}\"",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ CreateNoWindow = true
+ });
+ vgmstream.WaitForExit();
+ if (vgmstream.ExitCode == 0)
+ {
+ ListBoxVm.soundFiles.Remove(selectedItem);
+ _oldPlayedSound = newFile;
+ LoadFile(newFile);
+ }
+ }
+ }
+ else if (!_oldPlayedSound.Equals(selectedItem.FullPath))
+ _oldPlayedSound = selectedItem.FullPath;
+
+ if (output.HasMedia && output.FileName.Equals(_oldPlayedSound))
+ PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/pause.png"));
+ else
+ PlayPauseImg.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/play.png"));
+ }
+ }
}
}
diff --git a/FModel/Windows/SoundPlayer/Visualization/OutputSource.cs b/FModel/Windows/SoundPlayer/Visualization/OutputSource.cs
index 1ddbd068..624c560d 100644
--- a/FModel/Windows/SoundPlayer/Visualization/OutputSource.cs
+++ b/FModel/Windows/SoundPlayer/Visualization/OutputSource.cs
@@ -12,6 +12,10 @@ namespace FModel.Windows.SoundPlayer.Visualization
public class OutputSource : ISource, IDisposable
{
private string _filename;
+ public string FileName
+ {
+ get { return _filename; }
+ }
private Uri _uri;
private IWaveSource _waveSource;
private ISampleSource _sampleSource;