added Bnk sound reader + xml/txt asset support + fixed FN 1.8 textures

This commit is contained in:
iAmAsval 2020-07-07 20:13:55 +02:00
parent a298cbf7e5
commit 2c3a05684e
18 changed files with 711 additions and 91 deletions

View File

@ -110,6 +110,7 @@
<None Remove="Resources\volume-mute.png" />
<None Remove="Resources\volume-plus.png" />
<None Remove="Resources\wifi-strength-off.ico" />
<None Remove="Resources\Xml.xshd" />
<None Remove="Resources\zip-box.png" />
<None Include="FModel.ico">
<Pack>True</Pack>
@ -120,6 +121,7 @@
<ItemGroup>
<EmbeddedResource Include="Resources\Ini.xshd" />
<EmbeddedResource Include="Resources\Json.xshd" />
<EmbeddedResource Include="Resources\Xml.xshd" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,322 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace FModel.PakReader
{
/// <summary>
/// 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
/// </summary>
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<string, byte[]> AudioFiles;
public BnkReader(BinaryReader reader)
{
DIDXSection didxSection = null;
DATASection dataSection = null;
HIRCSection hircSection = null;
STIDSection stidSection = null;
STMGSection stmgSection = null;
AudioFiles = new Dictionary<string, byte[]>();
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<WemObject> WemFilesRef;
public DIDXSection(BinaryReader reader, long length)
{
WemFilesRef = new List<WemObject>();
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<byte[]> WemFiles;
public DATASection(BinaryReader reader, long position, DIDXSection didxSection)
{
WemFiles = new List<byte[]>(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<uint, string> SoundBanks;
public STIDSection(BinaryReader reader)
{
reader.ReadUInt32();
SoundBankNumber = reader.ReadUInt32();
SoundBanks = new Dictionary<uint, string>(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();
}
}
}
}
}

View File

@ -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))
{

View File

@ -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))
{

View File

@ -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)

View File

@ -32,11 +32,20 @@ namespace PakReader.Parsers.Class
{
var data = new List<FTexturePlatformData>(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();
}

View File

@ -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),

View File

@ -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,

View File

@ -2780,6 +2780,16 @@ namespace FModel.Properties {
}
}
/// <summary>
/// Recherche une ressource localisée de type System.Byte[].
/// </summary>
public static byte[] Xml {
get {
object obj = ResourceManager.GetObject("Xml", resourceCulture);
return ((byte[])(obj));
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Yes.
/// </summary>

View File

@ -1063,4 +1063,7 @@ It's now the most used free software to leak on Fortnite.</value>
<data name="WithPosition" xml:space="preserve">
<value>Position / Value</value>
</data>
<data name="Xml" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Xml.xshd;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</data>
</root>

63
FModel/Resources/Xml.xshd Normal file
View File

@ -0,0 +1,63 @@
<SyntaxDefinition name="XML" extensions=".xml;.xsl;.xslt;.xsd;.manifest;.config;.addin;.xshd;.wxs;.wxi;.wxl;.proj;.csproj;.vbproj;.ilproj;.booproj;.build;.xfrm;.targets;.xaml;.xpt;.xft;.map;.wsdl;.disco;.ps1xml;.nuspec" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
<Color foreground="Green" name="Comment" exampleText="&lt;!-- comment --&gt;" />
<Color foreground="Blue" name="CData" exampleText="&lt;![CDATA[data]]&gt;" />
<Color foreground="Blue" name="DocType" exampleText="&lt;!DOCTYPE rootElement&gt;" />
<Color foreground="#89DDFF" name="XmlDeclaration" exampleText='&lt;?xml version="1.0"?&gt;' />
<Color foreground="#F07178" name="XmlTag" exampleText='&lt;tag attribute="value" /&gt;' />
<Color foreground="#C792DD" name="AttributeName" exampleText='&lt;tag attribute="value" /&gt;' />
<Color foreground="#C3E88D" name="AttributeValue" exampleText='&lt;tag attribute="value" /&gt;' />
<Color foreground="Teal" name="Entity" exampleText="index.aspx?a=1&amp;amp;b=2" />
<Color foreground="Olive" name="BrokenEntity" exampleText="index.aspx?a=1&amp;b=2" />
<RuleSet>
<Span color="Comment" multiline="true">
<Begin>&lt;!--</Begin>
<End>--&gt;</End>
</Span>
<Span color="CData" multiline="true">
<Begin>&lt;!\[CDATA\[</Begin>
<End>]]&gt;</End>
</Span>
<Span color="DocType" multiline="true">
<Begin>&lt;!DOCTYPE</Begin>
<End>&gt;</End>
</Span>
<Span color="XmlDeclaration" multiline="true">
<Begin>&lt;\?</Begin>
<End>\?&gt;</End>
</Span>
<Span color="XmlTag" multiline="true">
<Begin>&lt;</Begin>
<End>&gt;</End>
<RuleSet>
<!-- Treat the position before '<' as end, as that's not a valid character
in attribute names and indicates the user forgot a closing quote. -->
<Span color="AttributeValue" multiline="true" ruleSet="EntitySet">
<Begin>"</Begin>
<End>"|(?=&lt;)</End>
</Span>
<Span color="AttributeValue" multiline="true" ruleSet="EntitySet">
<Begin>'</Begin>
<End>'|(?=&lt;)</End>
</Span>
<Rule color="AttributeName">[\d\w_\-\.]+(?=(\s*=))</Rule>
<Rule color="AttributeValue">=</Rule>
</RuleSet>
</Span>
<Import ruleSet="EntitySet"/>
</RuleSet>
<RuleSet name="EntitySet">
<Rule color="Entity">
&amp;
[\w\d\#]+
;
</Rule>
<Rule color="BrokenEntity">
&amp;
[\w\d\#]*
#missing ;
</Rule>
</RuleSet>
</SyntaxDefinition>

View File

@ -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<Window>(Properties.Resources.AudioPlayer))
new AudioPlayer().LoadFiles(bnk.AudioFiles, selected.PakEntry.GetPathWithoutFile());
else
((AudioPlayer)FWindows.GetOpenedWindow<Window>(Properties.Resources.AudioPlayer)).LoadFiles(bnk.AudioFiles, selected.PakEntry.GetPathWithoutFile());
});
break;
}
default:
AvalonEditVm.avalonEditViewModel.Set(GetJsonProperties(selected.PakEntry, mount, true), mount + selected.PakEntry.Name);
break;

View File

@ -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();

View File

@ -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<ListBoxViewModel> gameFiles = new ObservableSortedList<ListBoxViewModel>();
public static ObservableCollection<ListBoxViewModel2> soundFiles = new ObservableCollection<ListBoxViewModel2>();
}
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); }
}
}
}

View File

@ -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");

View File

@ -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">
<Grid>
@ -19,85 +22,116 @@
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TabControl Grid.Row="0" Grid.Column="0" BorderBrush="{x:Null}">
<TabItem x:Name="AudioPlayer_TabItm" Header="{x:Static properties:Resources.FFile}">
<Grid>
<Grid.Background>
<LinearGradientBrush EndPoint="0.25,0.9" StartPoint="0.75,1">
<GradientStop Color="#FF252D36" Offset="0"/>
<GradientStop Color="#FF252E38" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="2"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Column="1" Grid.Row="1" Content="{x:Static properties:Resources.AudioDevices}"/>
<Label Grid.Column="1" Grid.Row="2" Content="{x:Static properties:Resources.Name}"/>
<Label Grid.Column="1" Grid.Row="3" Content="{x:Static properties:Resources.BytesPerSecond}"/>
<Label Grid.Column="1" Grid.Row="4" Content="{x:Static properties:Resources.Duration}"/>
<Label Grid.Column="1" Grid.Row="5" Content="{x:Static properties:Resources.Volume}"/>
<Button Grid.Column="3" Grid.Row="7" Height="20" Width="40" Click="OnPlayPauseClick"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,50,-1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="1"/>
</Grid.ColumnDefinitions>
<TabControl Grid.Row="0" BorderBrush="{x:Null}">
<TabItem x:Name="AudioPlayer_TabItm" Header="{x:Static properties:Resources.FFile}">
<Grid>
<Grid.Background>
<LinearGradientBrush EndPoint="0.25,0.9" StartPoint="0.75,1">
<GradientStop Color="#FF252D36" Offset="0"/>
<GradientStop Color="#FF252E38" Offset="1"/>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
</Grid.ColumnDefinitions>
<Image x:Name="PlayPauseImg" Grid.Column="1" Source="/FModel;component/Resources/play.png"
Width="14" Stretch="UniformToFill" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</Button>
<Button Grid.Column="3" Grid.Row="7" Height="20" Width="40" Click="OnStopClick"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,0,-1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="1"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Grid.Row="1" Content="{x:Static properties:Resources.AudioDevices}"/>
<Label Grid.Column="1" Grid.Row="2" Content="{x:Static properties:Resources.Name}"/>
<Label Grid.Column="1" Grid.Row="3" Content="{x:Static properties:Resources.BytesPerSecond}"/>
<Label Grid.Column="1" Grid.Row="4" Content="{x:Static properties:Resources.Duration}"/>
<Label Grid.Column="1" Grid.Row="5" Content="{x:Static properties:Resources.Volume}"/>
<Image Grid.Column="1" Source="/FModel;component/Resources/stop.png"
Width="14" Stretch="UniformToFill" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</Button>
<Button Grid.Column="1" Grid.Row="7" Content="{x:Static properties:Resources.Open}"
Click="OnOpenClick" Height="19" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" />
<ComboBox x:Name="AudioDevices_CmbBox" Grid.Column="3" Grid.Row="1"
<ComboBox x:Name="AudioDevices_CmbBox" Grid.Column="3" Grid.Row="1"
DisplayMemberPath="Name" SelectionChanged="OnSelectionChanged"/>
<TextBox Grid.Column="3" Grid.Row="2" TextWrapping="NoWrap"
<TextBox Grid.Column="3" Grid.Row="2" TextWrapping="NoWrap"
Style="{StaticResource ResourceKey=SelectableTextBox}" Text="{Binding Content, Mode=TwoWay}"/>
<TextBox Grid.Column="3" Grid.Row="3" TextWrapping="NoWrap"
<TextBox Grid.Column="3" Grid.Row="3" TextWrapping="NoWrap"
Style="{StaticResource ResourceKey=SelectableTextBox}" Text="{Binding Bytes, Mode=TwoWay}"/>
<TextBox Grid.Column="3" Grid.Row="4" TextWrapping="NoWrap"
<TextBox Grid.Column="3" Grid.Row="4" TextWrapping="NoWrap"
Style="{StaticResource ResourceKey=SelectableTextBox}" Text="{Binding Duration, Mode=TwoWay}"/>
<Slider Grid.Column="3" Grid.Row="5"
<Slider Grid.Column="3" Grid.Row="5"
Style="{StaticResource Horizontal_Slider}" TickPlacement="None" VerticalAlignment="Center"
Minimum="0" Maximum="1" TickFrequency="0.1" Value="{Binding Volume, Mode=TwoWay}"
Thumb.DragCompleted="UpdateVolume"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
</TabItem>
</TabControl>
<Grid Grid.Row="2" Grid.Column="2" Margin="10,20,10,0" >
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2"/>
</Grid.RowDefinitions>
<ListBox x:Name="Sound_LstBox" Grid.Row="0" BorderBrush="#FF333C46" SelectionChanged="OnSelectedItemChanged"/>
</Grid>
<Grid Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="2" Grid.Row="1" Height="20" MinWidth="40" Click="OnAddClick"
Content="{x:Static properties:Resources.Add}" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="0,0,100,-1" Padding="5,1,5,1"/>
<Button Grid.Column="2" Grid.Row="1" Height="20" Width="40" Click="OnPlayPauseClick"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,50,-1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="1"/>
</Grid.ColumnDefinitions>
<Image x:Name="PlayPauseImg" Grid.Column="1" Source="/FModel;component/Resources/play.png"
Width="14" Stretch="UniformToFill" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</Button>
<Button Grid.Column="2" Grid.Row="1" Height="20" Width="40" Click="OnStopClick"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,0,-1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1"/>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="1"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="1" Source="/FModel;component/Resources/stop.png"
Width="14" Stretch="UniformToFill" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</Grid>
</Button>
</Grid>
</Grid>
<Grid Grid.Row="2" Grid.Column="1" Margin="10,20,10,0" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>

View File

@ -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<string, byte[]> 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"));
}
}
}
}

View File

@ -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;