mirror of
https://github.com/4sval/FModel.git
synced 2026-06-21 23:40:12 -05:00
Merge branch 'dev' into dotnet-10
This commit is contained in:
commit
76715cdbfd
2
.github/workflows/qa.yml
vendored
2
.github/workflows/qa.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: .NET Restore
|
||||
run: dotnet restore "./FModel/FModel.slnx"
|
||||
run: dotnet restore "./FModel/FModel.slnx" -r win-x64
|
||||
|
||||
- name: .NET Publish
|
||||
run: dotnet publish "./FModel/FModel.csproj" -c Release --no-restore --no-self-contained -r win-x64 -f net8.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
</ResourceDictionary>
|
||||
|
||||
<ResourceDictionary Source="pack://application:,,,/AdonisUI;component/ColorSchemes/Dark.xaml"/>
|
||||
<ResourceDictionary Source="pack://application:,,,/AdonisUI.ClassicTheme;component/Resources.xaml"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public partial class App
|
|||
var createMe = false;
|
||||
if (!Directory.Exists(UserSettings.Default.OutputDirectory))
|
||||
{
|
||||
var currentDir = Directory.GetCurrentDirectory();
|
||||
var currentDir = AppContext.BaseDirectory;
|
||||
try
|
||||
{
|
||||
var outputDir = Directory.CreateDirectory(Path.Combine(currentDir, "Output"));
|
||||
|
|
@ -92,6 +92,12 @@ public partial class App
|
|||
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.CodeDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
UserSettings.Default.CodeDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
|
||||
{
|
||||
createMe = true;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ public static class Constants
|
|||
|
||||
public const string _NO_PRESET_TRIGGER = "Hand Made";
|
||||
|
||||
// Common issues
|
||||
public const string MAPPING_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/418";
|
||||
public const string AUDIO_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/658";
|
||||
public const string RADA_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/422";
|
||||
public const string VERSION_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/425";
|
||||
|
||||
public static int PALETTE_LENGTH => COLOR_PALETTE.Length;
|
||||
public static readonly Vector3[] COLOR_PALETTE =
|
||||
{
|
||||
|
|
|
|||
47
FModel/Creator/Bases/FN/BaseAssembledMesh.cs
Normal file
47
FModel/Creator/Bases/FN/BaseAssembledMesh.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Creator.Bases.FN;
|
||||
|
||||
public class BaseAssembledMesh : UCreator
|
||||
{
|
||||
public BaseAssembledMesh(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FInstancedStruct[] additionalData, "AdditionalData"))
|
||||
{
|
||||
foreach (var data in additionalData)
|
||||
{
|
||||
if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") == true)
|
||||
{
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using var c = new SKCanvas(ret);
|
||||
|
||||
switch (Style)
|
||||
{
|
||||
case EIconStyle.NoBackground:
|
||||
DrawPreview(c);
|
||||
break;
|
||||
default:
|
||||
DrawBackground(c);
|
||||
DrawPreview(c);
|
||||
break;
|
||||
}
|
||||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ public class BaseIcon : UCreator
|
|||
|
||||
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||
{
|
||||
GetRarity(dataList);
|
||||
GetSeries(dataList);
|
||||
Preview = Utils.GetBitmap(dataList);
|
||||
}
|
||||
|
|
@ -139,6 +140,12 @@ public class BaseIcon : UCreator
|
|||
GetSeries(export);
|
||||
}
|
||||
|
||||
private void GetRarity(FInstancedStruct[] s)
|
||||
{
|
||||
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out EFortRarity _, "Rarity") == true) is { } dl)
|
||||
GetRarity(dl.NonConstStruct.Get<EFortRarity>("Rarity"));
|
||||
}
|
||||
|
||||
private void GetSeries(FInstancedStruct[] s)
|
||||
{
|
||||
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ public class BaseIconStats : BaseIcon
|
|||
weaponRowValue.TryGetValue(out float dmgPb, "DmgPB"); //Damage at point blank
|
||||
weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge"); //Max damage a weapon can do in a single hit, usually used for shotguns
|
||||
weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"); //Headshot multiplier
|
||||
weaponRowValue.TryGetValue(out float envDmgPb, "EnvDmgPB"); //Structure damage at point blank
|
||||
weaponRowValue.TryGetValue(out int clipSize, "ClipSize"); //Item magazine size
|
||||
weaponRowValue.TryGetValue(out float firingRate, "FiringRate"); //Item firing rate, value is shots per second
|
||||
weaponRowValue.TryGetValue(out float swingTime, "SwingTime"); //Item swing rate, value is swing per second
|
||||
|
|
@ -115,6 +116,15 @@ public class BaseIconStats : BaseIcon
|
|||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 160));
|
||||
}
|
||||
}
|
||||
{
|
||||
var envdmgmultiplier = bpc != 0f ? bpc : 1;
|
||||
if (envDmgPb != 0f)
|
||||
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "11AF67134E0F4E27E5E588806AB475BE", "Structure Damage"), envDmgPb * envdmgmultiplier, 160));
|
||||
}
|
||||
}
|
||||
|
||||
if (clipSize > 999f || clipSize == 0f)
|
||||
{
|
||||
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), Utils.GetLocalizedResource("", "0FAE8E5445029F2AA209ADB0FE49B23C", "Infinite"), -1));
|
||||
|
|
|
|||
|
|
@ -100,12 +100,14 @@ public class CreatorPackage : IDisposable
|
|||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortAlterableItemDefinition":
|
||||
case "SproutHousingItemDefinition":
|
||||
case "SparksKeyboardItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortConsumableItemDefinition":
|
||||
case "SproutBuildingItemDefinition":
|
||||
case "StWFortAccoladeItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortFOBCoreDecoItemDefinition":
|
||||
|
|
@ -163,6 +165,9 @@ public class CreatorPackage : IDisposable
|
|||
case "JunoAthenaDanceItemOverrideDefinition":
|
||||
creator = new BaseJuno(_object.Value, _style);
|
||||
return true;
|
||||
case "AssembledMeshSchema":
|
||||
creator = new BaseAssembledMesh(_object.Value, _style);
|
||||
return true;
|
||||
case "FortTandemCharacterData":
|
||||
creator = new BaseTandem(_object.Value, _style);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public enum SettingsOut
|
|||
public enum EStatusKind
|
||||
{
|
||||
Ready, // ready
|
||||
Configuring, // waiting for user input
|
||||
Loading, // doing stuff
|
||||
Stopping, // trying to stop
|
||||
Stopped, // stopped
|
||||
|
|
@ -107,6 +108,7 @@ public enum EBulkType
|
|||
Animations = 1 << 4,
|
||||
Audio = 1 << 5,
|
||||
Code = 1 << 6,
|
||||
Raw = 1 << 7,
|
||||
}
|
||||
|
||||
public enum EAssetCategory : uint
|
||||
|
|
@ -157,5 +159,15 @@ public enum EAssetCategory : uint
|
|||
AudioEvent = Media + 5,
|
||||
Particle = AssetCategoryExtensions.CategoryBase + (9 << 16),
|
||||
GameSpecific = AssetCategoryExtensions.CategoryBase + (10 << 16),
|
||||
Borderlands4 = GameSpecific + 1,
|
||||
Borderlands = GameSpecific + 1,
|
||||
Aion2 = GameSpecific + 2,
|
||||
RocoKingdomWorld = GameSpecific + 3,
|
||||
DeltaForce = GameSpecific + 4,
|
||||
LegoBatman = GameSpecific + 5,
|
||||
}
|
||||
|
||||
public enum EUnluacMode
|
||||
{
|
||||
Decompile,
|
||||
Disassemble,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public static class AvalonExtensions
|
|||
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
|
||||
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
|
||||
private static readonly IHighlightingDefinition _verseHighlighter = LoadHighlighter("Verse.xshd");
|
||||
private static readonly IHighlightingDefinition _luaHighlighter = LoadHighlighter("Lua.xshd");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IHighlightingDefinition LoadHighlighter(string resourceName)
|
||||
|
|
@ -29,6 +30,9 @@ public static class AvalonExtensions
|
|||
{
|
||||
switch (ext)
|
||||
{
|
||||
case "lua":
|
||||
case "luac":
|
||||
return _luaHighlighter;
|
||||
case "ini":
|
||||
case "csv":
|
||||
return _iniHighlighter;
|
||||
|
|
@ -45,6 +49,7 @@ public static class AvalonExtensions
|
|||
case "bat":
|
||||
case "txt":
|
||||
case "pem":
|
||||
case "js":
|
||||
case "po":
|
||||
return null;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
<None Remove="Resources\Xml.xshd" />
|
||||
<None Remove="Resources\Cpp.xshd" />
|
||||
<None Remove="Resources\Changelog.xshd" />
|
||||
<None Remove="Resources\Lua.xshd" />
|
||||
<None Remove="Resources\unix.png" />
|
||||
<None Remove="Resources\linux.png" />
|
||||
<None Remove="Resources\stateofdecay2.png" />
|
||||
|
|
@ -129,6 +130,7 @@
|
|||
<EmbeddedResource Include="Resources\Verse.xshd" />
|
||||
<EmbeddedResource Include="Resources\Xml.xshd" />
|
||||
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
||||
<EmbeddedResource Include="Resources\Lua.xshd" />
|
||||
<EmbeddedResource Include="Resources\Changelog.xshd" />
|
||||
<EmbeddedResource Include="Resources\default.frag" />
|
||||
<EmbeddedResource Include="Resources\default.vert" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
|
|
@ -62,7 +63,7 @@ public static class Helper
|
|||
GetOpenedWindow<T>(windowName).Close();
|
||||
}
|
||||
|
||||
private static bool IsWindowOpen<T>(string name = "") where T : Window
|
||||
public static bool IsWindowOpen<T>(string name = "") where T : Window
|
||||
{
|
||||
return string.IsNullOrEmpty(name)
|
||||
? Application.Current.Windows.OfType<T>().Any()
|
||||
|
|
@ -111,4 +112,24 @@ public static class Helper
|
|||
const float ratio = 180f / MathF.PI;
|
||||
return radians * ratio;
|
||||
}
|
||||
|
||||
public static string GetGameName(string path)
|
||||
{
|
||||
// install_folder/
|
||||
// ├─ Engine/
|
||||
// ├─ GameName/
|
||||
// │ ├─ Binaries/
|
||||
// │ ├─ Content/
|
||||
// │ │ ├─ Paks/
|
||||
// our goal is to get the GameName folder
|
||||
var dir = new DirectoryInfo(path);
|
||||
if (dir.Name.Equals("Paks", StringComparison.InvariantCulture) && dir.Parent is { Parent: not null } &&
|
||||
dir.Parent.Name.Equals("Content", StringComparison.InvariantCulture) &&
|
||||
dir.Parent.Parent.GetDirectories().Any(x => x.Name == "Binaries"))
|
||||
{
|
||||
return dir.Parent.Parent.Name;
|
||||
}
|
||||
|
||||
return dir.Name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" Closing="OnClosing" Loaded="OnLoaded" PreviewKeyDown="OnWindowKeyDown"
|
||||
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.95'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.90'}">
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.90'}"
|
||||
AllowDrop="True">
|
||||
<Window.TaskbarItemInfo>
|
||||
<TaskbarItemInfo ProgressValue="1.0">
|
||||
<TaskbarItemInfo.ProgressState>
|
||||
|
|
@ -628,6 +629,10 @@
|
|||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Configuring}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Stopped}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
|
|
@ -709,5 +714,7 @@
|
|||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
<controls:DropOverlay Grid.RowSpan="99"
|
||||
Panel.ZIndex="1000" />
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
|
@ -123,7 +124,8 @@ public partial class MainWindow
|
|||
{
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
_discordHandler.Initialize(_applicationView.GameDisplayName);
|
||||
})
|
||||
}),
|
||||
UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask
|
||||
).ConfigureAwait(false);
|
||||
|
||||
#if DEBUG
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<Color name="BooleanConstants" foreground="#569cd6" fontWeight="bold" />
|
||||
|
||||
<RuleSet ignoreCase="false">
|
||||
<Rule color="Comment">(\/\/.*|\/\*[\s\S]*?\*\/)</Rule>
|
||||
<Span color="String" begin=""" end=""" />
|
||||
<!-- UE Macros -->
|
||||
<Keywords color="UEMacro">
|
||||
|
|
@ -44,10 +45,19 @@
|
|||
<Word>Int16</Word>
|
||||
<Word>Int32</Word>
|
||||
<Word>Int64</Word>
|
||||
<Word>int8</Word>
|
||||
<Word>int16</Word>
|
||||
<Word>int32</Word>
|
||||
<Word>int64</Word>
|
||||
<Word>uint</Word>
|
||||
<Word>UInt8</Word>
|
||||
<Word>UInt16</Word>
|
||||
<Word>UInt32</Word>
|
||||
<Word>UInt64</Word>
|
||||
<Word>uint8</Word>
|
||||
<Word>uint16</Word>
|
||||
<Word>uint32</Word>
|
||||
<Word>uint64</Word>
|
||||
<Word>float</Word>
|
||||
<Word>double</Word>
|
||||
<Word>bool</Word>
|
||||
|
|
@ -83,6 +93,7 @@
|
|||
<Word>inline</Word>
|
||||
<Word>constexpr</Word>
|
||||
<Word>default</Word>
|
||||
<Word>&&</Word>
|
||||
</Keywords>
|
||||
|
||||
<Keywords color="Pointer">
|
||||
|
|
@ -120,8 +131,6 @@
|
|||
|
||||
<Rule color="Brace">[\[\]\{\}]</Rule>
|
||||
|
||||
<Rule color="Comment">(\/\/.*|\/\*[\s\S]*?\*\/)</Rule>
|
||||
|
||||
<!-- Template Functions -->
|
||||
<Rule color="Function">\b[A-Za-z_][A-Za-z0-9_]*\b(?=<)</Rule>
|
||||
|
||||
|
|
|
|||
248
FModel/Resources/Lua.xshd
Normal file
248
FModel/Resources/Lua.xshd
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
<SyntaxDefinition name="Lua" extensions=".lua;.luac" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008">
|
||||
<Color name="Keyword1" foreground="#d8a0df" fontWeight="bold" />
|
||||
<Color name="Keyword2" foreground="#569cd6" fontWeight="bold" />
|
||||
<Color name="Self" foreground="#569cd6" fontStyle="italic" />
|
||||
<Color name="Function" foreground="#dcdcaa" />
|
||||
<Color name="ObjectName" foreground="#4ec9b0" />
|
||||
<Color name="Property" foreground="#abd4a2" />
|
||||
<Color name="String" foreground="#f4ab6b" />
|
||||
<Color name="Number" foreground="#ff9e64" />
|
||||
<Color name="Comment" foreground="#565f89" fontStyle="italic" />
|
||||
<Color name="Punctuation" foreground="#89ddff" />
|
||||
<Color name="Constant" foreground="#e0af68" />
|
||||
<Color name="Metamethod" foreground="#bb9af7" fontStyle="italic" />
|
||||
<Color name="GotoLabel" foreground="#7dcfff" />
|
||||
<Color name="Discard" foreground="#3b4261" />
|
||||
|
||||
<RuleSet>
|
||||
<Rule color="Comment">--\[\[(.|\n)*?\]\]</Rule>
|
||||
<Rule color="Comment">--.*$</Rule>
|
||||
|
||||
<Rule color="String">\[\[(.|\n)*?\]\]</Rule>
|
||||
<Rule color="String">"([^"\\]|\\.)*"</Rule>
|
||||
<Rule color="String">'([^'\\]|\\.)*'</Rule>
|
||||
|
||||
<Rule color="Number">\b0[xX][0-9A-Fa-f]+\b</Rule>
|
||||
<Rule color="Number">\b\d+\.\d+([eE][+-]?\d+)?\b</Rule>
|
||||
<Rule color="Number">\b\d+[eE][+-]?\d+\b</Rule>
|
||||
<Rule color="Number">\b\d+\b</Rule>
|
||||
|
||||
<Keywords color="Keyword1">
|
||||
<Word>and</Word>
|
||||
<Word>break</Word>
|
||||
<Word>do</Word>
|
||||
<Word>else</Word>
|
||||
<Word>elseif</Word>
|
||||
<Word>end</Word>
|
||||
<Word>for</Word>
|
||||
<Word>function</Word>
|
||||
<Word>goto</Word>
|
||||
<Word>if</Word>
|
||||
<Word>in</Word>
|
||||
<Word>or</Word>
|
||||
<Word>repeat</Word>
|
||||
<Word>return</Word>
|
||||
<Word>then</Word>
|
||||
<Word>until</Word>
|
||||
<Word>while</Word>
|
||||
</Keywords>
|
||||
|
||||
<Keywords color="Keyword2">
|
||||
<Word>false</Word>
|
||||
<Word>local</Word>
|
||||
<Word>nil</Word>
|
||||
<Word>not</Word>
|
||||
<Word>true</Word>
|
||||
</Keywords>
|
||||
|
||||
<Keywords color="Self">
|
||||
<Word>self</Word>
|
||||
<Word>_G</Word>
|
||||
<Word>_ENV</Word>
|
||||
</Keywords>
|
||||
|
||||
<Keywords color="Function">
|
||||
<Word>assert</Word>
|
||||
<Word>collectgarbage</Word>
|
||||
<Word>dofile</Word>
|
||||
<Word>error</Word>
|
||||
<Word>getmetatable</Word>
|
||||
<Word>ipairs</Word>
|
||||
<Word>load</Word>
|
||||
<Word>loadfile</Word>
|
||||
<Word>module</Word>
|
||||
<Word>next</Word>
|
||||
<Word>pairs</Word>
|
||||
<Word>pcall</Word>
|
||||
<Word>print</Word>
|
||||
<Word>rawequal</Word>
|
||||
<Word>rawget</Word>
|
||||
<Word>rawlen</Word>
|
||||
<Word>rawset</Word>
|
||||
<Word>require</Word>
|
||||
<Word>select</Word>
|
||||
<Word>setmetatable</Word>
|
||||
<Word>tonumber</Word>
|
||||
<Word>tostring</Word>
|
||||
<Word>type</Word>
|
||||
<Word>unpack</Word>
|
||||
<Word>xpcall</Word>
|
||||
<Word>bit32</Word>
|
||||
<Word>coroutine</Word>
|
||||
<Word>debug</Word>
|
||||
<Word>io</Word>
|
||||
<Word>math</Word>
|
||||
<Word>os</Word>
|
||||
<Word>package</Word>
|
||||
<Word>string</Word>
|
||||
<Word>table</Word>
|
||||
<Word>utf8</Word>
|
||||
<Word>abs</Word>
|
||||
<Word>acos</Word>
|
||||
<Word>asin</Word>
|
||||
<Word>atan</Word>
|
||||
<Word>atan2</Word>
|
||||
<Word>ceil</Word>
|
||||
<Word>cos</Word>
|
||||
<Word>cosh</Word>
|
||||
<Word>deg</Word>
|
||||
<Word>exp</Word>
|
||||
<Word>floor</Word>
|
||||
<Word>fmod</Word>
|
||||
<Word>frexp</Word>
|
||||
<Word>huge</Word>
|
||||
<Word>ldexp</Word>
|
||||
<Word>log</Word>
|
||||
<Word>log10</Word>
|
||||
<Word>max</Word>
|
||||
<Word>maxinteger</Word>
|
||||
<Word>min</Word>
|
||||
<Word>mininteger</Word>
|
||||
<Word>modf</Word>
|
||||
<Word>pi</Word>
|
||||
<Word>pow</Word>
|
||||
<Word>rad</Word>
|
||||
<Word>random</Word>
|
||||
<Word>randomseed</Word>
|
||||
<Word>sin</Word>
|
||||
<Word>sinh</Word>
|
||||
<Word>sqrt</Word>
|
||||
<Word>tan</Word>
|
||||
<Word>tanh</Word>
|
||||
<Word>tointeger</Word>
|
||||
<Word>ult</Word>
|
||||
<Word>byte</Word>
|
||||
<Word>char</Word>
|
||||
<Word>dump</Word>
|
||||
<Word>find</Word>
|
||||
<Word>format</Word>
|
||||
<Word>gmatch</Word>
|
||||
<Word>gsub</Word>
|
||||
<Word>len</Word>
|
||||
<Word>lower</Word>
|
||||
<Word>match</Word>
|
||||
<Word>pack</Word>
|
||||
<Word>packsize</Word>
|
||||
<Word>rep</Word>
|
||||
<Word>reverse</Word>
|
||||
<Word>sub</Word>
|
||||
<Word>upper</Word>
|
||||
<Word>concat</Word>
|
||||
<Word>insert</Word>
|
||||
<Word>maxn</Word>
|
||||
<Word>move</Word>
|
||||
<Word>remove</Word>
|
||||
<Word>sort</Word>
|
||||
<Word>close</Word>
|
||||
<Word>flush</Word>
|
||||
<Word>input</Word>
|
||||
<Word>lines</Word>
|
||||
<Word>open</Word>
|
||||
<Word>output</Word>
|
||||
<Word>popen</Word>
|
||||
<Word>read</Word>
|
||||
<Word>seek</Word>
|
||||
<Word>setvbuf</Word>
|
||||
<Word>tmpfile</Word>
|
||||
<Word>write</Word>
|
||||
<Word>clock</Word>
|
||||
<Word>date</Word>
|
||||
<Word>difftime</Word>
|
||||
<Word>execute</Word>
|
||||
<Word>exit</Word>
|
||||
<Word>getenv</Word>
|
||||
<Word>rename</Word>
|
||||
<Word>setlocale</Word>
|
||||
<Word>time</Word>
|
||||
<Word>create</Word>
|
||||
<Word>isyieldable</Word>
|
||||
<Word>resume</Word>
|
||||
<Word>running</Word>
|
||||
<Word>status</Word>
|
||||
<Word>wrap</Word>
|
||||
<Word>yield</Word>
|
||||
<Word>arshift</Word>
|
||||
<Word>band</Word>
|
||||
<Word>bnot</Word>
|
||||
<Word>bor</Word>
|
||||
<Word>btest</Word>
|
||||
<Word>bxor</Word>
|
||||
<Word>extract</Word>
|
||||
<Word>lrotate</Word>
|
||||
<Word>lshift</Word>
|
||||
<Word>replace</Word>
|
||||
<Word>rrotate</Word>
|
||||
<Word>rshift</Word>
|
||||
<Word>charpattern</Word>
|
||||
<Word>codepoint</Word>
|
||||
<Word>codes</Word>
|
||||
<Word>offset</Word>
|
||||
<Word>gethook</Word>
|
||||
<Word>getinfo</Word>
|
||||
<Word>getlocal</Word>
|
||||
<Word>getregistry</Word>
|
||||
<Word>getupvalue</Word>
|
||||
<Word>getuservalue</Word>
|
||||
<Word>sethook</Word>
|
||||
<Word>setlocal</Word>
|
||||
<Word>setupvalue</Word>
|
||||
<Word>setuservalue</Word>
|
||||
<Word>traceback</Word>
|
||||
<Word>upvalueid</Word>
|
||||
<Word>upvaluejoin</Word>
|
||||
<Word>cpath</Word>
|
||||
<Word>loaded</Word>
|
||||
<Word>loadlib</Word>
|
||||
<Word>path</Word>
|
||||
<Word>preload</Word>
|
||||
<Word>searchers</Word>
|
||||
<Word>searchpath</Word>
|
||||
<Word>seeall</Word>
|
||||
</Keywords>
|
||||
|
||||
<Rule color="Punctuation">(\|)|(<<)|(>>)|(\/\/)|(==)|(~=)|(<=)|(>=)|(<)|(>)|(=)|(\()|(\))|(\{)|(\})|(\[)|(\])|(::)|(:)|(;)|(,)|(\.\.\.)|(\.\.)|(\.)|[+\-*%\^#&~]</Rule>
|
||||
|
||||
<Rule color="Metamethod">__[A-Za-z_][A-Za-z0-9_]*__</Rule>
|
||||
|
||||
<Rule color="GotoLabel">(?<=::)[A-Za-z_][A-Za-z0-9_]*(?=::)</Rule>
|
||||
|
||||
<Rule color="Discard">(?<![A-Za-z0-9_])_(?![A-Za-z0-9_])</Rule>
|
||||
|
||||
<Rule color="ObjectName">(?<=function\s)[A-Za-z_][A-Za-z0-9_]*(?=\.)</Rule>
|
||||
<Rule color="ObjectName">(?<=function\s)[A-Za-z_][A-Za-z0-9_]*(?=:)</Rule>
|
||||
|
||||
<Rule color="Function">(?<=function\s)[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
|
||||
|
||||
<Rule color="ObjectName">[A-Za-z_][A-Za-z0-9_]*(?=\.)</Rule>
|
||||
<Rule color="ObjectName">[A-Za-z_][A-Za-z0-9_]*(?=:)</Rule>
|
||||
|
||||
<Rule color="Function">(?<=:)[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
|
||||
<Rule color="Function">(?<=\.)[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
|
||||
<Rule color="Function">\b[A-Za-z_][A-Za-z0-9_]*(?=\s*\()</Rule>
|
||||
|
||||
<Rule color="Property">(?<=\.)[A-Za-z_][A-Za-z0-9_]*</Rule>
|
||||
|
||||
<Rule color="Constant">\b[A-Z][A-Z0-9]*_[A-Z0-9_]*\b</Rule>
|
||||
<Rule color="Constant">\b[A-Z]{2}[A-Z0-9_]*\b</Rule>
|
||||
</RuleSet>
|
||||
</SyntaxDefinition>
|
||||
|
|
@ -25,7 +25,8 @@ public class DirectorySettings : ViewModel, ICloneable
|
|||
Directories = old?.Directories ?? CustomDirectory.Default(gameName),
|
||||
AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null },
|
||||
LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1),
|
||||
CriwareDecryptionKey = old?.CriwareDecryptionKey ?? 0
|
||||
CriwareDecryptionKey = old?.CriwareDecryptionKey ?? 0,
|
||||
UnluacOpCodeMap = old?.UnluacOpCodeMap ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +107,13 @@ public class DirectorySettings : ViewModel, ICloneable
|
|||
set => SetProperty(ref _criwareDecryptionKey, value);
|
||||
}
|
||||
|
||||
private string _unluacOpCodeMap;
|
||||
public string UnluacOpCodeMap
|
||||
{
|
||||
get => _unluacOpCodeMap;
|
||||
set => SetProperty(ref _unluacOpCodeMap, value);
|
||||
}
|
||||
|
||||
private bool Equals(DirectorySettings other)
|
||||
{
|
||||
return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using CUE4Parse_Conversion.Animations;
|
|||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse_Conversion.UEFormat.Enums;
|
||||
using CUE4Parse.UE4.Lua.unluac;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
|
@ -119,6 +120,13 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _audioDirectory, value);
|
||||
}
|
||||
|
||||
private string _codeDirectory;
|
||||
public string CodeDirectory
|
||||
{
|
||||
get => _codeDirectory;
|
||||
set => SetProperty(ref _codeDirectory, value);
|
||||
}
|
||||
|
||||
private string _modelDirectory;
|
||||
public string ModelDirectory
|
||||
{
|
||||
|
|
@ -273,6 +281,36 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _convertAudioOnBulkExport, value);
|
||||
}
|
||||
|
||||
private bool _decompileLua;
|
||||
public bool DecompileLua
|
||||
{
|
||||
get => _decompileLua;
|
||||
set => SetProperty(ref _decompileLua, value);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public EUnluacMode UnluacMode
|
||||
{
|
||||
get => UnluacFlags.HasFlag(EUnluacFlags.Disassemble) ? EUnluacMode.Disassemble : EUnluacMode.Decompile;
|
||||
set
|
||||
{
|
||||
var withoutMode = UnluacFlags & ~(EUnluacFlags.Decompile | EUnluacFlags.Disassemble);
|
||||
var modeFlag = value == EUnluacMode.Disassemble ? EUnluacFlags.Disassemble : EUnluacFlags.Decompile;
|
||||
UnluacFlags = withoutMode | modeFlag;
|
||||
}
|
||||
}
|
||||
|
||||
private EUnluacFlags _unluacFlags = EUnluacFlags.Decompile;
|
||||
public EUnluacFlags UnluacFlags
|
||||
{
|
||||
get => _unluacFlags;
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _unluacFlags, value)) return;
|
||||
RaisePropertyChanged(nameof(UnluacMode));
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<string, DirectorySettings> _perDirectory = new Dictionary<string, DirectorySettings>();
|
||||
public IDictionary<string, DirectorySettings> PerDirectory
|
||||
{
|
||||
|
|
@ -454,13 +492,6 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _cameraMode, value);
|
||||
}
|
||||
|
||||
private int _wwiseMaxBnkPrefetch;
|
||||
public int WwiseMaxBnkPrefetch
|
||||
{
|
||||
get => _wwiseMaxBnkPrefetch;
|
||||
set => SetProperty(ref _wwiseMaxBnkPrefetch, value);
|
||||
}
|
||||
|
||||
private int _previewMaxTextureSize = 1024;
|
||||
public int PreviewMaxTextureSize
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace FModel.ViewModels.ApiEndpoints;
|
|||
public class DillyApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
private Backup[] _backups;
|
||||
private ManifestInfoDilly[] _manifests;
|
||||
|
||||
public DillyApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
|
|
@ -27,6 +28,19 @@ public class DillyApiEndpoint : AbstractApiProvider
|
|||
return _backups ??= GetBackupsAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<ManifestInfoDilly[]> GetManifestsAsync(CancellationToken token)
|
||||
{
|
||||
var request = new FRestRequest($"https://export-service-new.dillyapis.com/v1/manifests");
|
||||
var response = await _client.ExecuteAsync<ManifestInfoDilly[]>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public ManifestInfoDilly[] GetManifests(CancellationToken token)
|
||||
{
|
||||
return _manifests ??= GetManifestsAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<IDictionary<string, IDictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en")
|
||||
{
|
||||
var request = new FRestRequest("https://api.fortniteapi.com/v1/cloudstorage/hotfixes")
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ public class Backup
|
|||
[J] public string Url { get; private set; }
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{" + nameof(AppName) + "}")]
|
||||
public class ManifestInfoDilly
|
||||
{
|
||||
[J] public string AppName { get; private set; }
|
||||
[J] public string DownloadUrl { get; private set; }
|
||||
}
|
||||
|
||||
public class Donator
|
||||
{
|
||||
[J] public string Username { get; private set; }
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using System.Windows;
|
|||
using CUE4Parse_Conversion.Textures.BC;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.Encryption.Aes;
|
||||
using CUE4Parse.UE4.Lua.unluac;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Extensions;
|
||||
|
|
@ -104,7 +105,7 @@ public class ApplicationViewModel : ViewModel
|
|||
if (UserSettings.Default.CurrentDir is null)
|
||||
{
|
||||
//If no game is selected, many things will break before a shutdown request is processed in the normal way.
|
||||
//A hard exit is preferable to an unhandled expection in this case
|
||||
//A hard exit is preferable to an unhandled exception in this case
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +127,6 @@ public class ApplicationViewModel : ViewModel
|
|||
if (sender is not IAesVfsReader reader) return;
|
||||
CUE4Parse.GameDirectory.Disable(reader);
|
||||
};
|
||||
|
||||
CustomDirectories = new CustomDirectoriesViewModel();
|
||||
SettingsView = new SettingsViewModel();
|
||||
AesManager = new AesManagerViewModel(CUE4Parse);
|
||||
|
|
@ -141,8 +141,10 @@ public class ApplicationViewModel : ViewModel
|
|||
if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
|
||||
return currentDir;
|
||||
|
||||
Status.SetStatus(EStatusKind.Configuring);
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
|
||||
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
|
||||
Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value) return null;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
|
||||
|
|
@ -155,6 +157,35 @@ public class ApplicationViewModel : ViewModel
|
|||
return null;
|
||||
}
|
||||
|
||||
public DirectorySettings AddGameDirectory(string directory)
|
||||
{
|
||||
if (Status.Kind is EStatusKind.Configuring)
|
||||
{
|
||||
var directorySelector = Helper.GetWindow<DirectorySelector>("Directory Selector", null);
|
||||
directorySelector.AddManualGame(directory);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Status.SetStatus(EStatusKind.Configuring);
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(UserSettings.Default.GameDirectory);
|
||||
var directorySelector = new DirectorySelector(gameLauncherViewModel);
|
||||
directorySelector.AddManualGame(directory);
|
||||
var result = directorySelector.ShowDialog();
|
||||
Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return null;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
|
||||
if (UserSettings.Default.CurrentDir.Equals(gameLauncherViewModel.SelectedDirectory))
|
||||
return gameLauncherViewModel.SelectedDirectory;
|
||||
|
||||
UserSettings.Default.CurrentDir = gameLauncherViewModel.SelectedDirectory;
|
||||
RestartWithWarning();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
{
|
||||
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
|
|
@ -246,7 +277,7 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download vgmstream", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -310,4 +341,12 @@ public class ApplicationViewModel : ViewModel
|
|||
|
||||
DetexHelper.Initialize(detexPath);
|
||||
}
|
||||
|
||||
public static async Task InitUnluac()
|
||||
{
|
||||
var unluacPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", UnluacHelper.DllName);
|
||||
await UnluacHelper.InitializeAsync(unluacPath).ConfigureAwait(false);
|
||||
if (UnluacHelper.Instance is null)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download unluac", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
|
@ -298,13 +299,23 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
Save(a, true);
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
|
||||
FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true);
|
||||
});
|
||||
if (_audioFiles.Count > 1)
|
||||
FLogger.Append(ELog.Information, () => FLogger.Text($"Successfully saved {_audioFiles.Count} audio files", Constants.WHITE, true));
|
||||
{
|
||||
var dir = new DirectoryInfo(Path.GetDirectoryName(_audioFiles.First().FilePath));
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text($"Successfully saved {_audioFiles.Count} audio files to ", Constants.WHITE);
|
||||
FLogger.Link(dir.Name, dir.FullName, true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -330,12 +341,8 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write))
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(fileToSave.Data);
|
||||
writer.Flush();
|
||||
}
|
||||
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write);
|
||||
stream.Write(fileToSave.Data);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
|
|
@ -505,7 +512,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
|
||||
if (Spectrum != null && PlayedFile.PlaybackState == PlaybackState.Playing)
|
||||
{
|
||||
FftData = new float[4096];
|
||||
FftData = new float[4096+4];
|
||||
Spectrum.GetFftData(FftData);
|
||||
RaiseSourcePropertyChangedEvent(ESourceProperty.FftData, FftData);
|
||||
}
|
||||
|
|
@ -563,148 +570,226 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
{
|
||||
if (SelectedAudioFile?.Data == null)
|
||||
return false;
|
||||
if (SelectedAudioFile.Extension == "wav")
|
||||
return true;
|
||||
|
||||
switch (SelectedAudioFile.Extension)
|
||||
{
|
||||
case "binka":
|
||||
case "adpcm":
|
||||
case "xvag":
|
||||
case "opus":
|
||||
case "wem":
|
||||
case "at9":
|
||||
case "raw":
|
||||
{
|
||||
if (TryConvert(out var wavFilePath))
|
||||
{
|
||||
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath));
|
||||
Replace(newAudio);
|
||||
return true;
|
||||
}
|
||||
if (!TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, SelectedAudioFile.Extension, out var convertedFilePath, true))
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
case "adx":
|
||||
case "hca":
|
||||
return TryConvertCriware();
|
||||
case "rada":
|
||||
{
|
||||
if (TryDecode(SelectedAudioFile.Extension, out var rawFilePath))
|
||||
{
|
||||
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(rawFilePath));
|
||||
Replace(newAudio);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(convertedFilePath));
|
||||
Replace(newAudio);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryConvertCriware()
|
||||
public static bool TryConvert(string inputFilePath, byte[] inputFileData, string extension, out string wavFilePath, bool updateUi = false)
|
||||
{
|
||||
wavFilePath = string.Empty;
|
||||
|
||||
switch (extension.ToLowerInvariant())
|
||||
{
|
||||
case "hca":
|
||||
case "adx":
|
||||
return TryConvertCriware(inputFilePath, inputFileData, extension, out wavFilePath);
|
||||
case "rada":
|
||||
return TryConvertRada(inputFilePath, inputFileData, extension, out wavFilePath, updateUi);
|
||||
default:
|
||||
{
|
||||
var vgmStreamPath = TryGetVgmstreamPath();
|
||||
if (string.IsNullOrEmpty(vgmStreamPath))
|
||||
return false;
|
||||
|
||||
var success = TryConvertToWav(inputFilePath, inputFileData, vgmStreamPath, true, out wavFilePath);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Log.Error("Failed to convert {InputFilePath} to .wav format", Path.GetFileName(inputFilePath));
|
||||
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text("Failed to convert audio to .wav format. See: ", Constants.WHITE);
|
||||
FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConvertCriware(string inputFilePath, byte[] inputFileData, string extension, out string wavFilePath)
|
||||
{
|
||||
wavFilePath = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
byte[] wavData = SelectedAudioFile.Extension switch
|
||||
byte[] wavData = extension switch
|
||||
{
|
||||
"hca" => HcaWaveStream.ConvertHcaToWav(
|
||||
SelectedAudioFile.Data,
|
||||
UserSettings.Default.CurrentDir.CriwareDecryptionKey),
|
||||
"adx" => AdxDecoder.ConvertAdxToWav(
|
||||
SelectedAudioFile.Data,
|
||||
UserSettings.Default.CurrentDir.CriwareDecryptionKey),
|
||||
"hca" => HcaWaveStream.ConvertHcaToWav(inputFileData, UserSettings.Default.CurrentDir.CriwareDecryptionKey),
|
||||
"adx" => AdxDecoder.ConvertAdxToWav(inputFileData, UserSettings.Default.CurrentDir.CriwareDecryptionKey),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
|
||||
if (wavData.Length is 0)
|
||||
// Fallback for ADX
|
||||
if (wavData.Length == 0)
|
||||
{
|
||||
if (TryConvert(out var wavFilePathFallback))
|
||||
{
|
||||
var newAudioFallback = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePathFallback));
|
||||
Replace(newAudioFallback);
|
||||
return true;
|
||||
}
|
||||
var vgmStreamPath = TryGetVgmstreamPath();
|
||||
if (string.IsNullOrEmpty(vgmStreamPath))
|
||||
return false;
|
||||
|
||||
return TryConvertToWav(inputFilePath, inputFileData, vgmStreamPath, true, out wavFilePath);
|
||||
}
|
||||
|
||||
string wavFilePath = Path.Combine(
|
||||
UserSettings.Default.AudioDirectory,
|
||||
SelectedAudioFile.FilePath.TrimStart('/'));
|
||||
wavFilePath = Path.ChangeExtension(wavFilePath, ".wav");
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
|
||||
var directory = Path.GetDirectoryName(wavFilePath);
|
||||
if (!string.IsNullOrEmpty(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(wavFilePath)!);
|
||||
File.WriteAllBytes(wavFilePath, wavData);
|
||||
|
||||
var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath));
|
||||
Replace(newAudio);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (CriwareDecryptionException ex)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
|
||||
Log.Error($"Encrypted {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}");
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Encrypted {extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
|
||||
Log.Error($"Encrypted {extension.ToUpper()}: {ex.Message}");
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
|
||||
Log.Error($"Failed to convert {SelectedAudioFile.Extension.ToUpper()}: {ex.Message}");
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Failed to convert {extension.ToUpper()}: {ex.Message}", Constants.WHITE, true));
|
||||
Log.Error($"Failed to convert {extension.ToUpper()}: {ex.Message}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath);
|
||||
public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath)
|
||||
{
|
||||
wavFilePath = string.Empty;
|
||||
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
|
||||
if (!File.Exists(vgmFilePath))
|
||||
{
|
||||
vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-cli.exe");
|
||||
if (!File.Exists(vgmFilePath)) return false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(inputFilePath.SubstringBeforeLast("/"));
|
||||
File.WriteAllBytes(inputFilePath, inputFileData);
|
||||
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
var vgmProcess = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = vgmFilePath,
|
||||
Arguments = $"-o \"{wavFilePath}\" \"{inputFilePath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
vgmProcess?.WaitForExit(5000);
|
||||
|
||||
File.Delete(inputFilePath);
|
||||
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
|
||||
}
|
||||
|
||||
private bool TryDecode(string extension, out string rawFilePath)
|
||||
private static bool TryConvertRada(string inputFilePath, byte[] inputFileData, string extension, out string rawFilePath, bool updateUi = false)
|
||||
{
|
||||
rawFilePath = string.Empty;
|
||||
var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe");
|
||||
if (!File.Exists(decoderPath))
|
||||
{
|
||||
Log.Error("Failed to convert {FilePath}, {Extension} decoder is missing", inputFilePath, extension);
|
||||
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to convert audio because {extension} decoder is missing. See: ", Constants.WHITE);
|
||||
FLogger.Link("→ link ←", Constants.RADA_ISSUE_LINK, true);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/"));
|
||||
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
||||
return TryConvertToWav(inputFilePath, inputFileData, decoderPath, false, out rawFilePath);
|
||||
}
|
||||
|
||||
rawFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
||||
var decoderProcess = Process.Start(new ProcessStartInfo
|
||||
private static bool TryConvertToWav(string inputFilePath, byte[] inputFileData, string converterPath, bool usevgmstream, out string wavFilePath)
|
||||
{
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
var directory = Path.GetDirectoryName(inputFilePath);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var tempfile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + Path.GetExtension(inputFilePath));
|
||||
File.WriteAllBytes(tempfile, inputFileData);
|
||||
|
||||
var tempWavFilePath = Path.ChangeExtension(tempfile, ".wav");
|
||||
|
||||
var process = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = decoderPath,
|
||||
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"",
|
||||
FileName = converterPath,
|
||||
Arguments = usevgmstream ? $"-o \"{tempWavFilePath}\" \"{tempfile}\"" : $"-i \"{tempfile}\" -o \"{tempWavFilePath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
decoderProcess?.WaitForExit(5000);
|
||||
process?.WaitForExit(5000);
|
||||
|
||||
File.Delete(SelectedAudioFile.FilePath);
|
||||
return decoderProcess?.ExitCode == 0 && File.Exists(rawFilePath);
|
||||
File.Delete(tempfile);
|
||||
|
||||
var success = process?.ExitCode == 0 && File.Exists(tempWavFilePath);
|
||||
if (success)
|
||||
{
|
||||
File.Move(tempWavFilePath, wavFilePath, true);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private static string TryGetVgmstreamPath()
|
||||
{
|
||||
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
|
||||
if (!File.Exists(vgmFilePath))
|
||||
{
|
||||
vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-cli.exe");
|
||||
if (!File.Exists(vgmFilePath))
|
||||
{
|
||||
Log.Error("Failed to convert audio, vgmstream is missing");
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text("Failed to convert audio because vgmstream is missing. See: ", Constants.WHITE);
|
||||
FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true);
|
||||
});
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return vgmFilePath;
|
||||
}
|
||||
|
||||
// Since Square Enix soundbanks are pretty niche, let's just use vgmstream to extract them
|
||||
public static List<string> ExtractSquareEnixAudio(string sabPath, byte[] sqexData)
|
||||
{
|
||||
var vgmStreamPath = TryGetVgmstreamPath();
|
||||
if (string.IsNullOrEmpty(vgmStreamPath))
|
||||
return [];
|
||||
if (sqexData.Length == 0)
|
||||
return [];
|
||||
|
||||
var extractionDir = Path.GetDirectoryName(sabPath);
|
||||
Directory.CreateDirectory(extractionDir);
|
||||
|
||||
// There's no clean way to know what was extracted with vgmstream (it's a soundbank, might contain multiple sounds) so we're monitoring extraction directory
|
||||
var capturedFiles = new ConcurrentBag<string>();
|
||||
using var watcher = new FileSystemWatcher(extractionDir)
|
||||
{
|
||||
Filter = "*.wav",
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime
|
||||
};
|
||||
|
||||
void handler(object s, FileSystemEventArgs e) => capturedFiles.Add(e.FullPath);
|
||||
|
||||
watcher.Created += handler;
|
||||
watcher.Changed += handler;
|
||||
watcher.EnableRaisingEvents = true;
|
||||
|
||||
var tempSab = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".sab");
|
||||
File.WriteAllBytes(tempSab, sqexData);
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = vgmStreamPath,
|
||||
Arguments = $"-S 0 -o \"{extractionDir}\\?n_?s.wav\" \"{tempSab}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (var process = Process.Start(startInfo))
|
||||
{
|
||||
process?.WaitForExit(15000);
|
||||
}
|
||||
|
||||
File.Delete(tempSab);
|
||||
watcher.EnableRaisingEvents = false;
|
||||
|
||||
return [.. capturedFiles.Distinct()];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,12 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
using AdonisUI.Controls;
|
||||
|
||||
using CUE4Parse;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.Encryption.Aes;
|
||||
|
|
@ -22,10 +20,16 @@ using CUE4Parse.FileProvider.Vfs;
|
|||
using CUE4Parse.GameTypes.Aion2.Objects;
|
||||
using CUE4Parse.GameTypes.AoC.Objects;
|
||||
using CUE4Parse.GameTypes.AshEchoes.FileProvider;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.KRD.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Wwise;
|
||||
using CUE4Parse.GameTypes.DFHO.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.HonorOfKings.FileProvider;
|
||||
using CUE4Parse.GameTypes.KRD.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.LegoBatman.Assets;
|
||||
using CUE4Parse.GameTypes.RocoKingdomWorld.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
|
||||
using CUE4Parse.MappingsProvider;
|
||||
using CUE4Parse.UE4.AssetRegistry;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
|
|
@ -40,12 +44,14 @@ using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
|||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Exports.Verse;
|
||||
using CUE4Parse.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.BinaryConfig;
|
||||
using CUE4Parse.UE4.CriWare;
|
||||
using CUE4Parse.UE4.CriWare.Readers;
|
||||
using CUE4Parse.UE4.FMod;
|
||||
using CUE4Parse.UE4.IO;
|
||||
using CUE4Parse.UE4.Localization;
|
||||
using CUE4Parse.UE4.Lua.unluac;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
|
|
@ -56,14 +62,11 @@ using CUE4Parse.UE4.Shaders;
|
|||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.UE4.Wwise;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Sounds;
|
||||
|
||||
using EpicManifestParser;
|
||||
using EpicManifestParser.UE;
|
||||
using EpicManifestParser.ZlibngDotNetDecompressor;
|
||||
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
|
|
@ -72,21 +75,14 @@ using FModel.Settings;
|
|||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using FModel.Views.Snooper;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using SkiaSharp;
|
||||
|
||||
using Svg.Skia;
|
||||
|
||||
using UE4Config.Parsing;
|
||||
|
||||
using Application = System.Windows.Application;
|
||||
using FGuid = CUE4Parse.UE4.Objects.Core.Misc.FGuid;
|
||||
|
||||
|
|
@ -161,6 +157,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public CriWareProvider CriWareProvider => _criWareProviderLazy?.Value;
|
||||
public ConcurrentBag<string> UnknownExtensions = [];
|
||||
|
||||
public int ExportedCount;
|
||||
public int FailedExportCount;
|
||||
|
||||
public CUE4ParseViewModel()
|
||||
{
|
||||
var currentDir = UserSettings.Default.CurrentDir;
|
||||
|
|
@ -200,6 +199,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
], SearchOption.AllDirectories, versionContainer, pathComparer),
|
||||
_ when versionContainer.Game is EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
|
||||
_ when versionContainer.Game is EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal),
|
||||
_ when versionContainer.Game is EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
|
||||
_ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer)
|
||||
};
|
||||
|
||||
|
|
@ -221,14 +221,12 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
public async Task Initialize()
|
||||
{
|
||||
await _apiEndpointView.EpicApi.VerifyAuth(CancellationToken.None);
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
Provider.OnDemandOptions = new IoStoreOnDemandOptions
|
||||
{
|
||||
ChunkHostUri = new Uri("https://download.epicgames.com/", UriKind.Absolute),
|
||||
ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")),
|
||||
Authorization = new AuthenticationHeaderValue("Bearer", UserSettings.Default.LastAuthResponse.AccessToken),
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
|
||||
|
|
@ -263,7 +261,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
(manifest, _) = manifestInfo.DownloadAndParseAsync(manifestOptions,
|
||||
cancellationToken: cancellationToken,
|
||||
elementManifestPredicate: static x => x.Uri.Host == "download.epicgames.com"
|
||||
elementManifestPredicate: static x => x.Uri.Host == "download.epicgames.com" || x.Uri.Host == "epicgames-download1.akamaized.net"
|
||||
).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
|
|
@ -283,6 +281,20 @@ public class CUE4ParseViewModel : ViewModel
|
|||
it => new FRandomAccessStreamArchive(it, manifest.FindFile(it)!.GetStream(), p.Versions));
|
||||
});
|
||||
|
||||
var manifests = _apiEndpointView.DillyApi.GetManifests(cancellationToken);
|
||||
var downloadUrl = manifests.First(x => x.AppName == "Fortnite_Studio").DownloadUrl;
|
||||
|
||||
using var client = new HttpClient();
|
||||
var manifestBytes = client.GetByteArrayAsync(downloadUrl).GetAwaiter().GetResult();
|
||||
|
||||
var uefnManifest = FBuildPatchAppManifest.Deserialize(manifestBytes, manifestOptions);
|
||||
|
||||
Parallel.ForEach(uefnManifest.Files.Where(x => _fnLiveRegex.IsMatch(x.FileName)), fileManifest =>
|
||||
{
|
||||
p.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream()],
|
||||
it => new FRandomAccessStreamArchive(it, uefnManifest.FindFile(it)!.GetStream(), p.Versions));
|
||||
});
|
||||
|
||||
var elapsedTime = Stopwatch.GetElapsedTime(startTs);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Fortnite [LIVE] has been loaded successfully in {elapsedTime.TotalMilliseconds:F1}ms", Constants.WHITE, true));
|
||||
|
|
@ -321,7 +333,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
Provider.Initialize();
|
||||
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory, UserSettings.Default.WwiseMaxBnkPrefetch));
|
||||
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
_fmodProviderLazy = new Lazy<FModProvider>(() => new FModProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
_criWareProviderLazy = new Lazy<CriWareProvider>(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
Log.Information($"{Provider.Versions.Game} ({Provider.Versions.Platform}) | Archives: x{Provider.UnloadedVfs.Count} | AES: x{Provider.RequiredKeys.Count} | Loose Files: x{Provider.Files.Count}");
|
||||
|
|
@ -389,6 +401,16 @@ public class CUE4ParseViewModel : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
private ITypeMappingsProvider SelectMappingsProvider(string path)
|
||||
{
|
||||
if (path.EndsWith(".jmap.gz", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jmap", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new JmapTypeMappingsProvider(path);
|
||||
}
|
||||
|
||||
return new FileUsmapTypeMappingsProvider(path);
|
||||
}
|
||||
|
||||
public Task InitMappings(bool force = false)
|
||||
{
|
||||
if (!UserSettings.IsEndpointValid(EEndpointType.Mapping, out var endpoint))
|
||||
|
|
@ -402,7 +424,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var l = ELog.Information;
|
||||
if (endpoint.Overwrite && File.Exists(endpoint.FilePath))
|
||||
{
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath);
|
||||
Provider.MappingsContainer = SelectMappingsProvider(endpoint.FilePath);
|
||||
}
|
||||
else if (endpoint.IsValid)
|
||||
{
|
||||
|
|
@ -428,7 +450,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
_apiEndpointView.DownloadFile(mapping.Url, mappingPath);
|
||||
}
|
||||
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath);
|
||||
Provider.MappingsContainer = SelectMappingsProvider(mappingPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -490,7 +512,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud", inst[0].Value.SubstringAfterLast("/").SubstringBefore("\""));
|
||||
if (!File.Exists(ioStoreOnDemandPath)) return;
|
||||
|
||||
await Provider.RegisterVfsAsync(new IoChunkToc(ioStoreOnDemandPath));
|
||||
await Provider.RegisterVfsAsync(new IoChunkToc(ioStoreOnDemandPath, Provider.Versions));
|
||||
var onDemandCount = await Provider.MountAsync();
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"{onDemandCount} on-demand archive{(onDemandCount > 1 ? "s" : "")} streamed via epicgames.com", Constants.WHITE, true));
|
||||
|
|
@ -597,6 +619,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
|
||||
}
|
||||
|
||||
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk));
|
||||
|
||||
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs));
|
||||
|
||||
|
|
@ -615,6 +640,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public void AudioFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto));
|
||||
|
||||
public void CodeFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Code | EBulkType.Auto));
|
||||
|
||||
public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None)
|
||||
{
|
||||
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
|
||||
|
|
@ -628,6 +656,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var saveProperties = HasFlag(bulk, EBulkType.Properties);
|
||||
var saveTextures = HasFlag(bulk, EBulkType.Textures);
|
||||
var saveAudio = HasFlag(bulk, EBulkType.Audio);
|
||||
var saveDecompiled = HasFlag(bulk, EBulkType.Code);
|
||||
switch (entry.Extension)
|
||||
{
|
||||
case "uasset":
|
||||
|
|
@ -642,6 +671,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (saveProperties) break; // do not search for viewable exports if we are dealing with jsons
|
||||
}
|
||||
|
||||
if (saveDecompiled)
|
||||
{
|
||||
if (Decompile(entry, false))
|
||||
TabControl.SelectedTab.SaveDecompiled(updateUi);
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = result.InclusiveStart; i < result.ExclusiveEnd; i++)
|
||||
{
|
||||
if (CheckExport(cancellationToken, result.Package, i, bulk))
|
||||
|
|
@ -665,11 +701,28 @@ public class CUE4ParseViewModel : ViewModel
|
|||
ProcessAion2DatFile(entry, updateUi, saveProperties);
|
||||
break;
|
||||
}
|
||||
case "bytes" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
|
||||
{
|
||||
ProcessRocoBinFile(entry, updateUi, saveProperties);
|
||||
break;
|
||||
}
|
||||
case "dbc" when Provider.Versions.Game is EGame.GAME_AshesOfCreation:
|
||||
{
|
||||
ProcessCacheDBFile(entry, updateUi, saveProperties);
|
||||
break;
|
||||
}
|
||||
case "luac":
|
||||
case "lua":
|
||||
{
|
||||
var data = Provider.SaveAsset(entry);
|
||||
byte[] decompiled = ProcessLuaFile(data);
|
||||
|
||||
using var stream = new MemoryStream(decompiled);
|
||||
using var reader = new StreamReader(stream);
|
||||
TabControl.SelectedTab.SetDocumentText(reader.ReadToEnd(), saveProperties, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
case "upluginmanifest":
|
||||
case "code-workspace":
|
||||
case "projectstore":
|
||||
|
|
@ -680,6 +733,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "archive":
|
||||
case "dnearchive": // Banishers: Ghosts of New Eden
|
||||
case "gitignore":
|
||||
case "gitattributes":
|
||||
case "LICENSE":
|
||||
case "playstats": // Dispatch
|
||||
case "template":
|
||||
|
|
@ -692,13 +746,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "verse":
|
||||
case "html":
|
||||
case "json5":
|
||||
case "json":
|
||||
case "uref":
|
||||
case "cube":
|
||||
case "usda":
|
||||
case "ocio":
|
||||
case "data" when Provider.ProjectName is "OakGame":
|
||||
case "scss":
|
||||
case "yaml":
|
||||
case "ini":
|
||||
case "txt":
|
||||
case "log":
|
||||
|
|
@ -721,13 +775,15 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "apx":
|
||||
case "udn":
|
||||
case "doc":
|
||||
case "lua":
|
||||
case "vdf":
|
||||
case "yml":
|
||||
case "js":
|
||||
case "po":
|
||||
case "py":
|
||||
case "md":
|
||||
case "h":
|
||||
case "non" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
|
||||
case "cam" when Provider.Versions.Game is EGame.GAME_RocoKingdomWorld:
|
||||
// Uncharted Waters Origin
|
||||
case "crn":
|
||||
case "uwt":
|
||||
|
|
@ -736,6 +792,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "bl":
|
||||
case "bm":
|
||||
case "br":
|
||||
case "sql":
|
||||
case "cs":
|
||||
{
|
||||
var data = Provider.SaveAsset(entry);
|
||||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
|
|
@ -745,6 +803,18 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
break;
|
||||
}
|
||||
case "ebd" when Provider.Versions.Game is EGame.GAME_ArcRaiders:
|
||||
case "json":
|
||||
{
|
||||
var data = Provider.SaveAsset(entry);
|
||||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
var parsedJson = JsonConvert.DeserializeObject(reader.ReadToEnd());
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(parsedJson, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
case "locmeta":
|
||||
{
|
||||
var archive = entry.CreateReader();
|
||||
|
|
@ -792,7 +862,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var directory = Path.GetDirectoryName(entry.Path) ?? "/FMOD/Desktop/";
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -801,13 +871,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "pck":
|
||||
{
|
||||
var archive = entry.CreateReader();
|
||||
var wwise = new WwiseReader(archive);
|
||||
var wwise = new WwiseReader(new FWwiseArchive(archive), new WwiseGameFileSource(entry));
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
var medias = WwiseProvider.ExtractBankSounds(wwise);
|
||||
foreach (var media in medias)
|
||||
{
|
||||
SaveAndPlaySound(media.OutputPath, media.Extension, media.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, media.OutputPath, media.Extension, media.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -823,7 +893,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var extractedSounds = CriWareProvider.ExtractCriWareSounds(awbReader, archive.Name);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -839,7 +909,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var extractedSounds = CriWareProvider.ExtractCriWareSounds(acbReader, archive.Name);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -854,7 +924,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
// todo: CSCore.MediaFoundation.MediaFoundationException The byte stream type of the given URL is unsupported. case "aif":
|
||||
{
|
||||
var data = Provider.SaveAsset(entry);
|
||||
SaveAndPlaySound(entry.PathWithoutExtension, entry.Extension, data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, entry.PathWithoutExtension, entry.Extension, data, saveAudio, updateUi);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
@ -866,6 +936,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
break;
|
||||
}
|
||||
case "ustbin" when Provider.Versions.Game is EGame.GAME_DeltaForce:
|
||||
{
|
||||
var archive = entry.CreateReader();
|
||||
var ustbin = new FDeltaStringTable(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ustbin, Formatting.Indented), saveProperties, updateUi);
|
||||
break;
|
||||
}
|
||||
case "png":
|
||||
case "jpg":
|
||||
case "bmp":
|
||||
|
|
@ -933,7 +1010,6 @@ public class CUE4ParseViewModel : ViewModel
|
|||
break;
|
||||
}
|
||||
case "res": // just skip
|
||||
case "luac": // compiled lua
|
||||
case "bytes": // wuthering waves
|
||||
break;
|
||||
default:
|
||||
|
|
@ -949,6 +1025,56 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
// Roco Kingdom: World
|
||||
void ProcessRocoBinFile(GameFile entry, bool updateUi, bool saveProperties)
|
||||
{
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
|
||||
var nonFileName = "/" + entry.NameWithoutExtension + ".non";
|
||||
var nonPath = Provider.Files.Keys.FirstOrDefault(k => k.EndsWith(nonFileName, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// I will only get one localization file because they did not translate any languages, lol
|
||||
var locPathKey = entry.Path.Replace("/BinData/", "/BinLocalize/zh_Hans/").Replace("/BinDataCompressed/", "/BinLocalize/zh_Hans/");
|
||||
var locFileFound = Provider.Files.TryGetValue(locPathKey, out var locEntry);
|
||||
|
||||
if (entry.Path is "NRC/Content/ScriptC/Data/Audio/dataconfig_audio.bytes")
|
||||
{
|
||||
var descFound = Provider.Files.TryGetValue("NRC/Content/ScriptC/Data/Audio/dataconfig_audiodesc.bytes", out var descEntry);
|
||||
var typeDescFound = Provider.Files.TryGetValue("NRC/Content/ScriptC/Data/Audio/typeDesc.bytes", out var typeDescEntry);
|
||||
|
||||
if (!descFound || !typeDescFound)
|
||||
{
|
||||
Log.Warning("Could not find associated dataconfig_audiodesc.bytes or typeDesc.bytes, cannot parse audio config");
|
||||
return;
|
||||
}
|
||||
|
||||
var data = new FRocoAudioConfig(entry.CreateReader(), descEntry.CreateReader(), typeDescEntry.CreateReader());
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(data, Formatting.Indented), saveProperties, updateUi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(nonPath) && Provider.Files.TryGetValue(nonPath, out var nonEntry))
|
||||
{
|
||||
string json = Encoding.UTF8.GetString(nonEntry.Read());
|
||||
var schema = JsonConvert.DeserializeObject<FRocoSchema>(json);
|
||||
var archive = entry.CreateReader();
|
||||
var locArchive = locFileFound ? new FRocoBinData(locEntry.CreateReader(), null, ERocoBinDataType.BinLocalize) : null;
|
||||
|
||||
var data = entry.PathWithoutExtension switch
|
||||
{
|
||||
var p when p.Contains("BinDataCompressed") => new FRocoBinData(archive, schema, ERocoBinDataType.BinDataCompressed, locArchive),
|
||||
var p when p.Contains("BinData") => new FRocoBinData(archive, schema, ERocoBinDataType.BinData, locArchive),
|
||||
var p when p.Contains("BinLocalize") => new FRocoBinData(archive, null, ERocoBinDataType.BinLocalize),
|
||||
_ => null
|
||||
};
|
||||
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(data, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
else if (entry.PathWithoutExtension.Contains("/Bin/"))
|
||||
{
|
||||
throw new Exception($"Could not find associated .non file for {entry.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessAion2DatFile(GameFile entry, bool updateUi, bool saveProperties)
|
||||
{
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
|
||||
|
|
@ -963,7 +1089,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
else if (entry.NameWithoutExtension.Equals("L10NString"))
|
||||
{
|
||||
var l10nData = new FAion2L10NFile(entry);
|
||||
var l10nData = new FAion2L10NFile(entry, Provider);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(l10nData, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
else
|
||||
|
|
@ -1005,6 +1131,53 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private byte[] ProcessLuaFile(byte[] data)
|
||||
{
|
||||
var result = EUnluacErrorCode.Ok;
|
||||
byte[] output = [];
|
||||
if (BitConverter.ToUInt32(data) == UnluacHelper.LuaMagic && UnluacHelper.Instance is not null)
|
||||
{
|
||||
// opcodemap patch
|
||||
byte[] opmapData = Provider.Versions.Game switch
|
||||
{
|
||||
_ => [],
|
||||
};
|
||||
|
||||
var flags = UserSettings.Default.UnluacFlags;
|
||||
var opcodemap = UserSettings.Default.CurrentDir.UnluacOpCodeMap;
|
||||
if (!string.IsNullOrWhiteSpace(opcodemap))
|
||||
{
|
||||
opmapData = Encoding.UTF8.GetBytes(opcodemap);
|
||||
flags |= EUnluacFlags.OpCodeMap;
|
||||
}
|
||||
else if (opmapData is { Length: > 12 })
|
||||
{
|
||||
flags |= EUnluacFlags.OpCodeMapPatch;
|
||||
}
|
||||
|
||||
result = UnluacHelper.Decompile(data, opmapData, (uint)flags, out output, out var log);
|
||||
if (result != EUnluacErrorCode.Ok && log.Length > 0)
|
||||
{
|
||||
Log.Error(Encoding.UTF8.GetString(log));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = EUnluacErrorCode.Error;
|
||||
}
|
||||
|
||||
var decompiled = result switch
|
||||
{
|
||||
EUnluacErrorCode.Ok => output,
|
||||
#if DEBUG
|
||||
EUnluacErrorCode.PartialDecompile => output,
|
||||
#endif
|
||||
_ => data,
|
||||
};
|
||||
|
||||
return decompiled;
|
||||
}
|
||||
|
||||
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
|
||||
{
|
||||
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
|
||||
|
|
@ -1036,7 +1209,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var pointer = new FPackageIndex(pkg, index + 1).ResolvedObject;
|
||||
if (pointer?.Object is null) return false;
|
||||
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg);
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class, pkg);
|
||||
switch (dummy)
|
||||
{
|
||||
case UVerseDigest when isNone && pointer.Object.Value is UVerseDigest verseDigest:
|
||||
|
|
@ -1114,7 +1287,17 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource:
|
||||
{
|
||||
var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath);
|
||||
SaveAndPlaySound(audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio, updateUi);
|
||||
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
|
||||
SaveAndPlaySound(cancellationToken, outputPath, "wem", externalSource.Data?.WemFile?.GetData() ?? [], saveAudio, updateUi);
|
||||
return false;
|
||||
}
|
||||
case UAkAudioBank when (isNone || saveAudio) && pointer.Object.Value is UAkAudioBank soundBank:
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractBankSounds(soundBank);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UAkAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UAkAudioEvent audioEvent:
|
||||
|
|
@ -1122,27 +1305,27 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UFMODEvent when (isNone || saveAudio) && pointer.Object.Value is UFMODEvent fmodEvent:
|
||||
{
|
||||
var extractedSounds = FmodProvider.ExtractEventSounds(fmodEvent);
|
||||
var directory = Path.GetDirectoryName(fmodEvent.Owner?.Name) ?? "/FMOD/Desktop/";
|
||||
var directory = Path.GetDirectoryName(Provider.FixPath(fmodEvent.Owner?.Name ?? "/FMOD/Desktop/"));
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UFMODBank when (isNone || saveAudio) && pointer.Object.Value is UFMODBank fmodBank:
|
||||
{
|
||||
var extractedSounds = FmodProvider.ExtractBankSounds(fmodBank);
|
||||
var directory = Path.GetDirectoryName(fmodBank.Owner?.Name) ?? "/FMOD/Desktop/";
|
||||
var directory = Path.GetDirectoryName(Provider.FixPath(fmodBank.Owner?.Name ?? "/FMOD/Desktop/"));
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(Path.Combine(directory, sound.Name), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1157,11 +1340,26 @@ public class CUE4ParseViewModel : ViewModel
|
|||
_ => []
|
||||
};
|
||||
|
||||
var directory = Path.GetDirectoryName(atomObject.Owner?.Name) ?? "/Criware/";
|
||||
directory = Path.GetDirectoryName(atomObject.Owner.Provider.FixPath(directory));
|
||||
var directory = Path.GetDirectoryName(Provider.FixPath(atomObject.Owner?.Name ?? "/Criware/"));
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, Path.Combine(directory, sound.Name).Replace("\\", "/"), sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case USQEXSEADSoundBank or USQEXSEADSound when (isNone || saveAudio) && pointer.Object.Value is UObject squareEnixObject:
|
||||
{
|
||||
var data = squareEnixObject switch
|
||||
{
|
||||
USQEXSEADSoundBank sqexSoundBank => sqexSoundBank.SQEXSoundBankData?.ReadDataOnce() ?? [],
|
||||
USQEXSEADSound sqexSound => sqexSound.SQEXSoundData?.ReadDataOnce() ?? [],
|
||||
_ => [],
|
||||
};
|
||||
var sabPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), squareEnixObject.Name);
|
||||
var extractedSounds = AudioPlayerViewModel.ExtractSquareEnixAudio(sabPath, data);
|
||||
foreach (var soundPath in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, soundPath, "wav", File.ReadAllBytes(soundPath), saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1169,7 +1367,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case USoundWave when isNone || saveAudio:
|
||||
{
|
||||
// If UAkMediaAsset exists in the same package it should be used to handle the audio instead (because it contains actual audio name)
|
||||
if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer is UAkMediaAsset)
|
||||
if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer.Object.Value is UAkMediaAsset)
|
||||
return false;
|
||||
|
||||
var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed;
|
||||
|
|
@ -1181,18 +1379,19 @@ public class CUE4ParseViewModel : ViewModel
|
|||
return false;
|
||||
}
|
||||
|
||||
SaveAndPlaySound(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/'), audioFormat, data, saveAudio, updateUi);
|
||||
return false;
|
||||
}
|
||||
case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset:
|
||||
{
|
||||
var audioName = akMediaAsset.MediaName;
|
||||
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
|
||||
var audioName = akMediaAsset.MediaName ?? akMediaAsset.Name;
|
||||
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
|
||||
if (akMediaAsset.CurrentMediaAssetData?.ResolvedObject?.Object?.Value is UAkMediaAssetData akMediaAssetData)
|
||||
{
|
||||
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
|
||||
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
|
||||
|
||||
SaveAndPlaySound(audioName, audioFormat, data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1201,31 +1400,43 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
|
||||
foreach (var mediaIndex in akAudioEventData.MediaList)
|
||||
{
|
||||
if (mediaIndex.TryLoad<UAkMediaAsset>(out var akMediaAsset))
|
||||
if (mediaIndex?.Object?.Value is UAkMediaAsset akMediaAsset)
|
||||
{
|
||||
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
|
||||
if (akMediaAsset.CurrentMediaAssetData?.ResolvedObject?.Object?.Value is UAkMediaAssetData akMediaAssetData)
|
||||
{
|
||||
var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})";
|
||||
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
|
||||
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
|
||||
|
||||
SaveAndPlaySound(audioName, audioFormat, data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Borderlands 3
|
||||
case UDialogPerformanceData when (isNone || saveAudio) && pointer.Object.Value is UDialogPerformanceData dialogPerformanceData:
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractDialogBorderlands3(dialogPerformanceData);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Borderlands 4
|
||||
case UFaceFXAnimSet when (isNone || saveAudio) && pointer.Object.Value is UFaceFXAnimSet faceFXAnimSet:
|
||||
{
|
||||
if (Provider.Versions.Game is not EGame.GAME_Borderlands4)
|
||||
return false;
|
||||
|
||||
var ownerDirectory = WwiseProvider.GetOwnerDirectory(faceFXAnimSet);
|
||||
foreach (var faceFXAnimData in faceFXAnimSet.FaceFXAnimDataList)
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(faceFXAnimData.ID.Name, false);
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(ownerDirectory, faceFXAnimData.ID.Name, false);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1234,17 +1445,39 @@ public class CUE4ParseViewModel : ViewModel
|
|||
// Borderlands 4
|
||||
case UGbxGraphAsset when (isNone || saveAudio) && pointer.Object.Value is UGbxGraphAsset gbxGraphAsset:
|
||||
{
|
||||
var ownerDirectory = WwiseProvider.GetOwnerDirectory(gbxGraphAsset);
|
||||
foreach (var (eventName, useSoundTag) in GbxAudioUtil.GetAndClearEvents())
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(eventName, useSoundTag);
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(ownerDirectory, eventName, useSoundTag);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// LEGO® Batman™: Legacy of the Dark Knight
|
||||
case UWubAudioEvent when (isNone || saveAudio) && pointer.Object.Value is UWubAudioEvent wubAudioEvent:
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractWubAudioEventSounds(wubAudioEvent);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UWubDialogueEvent when (isNone || saveAudio) && pointer.Object.Value is UWubDialogueEvent wubDialogueEvent:
|
||||
{
|
||||
var files = wubDialogueEvent.Wems
|
||||
.SelectMany(wem => Provider.Files.Values.Where(file => file.Path.EndsWith(wem.Text + ".wem", StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
foreach (var entry in files)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, entry.PathWithoutExtension, entry.Extension, entry.Read(), saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
|
||||
case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch
|
||||
{
|
||||
|
|
@ -1332,11 +1565,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
|
||||
public void Decompile(GameFile entry)
|
||||
public bool Decompile(GameFile entry, bool AddTab = true)
|
||||
{
|
||||
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
|
||||
|
||||
if (TabControl.CanAddTabs) TabControl.AddTab(entry);
|
||||
if (TabControl.CanAddTabs && AddTab)
|
||||
{
|
||||
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
|
||||
TabControl.AddTab(entry);
|
||||
}
|
||||
else TabControl.SelectedTab.SoftReset(entry);
|
||||
|
||||
TabControl.SelectedTab.TitleExtra = "Decompiled";
|
||||
|
|
@ -1361,48 +1596,61 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (pointer?.Object is null && pointer.Class?.Object?.Value is null)
|
||||
continue;
|
||||
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg);
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class, pkg);
|
||||
if (dummy is not UClass || pointer.Object.Value is not UClass blueprint)
|
||||
continue;
|
||||
|
||||
cppList.Add(blueprint.DecompileBlueprintToPseudo(cookedMetaData));
|
||||
cppList.Add(blueprint.DecompileBlueprintToPseudo(pkg.Mappings, cookedMetaData));
|
||||
}
|
||||
|
||||
if (cppList.Count == 0) return false;
|
||||
var cpp = cppList.Count > 1 ? string.Join("\n\n", cppList) : cppList.FirstOrDefault() ?? string.Empty;
|
||||
if (entry.Path.Contains("_Verse.uasset"))
|
||||
{
|
||||
cpp = Regex.Replace(cpp, "__verse_0x[a-fA-F0-9]{8}_", ""); // UnmangleCasedName
|
||||
}
|
||||
cpp = Regex.Replace(cpp, @"CallFunc_([A-Za-z0-9_]+)_ReturnValue", "$1");
|
||||
|
||||
cpp = Regex.Replace(cpp, @"K2Node_DynamicCast_([A-Za-z0-9_]+)", "$1");
|
||||
cpp = Regex.Replace(cpp, @"K2Node_([A-Za-z0-9_]+)", "$1");
|
||||
|
||||
TabControl.SelectedTab.SetDocumentText(cpp, false, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SaveAndPlaySound(string fullPath, string ext, byte[] data, bool isBulk, bool updateUi)
|
||||
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool saveAudio, bool updateUi)
|
||||
{
|
||||
if (fullPath.StartsWith('/')) fullPath = fullPath[1..];
|
||||
var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}";
|
||||
var extLower = ext.ToLowerInvariant();
|
||||
var baseFilePath = UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/');
|
||||
var combinedPath = Path.Combine(UserSettings.Default.AudioDirectory, baseFilePath);
|
||||
var savedAudioPath = Path.ChangeExtension(combinedPath, extLower).Replace('\\', '/');
|
||||
|
||||
if (isBulk)
|
||||
if (saveAudio)
|
||||
{
|
||||
Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/'));
|
||||
using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var directory = Path.GetDirectoryName(savedAudioPath);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
bool conversionSuccess = true;
|
||||
if (UserSettings.Default.ConvertAudioOnBulkExport && extLower is not "wav")
|
||||
{
|
||||
writer.Write(data);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
if (UserSettings.Default.ConvertAudioOnBulkExport)
|
||||
{
|
||||
AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath);
|
||||
savedAudioPath = wavFilePath;
|
||||
if (AudioPlayerViewModel.TryConvert(savedAudioPath, data, extLower, out string wavFilePath))
|
||||
savedAudioPath = wavFilePath;
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);
|
||||
stream.Write(data);
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("Successfully saved {FilePath}", savedAudioPath);
|
||||
if (updateUi)
|
||||
if (updateUi && conversionSuccess)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
|
|
@ -1414,6 +1662,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
return;
|
||||
}
|
||||
|
||||
if (!updateUi)
|
||||
return;
|
||||
|
||||
// TODO
|
||||
// since we are currently in a thread, the audio player's lifetime (memory-wise) will keep the current thread up and running until fmodel itself closes
|
||||
// the solution would be to kill the current thread at this line and then open the audio player without "Application.Current.Dispatcher.Invoke"
|
||||
|
|
@ -1431,6 +1682,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
|
||||
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
|
||||
{
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("Successfully saved {FilePath}", savedFilePath);
|
||||
if (updateUi)
|
||||
{
|
||||
|
|
@ -1443,6 +1695,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
Log.Error("{FileName} could not be saved", export.Name);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
|
||||
}
|
||||
|
|
@ -1464,6 +1717,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
});
|
||||
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("{FileName} successfully exported", entry.Name);
|
||||
if (updateUi)
|
||||
{
|
||||
|
|
@ -1476,6 +1730,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
Log.Error("{FileName} could not be exported", entry.Name);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -13,8 +17,21 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
|
||||
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
|
||||
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) { }
|
||||
|
||||
private enum EAction
|
||||
{
|
||||
Show,
|
||||
Export,
|
||||
}
|
||||
|
||||
private enum EShowAssetType
|
||||
{
|
||||
None,
|
||||
JSON,
|
||||
Metadata,
|
||||
References,
|
||||
Decompile,
|
||||
}
|
||||
|
||||
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
|
||||
|
|
@ -26,189 +43,151 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
if (param.Length == 0) return;
|
||||
|
||||
var folders = param.OfType<TreeItem>().ToArray();
|
||||
var assets = param.SelectMany(item => item switch
|
||||
{
|
||||
GameFile gf => new[] { gf }, // search view passes GameFile directly
|
||||
GameFileViewModel gvm => new[] { gvm.Asset },
|
||||
_ => []
|
||||
}).ToArray();
|
||||
var assets = param
|
||||
.Select(static item => item switch
|
||||
{
|
||||
GameFile gf => gf, // Search view passes GameFile directly
|
||||
GameFileViewModel gvm => gvm.Asset,
|
||||
_ => null
|
||||
})
|
||||
.Where(static gf => gf is not null).ToArray();
|
||||
|
||||
if (folders.Length == 0 && assets.Length == 0)
|
||||
return;
|
||||
|
||||
var updateUi = assets.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||
var assetsGroups = assets.GroupBy(static gf => gf.Directory);
|
||||
var (action, showtype, bulktype) = trigger switch
|
||||
{
|
||||
"Assets_Extract_New_Tab" => (EAction.Show, EShowAssetType.JSON, EBulkType.None),
|
||||
"Assets_Show_Metadata" => (EAction.Show, EShowAssetType.Metadata, EBulkType.None),
|
||||
"Assets_Show_References" => (EAction.Show, EShowAssetType.References, EBulkType.None),
|
||||
"Assets_Decompile" => (EAction.Show, EShowAssetType.Decompile, EBulkType.Code),
|
||||
|
||||
"Save_Data" => (EAction.Export, EShowAssetType.None, EBulkType.Raw),
|
||||
"Save_Properties" => (EAction.Export, EShowAssetType.None, EBulkType.Properties),
|
||||
"Save_Textures" => (EAction.Export, EShowAssetType.None, EBulkType.Textures),
|
||||
"Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes),
|
||||
"Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations),
|
||||
"Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio),
|
||||
"Save_Code" => (EAction.Export, EShowAssetType.None, EBulkType.Code),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException("Unsupported asset action."),
|
||||
};
|
||||
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.ExportedCount, 0);
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.FailedExportCount, 0);
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
if (action is EAction.Show)
|
||||
{
|
||||
#region Asset Commands
|
||||
case "Assets_Extract_New_Tab":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true);
|
||||
}
|
||||
break;
|
||||
case "Assets_Show_Metadata":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ShowMetadata(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Show_References":
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.FindReferences(assets.FirstOrDefault());
|
||||
}
|
||||
break;
|
||||
case "Assets_Decompile":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Decompile(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Export_Data":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Properties":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Textures":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Models":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Animations":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Audio":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi);
|
||||
}
|
||||
break;
|
||||
#endregion
|
||||
if (showtype is EShowAssetType.References)
|
||||
assets = [assets.FirstOrDefault()];
|
||||
|
||||
#region Folder Commands
|
||||
case "Folders_Export_Data":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder);
|
||||
Action<GameFile> entryAction = showtype switch
|
||||
{
|
||||
EShowAssetType.JSON => entry => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true),
|
||||
EShowAssetType.Metadata => entry => contextViewModel.CUE4Parse.ShowMetadata(entry),
|
||||
EShowAssetType.Decompile => entry => contextViewModel.CUE4Parse.Decompile(entry),
|
||||
EShowAssetType.References => entry => contextViewModel.CUE4Parse.FindReferences(entry),
|
||||
_ => throw new ArgumentOutOfRangeException("Unsupported asset action type."),
|
||||
};
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Properties":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.SaveFolder(cancellationToken, folder);
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
entryAction(entry);
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Textures":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.TextureFolder(cancellationToken, folder);
|
||||
return;
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved textures from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Models":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ModelFolder(cancellationToken, folder);
|
||||
var (dirType, filetype) = bulktype switch
|
||||
{
|
||||
EBulkType.Raw => (UserSettings.Default.RawDataDirectory, "files"),
|
||||
EBulkType.Properties => (UserSettings.Default.PropertiesDirectory, "json files"),
|
||||
EBulkType.Textures => (UserSettings.Default.TextureDirectory, "textures"),
|
||||
EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"),
|
||||
EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"),
|
||||
EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"),
|
||||
EBulkType.Code => (UserSettings.Default.CodeDirectory, "code files"),
|
||||
_ => (null, null),
|
||||
};
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved models from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Animations":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.AnimationFolder(cancellationToken, folder);
|
||||
if (string.IsNullOrEmpty(dirType))
|
||||
return;
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved animations from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Audio":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.AudioFolder(cancellationToken, folder);
|
||||
Action<TreeItem> folderAction = bulktype switch
|
||||
{
|
||||
EBulkType.Raw => folder => contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder),
|
||||
_ => folder => contextViewModel.CUE4Parse.ExtractFolder(cancellationToken, folder, bulktype | EBulkType.Auto),
|
||||
};
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
#endregion
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
folderAction(folder);
|
||||
|
||||
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? folder.PathAtThisPoint : folder.PathAtThisPoint.SubstringAfterLast('/')).Replace('\\', '/');
|
||||
LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype);
|
||||
}
|
||||
|
||||
Action<GameFile, EBulkType, bool> fileAction = bulktype switch
|
||||
{
|
||||
EBulkType.Raw => (entry, _, update) => contextViewModel.CUE4Parse.ExportData(entry, !update),
|
||||
_ => (entry, bulk, update) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk),
|
||||
};
|
||||
|
||||
foreach (var group in assetsGroups)
|
||||
{
|
||||
var directory = group.Key;
|
||||
var list = group.ToArray();
|
||||
var update = list.Length > 1;
|
||||
var bulk = bulktype | (update ? EBulkType.Auto : EBulkType.None);
|
||||
foreach (var entry in list)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
fileAction(entry, bulk, update);
|
||||
}
|
||||
|
||||
if (update)
|
||||
{
|
||||
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? directory : directory.SubstringAfterLast('/')).Replace('\\', '/');
|
||||
LogExport(contextViewModel, directory, path, dirType, filetype);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType)
|
||||
{
|
||||
if (contextViewModel.CUE4Parse.ExportedCount > 0)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text($"Successfully exported {contextViewModel.CUE4Parse.ExportedCount} {fileType} from ", Constants.WHITE);
|
||||
FLogger.Link(directory, Path.Exists(path) ? path : basePath, true);
|
||||
});
|
||||
}
|
||||
else if (contextViewModel.CUE4Parse.FailedExportCount == 0)
|
||||
{
|
||||
// Not an error because folder simply might not contain type of asset user is trying to save
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to find any {fileType} in {directory}", Constants.WHITE, true);
|
||||
});
|
||||
}
|
||||
|
||||
if (contextViewModel.CUE4Parse.FailedExportCount > 0)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to export {contextViewModel.CUE4Parse.FailedExportCount} {fileType} from {directory}", Constants.WHITE, true);
|
||||
});
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.ExportedCount, 0);
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.FailedExportCount, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,34 +34,34 @@ public class TabCommand : ViewModelCommand<TabItem>
|
|||
case "Find_References":
|
||||
_applicationView.CUE4Parse.FindReferences(tabViewModel.Entry);
|
||||
break;
|
||||
case "Asset_Export_Data":
|
||||
case "Save_Data":
|
||||
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry));
|
||||
break;
|
||||
case "Asset_Save_Properties":
|
||||
case "Save_Properties":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Properties);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Textures":
|
||||
case "Save_Textures":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Textures);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Models":
|
||||
case "Save_Models":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Animations":
|
||||
case "Save_Animations":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Audio":
|
||||
case "Save_Audio":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Audio);
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ using System.Windows.Media;
|
|||
using System.Windows.Media.Imaging;
|
||||
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.FN.Assets.Exports.DataAssets;
|
||||
using CUE4Parse.GameTypes.LegoBatman.Assets;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
|
|
@ -66,6 +69,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
private const int MaxPreviewSize = 128;
|
||||
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private EGame? GameVersion => _applicationView.CUE4Parse?.Provider.Versions.Game;
|
||||
|
||||
public EResolveCompute Resolved { get; private set; } = EResolveCompute.None;
|
||||
public GameFile Asset { get; } = asset;
|
||||
|
|
@ -195,7 +199,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
if (pointer?.Object is null)
|
||||
return;
|
||||
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class?.Object?.Value as UStruct, pkg);
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class, pkg);
|
||||
ResolvedAssetType = dummy.ExportType;
|
||||
|
||||
(AssetCategory, AssetActions) = dummy switch
|
||||
|
|
@ -243,9 +247,9 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
|
||||
UFMODBankLookup => (EAssetCategory.Data, EBulkType.None),
|
||||
|
||||
UFMODBus or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA => (EAssetCategory.Audio, EBulkType.None),
|
||||
UFMODBus or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA or USQEXSEADSoundAttenuation => (EAssetCategory.Audio, EBulkType.None),
|
||||
|
||||
UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank => (EAssetCategory.SoundBank, EBulkType.Audio),
|
||||
UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank or USQEXSEADSoundBank => (EAssetCategory.SoundBank, EBulkType.Audio),
|
||||
|
||||
UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomCueSheet
|
||||
or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank
|
||||
|
|
@ -257,8 +261,10 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
UNiagaraSystem or UNiagaraScriptBase or UParticleSystem => (EAssetCategory.Particle, EBulkType.None),
|
||||
|
||||
// Game specific assets below
|
||||
UGbxGraphAsset => (EAssetCategory.Borderlands4, EBulkType.Audio), // Borderlands 4
|
||||
UFaceFXAnimSet when _applicationView.CUE4Parse?.Provider.Versions.Game is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands4, EBulkType.Audio), // Borderlands 4
|
||||
UBorderlandsDialogObject when GameVersion is EGame.GAME_Borderlands3 => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3;
|
||||
UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData when GameVersion is EGame.GAME_Borderlands4 or EGame.GAME_Borderlands3 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3;
|
||||
UFaceFXAnimSet when GameVersion is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
|
||||
UWubAudioEvent or UWubDialogueEvent when GameVersion is EGame.GAME_LEGOBatmanLegacyoftheDarkKnight => (EAssetCategory.LegoBatman, EBulkType.Audio), // Lego Batman: Legacy of the Dark Knight;
|
||||
|
||||
_ => (EAssetCategory.All, EBulkType.None),
|
||||
};
|
||||
|
|
@ -346,14 +352,20 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
case "pem":
|
||||
case "xml":
|
||||
case "gitignore":
|
||||
case "gitattributes":
|
||||
case "html":
|
||||
case "css":
|
||||
case "js":
|
||||
case "data":
|
||||
case "csv":
|
||||
case "sql":
|
||||
case "py":
|
||||
case "cs":
|
||||
AssetCategory = EAssetCategory.Data;
|
||||
break;
|
||||
case "stinfo":
|
||||
case "ushaderbytecode":
|
||||
case "upipelinecache":
|
||||
AssetCategory = EAssetCategory.ByteCode;
|
||||
break;
|
||||
case "wav":
|
||||
|
|
@ -427,6 +439,22 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
bitmap.Dispose();
|
||||
});
|
||||
}
|
||||
// Game specific extensions below
|
||||
case "ace" when GameVersion is EGame.GAME_Borderlands3:
|
||||
case "ncs" when GameVersion is EGame.GAME_Borderlands4:
|
||||
AssetCategory = EAssetCategory.Borderlands;
|
||||
break;
|
||||
case "dat" when GameVersion is EGame.GAME_Aion2:
|
||||
AssetCategory = EAssetCategory.Aion2;
|
||||
break;
|
||||
case "bytes" when GameVersion is EGame.GAME_RocoKingdomWorld:
|
||||
case "non" when GameVersion is EGame.GAME_RocoKingdomWorld:
|
||||
case "cam" when GameVersion is EGame.GAME_RocoKingdomWorld:
|
||||
AssetCategory = EAssetCategory.RocoKingdomWorld;
|
||||
break;
|
||||
case "ustbin" when GameVersion is EGame.GAME_DeltaForce:
|
||||
AssetCategory = EAssetCategory.DeltaForce;
|
||||
break;
|
||||
default:
|
||||
AssetCategory = EAssetCategory.All; // just so it sets resolved
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ using Serilog;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
|
@ -67,12 +69,103 @@ public class GameSelectorViewModel : ViewModel
|
|||
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
|
||||
public void AddUndetectedDir(string gameName, string gameDirectory)
|
||||
{
|
||||
var setting = DirectorySettings.Default(gameName, gameDirectory, true);
|
||||
if (TryDetectUeVersion(gameDirectory, out var ueVersion, out var newGameDirectory))
|
||||
{
|
||||
// gameDirectory = newGameDirectory; // directory was changed to point to the correct paks folder
|
||||
}
|
||||
|
||||
var setting = DirectorySettings.Default(gameName, gameDirectory, true, ueVersion);
|
||||
UserSettings.Default.PerDirectory[gameDirectory] = setting;
|
||||
_detectedDirectories.Add(setting);
|
||||
SelectedDirectory = DetectedDirectories.Last();
|
||||
}
|
||||
|
||||
private bool TryDetectUeVersion(string gameDirectory, out EGame ueVersion, [MaybeNullWhen(false)] out string newGameDirectory)
|
||||
{
|
||||
var targetGameDir = gameDirectory;
|
||||
if (!targetGameDir.EndsWith("Paks", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var dirs = Directory.GetDirectories(targetGameDir, "Paks", SearchOption.AllDirectories);
|
||||
var paksDir = dirs.Length == 1 ? dirs[0] : dirs.FirstOrDefault(x => !x.EndsWith("Engine\\Programs\\CrashReportClient\\Content\\Paks"));
|
||||
if (!string.IsNullOrEmpty(paksDir))
|
||||
{
|
||||
Log.Warning("Selected directory \"{GameDirectory}\" does not end with \"Paks\". Looking in \"{PaksDir}\" instead.", targetGameDir, paksDir);
|
||||
targetGameDir = paksDir;
|
||||
}
|
||||
|
||||
if (Directory.GetFiles(gameDirectory, "*.exe") is { Length: 1 } exe && TryGetUeVersionFromExe(exe[0], out ueVersion))
|
||||
{
|
||||
// we checked the exe in the original directory, the BootstrapPackagedGame one
|
||||
// but we still want c4p to use the paks folder as the game directory (if any), not the original one
|
||||
newGameDirectory = targetGameDir;
|
||||
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe[0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// past this point, we assume targetGameDir is the correct Paks folder
|
||||
newGameDirectory = targetGameDir;
|
||||
var projectDir = Path.Combine(targetGameDir, "..", "..");
|
||||
|
||||
var projectBinariesDir = Path.Combine(projectDir, "Binaries", "Win64");
|
||||
if (Directory.Exists(projectBinariesDir))
|
||||
{
|
||||
if (Directory.GetFiles(projectBinariesDir, "*-Win64-Shipping.exe") is { Length: > 0 } shipping)
|
||||
{
|
||||
foreach (var exe in shipping)
|
||||
{
|
||||
if (TryGetUeVersionFromExe(exe, out ueVersion))
|
||||
{
|
||||
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Directory.GetFiles(projectBinariesDir, "*.exe") is { Length: < 3 } exes)
|
||||
{
|
||||
foreach (var exe in exes)
|
||||
{
|
||||
if (TryGetUeVersionFromExe(exe, out ueVersion))
|
||||
{
|
||||
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var crashReportClientExe = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64", "CrashReportClient.exe");
|
||||
if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion))
|
||||
{
|
||||
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe);
|
||||
return true;
|
||||
}
|
||||
|
||||
ueVersion = EGame.GAME_UE4_LATEST;
|
||||
Log.Warning("Failed to detect UE version for \"{GameDirectory}\".", gameDirectory);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetUeVersionFromExe(string exePath, out EGame ueVersion)
|
||||
{
|
||||
ueVersion = EGame.GAME_UE4_LATEST;
|
||||
try
|
||||
{
|
||||
var info = FileVersionInfo.GetVersionInfo(exePath);
|
||||
ueVersion = info.FileMajorPart switch
|
||||
{
|
||||
4 => (EGame) Math.Min((uint)(GameUtils.GameUe4Base + (info.FileMinorPart << 16)), (uint) EGame.GAME_UE4_LATEST),
|
||||
5 => (EGame) Math.Min((uint)(GameUtils.GameUe5Base + (info.FileMinorPart << 16)), (uint) EGame.GAME_UE5_LATEST),
|
||||
_ => throw new InvalidOperationException($"Unsupported UE major version {info.FileMajorPart} detected from {exePath}")
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteSelectedGame()
|
||||
{
|
||||
UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem
|
||||
|
|
|
|||
|
|
@ -172,6 +172,13 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _criwareDecryptionKey, value);
|
||||
}
|
||||
|
||||
private string _unluacOpcodeMap;
|
||||
public string UnluacOpcodeMap
|
||||
{
|
||||
get => _unluacOpcodeMap;
|
||||
set => SetProperty(ref _unluacOpcodeMap, value);
|
||||
}
|
||||
|
||||
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
|
||||
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
|
||||
|
||||
|
|
@ -195,6 +202,7 @@ public class SettingsViewModel : ViewModel
|
|||
private string _propertiesSnapshot;
|
||||
private string _textureSnapshot;
|
||||
private string _audioSnapshot;
|
||||
private string _codeSnapshot;
|
||||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private ETexturePlatform _uePlatformSnapshot;
|
||||
|
|
@ -227,6 +235,7 @@ public class SettingsViewModel : ViewModel
|
|||
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
|
||||
_textureSnapshot = UserSettings.Default.TextureDirectory;
|
||||
_audioSnapshot = UserSettings.Default.AudioDirectory;
|
||||
_codeSnapshot = UserSettings.Default.CodeDirectory;
|
||||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
|
||||
|
|
@ -235,6 +244,7 @@ public class SettingsViewModel : ViewModel
|
|||
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
|
||||
_mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes;
|
||||
_criwareDecryptionKey = UserSettings.Default.CurrentDir.CriwareDecryptionKey;
|
||||
_unluacOpcodeMap = UserSettings.Default.CurrentDir.UnluacOpCodeMap;
|
||||
|
||||
AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0];
|
||||
MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1];
|
||||
|
|
@ -271,6 +281,7 @@ public class SettingsViewModel : ViewModel
|
|||
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
|
||||
SelectedTextureExportFormat = _textureExportFormatSnapshot;
|
||||
CriwareDecryptionKey = _criwareDecryptionKey;
|
||||
UnluacOpcodeMap = _unluacOpcodeMap;
|
||||
SelectedAesReload = UserSettings.Default.AesReload;
|
||||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||
|
||||
|
|
@ -303,12 +314,6 @@ public class SettingsViewModel : ViewModel
|
|||
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
|
||||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
|
||||
_mapStructTypesSnapshot != SelectedMapStructTypes ||
|
||||
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
|
||||
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
|
||||
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
|
||||
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
|
||||
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
|
||||
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
|
||||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
|
||||
restart = true;
|
||||
|
||||
|
|
@ -318,6 +323,7 @@ public class SettingsViewModel : ViewModel
|
|||
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
|
||||
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
|
||||
UserSettings.Default.CurrentDir.CriwareDecryptionKey = CriwareDecryptionKey;
|
||||
UserSettings.Default.CurrentDir.UnluacOpCodeMap = UnluacOpcodeMap;
|
||||
|
||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.Utils;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.Commands;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
|
@ -8,15 +19,6 @@ using ICSharpCode.AvalonEdit.Document;
|
|||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
@ -374,8 +376,7 @@ public class TabItem : ViewModel
|
|||
public void SaveImage() => SaveImage(SelectedImage, true);
|
||||
private void SaveImage(TabImage image, bool updateUi)
|
||||
{
|
||||
if (image == null)
|
||||
return;
|
||||
if (image is null) return;
|
||||
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory, UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", image.ExportName).Replace('\\', '/');
|
||||
|
||||
|
|
@ -392,6 +393,7 @@ public class TabItem : ViewModel
|
|||
|
||||
private void SaveImage(TabImage image, string path)
|
||||
{
|
||||
if (image.ImageBuffer is null) return;
|
||||
using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
|
||||
}
|
||||
|
|
@ -407,11 +409,22 @@ public class TabItem : ViewModel
|
|||
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
||||
SaveCheck(directory, fileName, updateUi);
|
||||
}
|
||||
public void SaveDecompiled(bool updateUi)
|
||||
{
|
||||
var fileName = Path.ChangeExtension(Entry.Name, ".cpp");
|
||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/');
|
||||
|
||||
Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
||||
SaveCheck(directory, fileName, updateUi);
|
||||
}
|
||||
private void SaveCheck(string path, string fileName, bool updateUi)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Interlocked.Increment(ref ApplicationService.ApplicationView.CUE4Parse.ExportedCount);
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
if (updateUi)
|
||||
{
|
||||
|
|
@ -424,6 +437,7 @@ public class TabItem : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref ApplicationService.ApplicationView.CUE4Parse.FailedExportCount);
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileName}'", Constants.WHITE, true));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CUE4Parse.UE4.Exceptions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
|
@ -100,7 +101,26 @@ public class ThreadWorkerViewModel : ViewModel
|
|||
CurrentCancellationTokenSource = null; // kill token
|
||||
|
||||
Log.Error("{Exception}", e);
|
||||
FLogger.Append(e);
|
||||
switch (e)
|
||||
{
|
||||
case MappingException:
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text("Package has unversioned properties but mapping file (.usmap) is missing, can't serialize. See: ", Constants.WHITE);
|
||||
FLogger.Link("→ link ←", Constants.MAPPING_ISSUE_LINK, true);
|
||||
});
|
||||
break;
|
||||
case VersionException v: // Error might be unrelated to version, but it's usually the case
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text(v.Message[..^1] + ", can't serialize. Make sure the correct UE version is configured. See: ", Constants.WHITE);
|
||||
FLogger.Link("→ link ←", Constants.VERSION_ISSUE_LINK, true);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
FLogger.Append(e);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ public partial class UpdateViewModel : ViewModel
|
|||
if (username.Equals("Asval", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
username = "4sval"; // found out the hard way co-authored usernames can't be trusted
|
||||
} else if (username.Equals("Krowe Moh", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
username = "Krowe-moh";
|
||||
}
|
||||
|
||||
coAuthorMap[commit].Add(username);
|
||||
|
|
@ -101,7 +104,7 @@ public partial class UpdateViewModel : ViewModel
|
|||
}
|
||||
catch
|
||||
{
|
||||
//
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
|
||||
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" SizeToContent="Height"
|
||||
MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.20'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.25'}">
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.25'}"
|
||||
AllowDrop="True">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="Directory Selector" />
|
||||
|
|
@ -83,9 +84,12 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" />
|
||||
|
||||
<Expander ExpandDirection="Down" IsExpanded="False">
|
||||
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" Margin="0,0,0,0"/>
|
||||
<TextBlock Text="Drag & drop folder to quickly configure new game"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||
Margin="0,0,0,5" />
|
||||
<Expander x:Name="ManualGameExpander" ExpandDirection="Down" IsExpanded="False">
|
||||
<Grid MaxWidth="{Binding ActualWidth, ElementName=Hello}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
|
@ -137,5 +141,8 @@
|
|||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Cancel" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<controls:DropOverlay Grid.RowSpan="99"
|
||||
Grid.ColumnSpan="99"
|
||||
Panel.ZIndex="1000" />
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FModel.ViewModels;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using System.Windows;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
|
|
@ -43,28 +39,7 @@ public partial class DirectorySelector
|
|||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
|
||||
|
||||
// install_folder/
|
||||
// ├─ Engine/
|
||||
// ├─ GameName/
|
||||
// │ ├─ Binaries/
|
||||
// │ ├─ Content/
|
||||
// │ │ ├─ Paks/
|
||||
// our goal is to get the GameName folder
|
||||
var currentFolder = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
if (currentFolder.Equals("Paks", StringComparison.InvariantCulture))
|
||||
{
|
||||
var dir = new DirectoryInfo(folderBrowser.SelectedPath);
|
||||
if (dir.Parent is { Parent: not null } &&
|
||||
dir.Parent.Name.Equals("Content", StringComparison.InvariantCulture) &&
|
||||
dir.Parent.Parent.GetDirectories().Any(x => x.Name == "Binaries"))
|
||||
{
|
||||
HelloMyNameIsGame.Text = dir.Parent.Parent.Name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HelloMyNameIsGame.Text = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
HelloMyNameIsGame.Text = Helper.GetGameName(folderBrowser.SelectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,4 +62,11 @@ public partial class DirectorySelector
|
|||
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
}
|
||||
|
||||
public void AddManualGame(string directory)
|
||||
{
|
||||
ManualGameExpander.IsExpanded = true;
|
||||
HelloMyNameIsGame.Text = Helper.GetGameName(directory);
|
||||
HelloGameMyNameIsDirectory.Text = directory;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<SolidColorBrush x:Key="AudioBrush" Color="MediumSeaGreen" />
|
||||
<SolidColorBrush x:Key="SoundBankBrush" Color="DarkSeaGreen" />
|
||||
<SolidColorBrush x:Key="AudioEventBrush" Color="DarkTurquoise" />
|
||||
<SolidColorBrush x:Key="AudioEventBrush" Color="LightSeaGreen" />
|
||||
|
||||
<SolidColorBrush x:Key="VideoBrush" Color="IndianRed" />
|
||||
<SolidColorBrush x:Key="DataTableBrush" Color="SteelBlue" />
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<SolidColorBrush x:Key="FoliageBrush" Color="ForestGreen" />
|
||||
<SolidColorBrush x:Key="ParticleBrush" Color="Gold" />
|
||||
<SolidColorBrush x:Key="AnimationBrush" Color="Coral" />
|
||||
<SolidColorBrush x:Key="LuaBrush" Color="DarkBlue" />
|
||||
<SolidColorBrush x:Key="LuaBrush" Color="Blue" />
|
||||
<SolidColorBrush x:Key="JsonXmlBrush" Color="LightGreen" />
|
||||
<SolidColorBrush x:Key="CodeBrush" Color="SandyBrown" />
|
||||
<SolidColorBrush x:Key="HtmlBrush" Color="Tomato" />
|
||||
|
|
@ -47,7 +47,15 @@
|
|||
<SolidColorBrush x:Key="CssBrush" Color="MediumPurple" />
|
||||
<SolidColorBrush x:Key="GitBrush" Color="Coral" />
|
||||
<SolidColorBrush x:Key="CsvBrush" Color="ForestGreen" />
|
||||
<SolidColorBrush x:Key="AIBrush" Color="LightGray" />
|
||||
<SolidColorBrush x:Key="SQLBrush" Color="#00758f" />
|
||||
<SolidColorBrush x:Key="PythonBrush" Color="#3d77a8" />
|
||||
<SolidColorBrush x:Key="CSharpBrush" Color="#9e559a" />
|
||||
|
||||
<!-- For specific games -->
|
||||
<SolidColorBrush x:Key="BorderlandsBrush" Color="Yellow"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AionBrush" Color="DeepSkyBlue"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="RocoKingdomWorldBrush" Color="#fecf4d"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="DeltaForceBrush" Color="LightGreen"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="BatmanBrush" Color="Gold"></SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@
|
|||
</MenuItem.Header>
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -135,7 +135,7 @@
|
|||
<MenuItem Header="Save Properties (.json)" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
<MenuItem Header="Save Texture" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -176,7 +176,7 @@
|
|||
<MenuItem Header="Save Model" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -202,7 +202,7 @@
|
|||
<MenuItem Header="Save Animation" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -228,7 +228,7 @@
|
|||
<MenuItem Header="Save Audio" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Export_Data" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -65,11 +65,31 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Decompiled Blueprints"
|
||||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Save_Code" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16"
|
||||
Height="16">
|
||||
<Canvas Width="24"
|
||||
Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||
Data="{StaticResource CodeIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Models"
|
||||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -89,7 +109,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -109,7 +129,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
|
|||
58
FModel/Views/Resources/Controls/DropOverlay.xaml
Normal file
58
FModel/Views/Resources/Controls/DropOverlay.xaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<UserControl x:Class="FModel.Views.Resources.Controls.DropOverlay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:FModel.Views.Resources.Controls"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
Visibility="Collapsed"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<Grid Background="#DD000000"
|
||||
IsHitTestVisible="False"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="99">
|
||||
<Border BorderThickness="2"
|
||||
CornerRadius="10"
|
||||
Margin="60"
|
||||
Background="#1FFFFFFF">
|
||||
<Border.BorderBrush>
|
||||
<VisualBrush>
|
||||
<VisualBrush.Visual>
|
||||
<Rectangle Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
|
||||
StrokeThickness="2"
|
||||
StrokeDashArray="4 2"
|
||||
StrokeDashCap="Round"
|
||||
Width="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualWidth}"
|
||||
Height="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualHeight}"
|
||||
RadiusX="10"
|
||||
RadiusY="10" />
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Border.BorderBrush>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<Viewbox Width="80"
|
||||
Height="80"
|
||||
Margin="0,0,0,24"
|
||||
HorizontalAlignment="Center">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
|
||||
Data="{StaticResource ImportIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock x:Name="TitleText" Text="Drop .usmap to import"
|
||||
Foreground="White"
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="DescriptionText" Text="Mapping file will be applied immediately"
|
||||
Foreground="LightGray"
|
||||
FontSize="14"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10,0,0" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
170
FModel/Views/Resources/Controls/DropOverlay.xaml.cs
Normal file
170
FModel/Views/Resources/Controls/DropOverlay.xaml.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public partial class DropOverlay : UserControl
|
||||
{
|
||||
enum DragStatus
|
||||
{
|
||||
None,
|
||||
File,
|
||||
Folder,
|
||||
}
|
||||
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DragStatus _dragStatus = DragStatus.None;
|
||||
private string _path = null;
|
||||
|
||||
public DropOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ResetState()
|
||||
{
|
||||
_dragStatus = DragStatus.None;
|
||||
_path = null;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
if (window is null)
|
||||
return;
|
||||
|
||||
window.PreviewDragEnter += OnPreviewDragEnter;
|
||||
window.PreviewDragOver += OnPreviewDragOver;
|
||||
window.PreviewDragLeave += OnPreviewDragLeave;
|
||||
window.Drop += OnDrop;
|
||||
}
|
||||
|
||||
private void OnPreviewDragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
GetValidTarget(sender, e);
|
||||
if (_dragStatus is DragStatus.None)
|
||||
{
|
||||
e.Effects = DragDropEffects.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
Visibility = Visibility.Visible;
|
||||
e.Effects = DragDropEffects.Copy;
|
||||
|
||||
if (_dragStatus is DragStatus.Folder)
|
||||
{
|
||||
TitleText.Text = "Drop folder to add new game";
|
||||
DescriptionText.Text = "Folder will be added to the directory selector";
|
||||
}
|
||||
else if (_dragStatus is DragStatus.File)
|
||||
{
|
||||
TitleText.Text = "Drop usmap/jmap to import";
|
||||
DescriptionText.Text = "Mapping file will be applied immediately";
|
||||
}
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPreviewDragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Effects = _dragStatus is DragStatus.None ? DragDropEffects.None : DragDropEffects.Copy;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPreviewDragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private async void OnDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
e.Handled = true;
|
||||
switch (_dragStatus)
|
||||
{
|
||||
case DragStatus.Folder:
|
||||
await Dispatcher.InvokeAsync(() => _applicationView.AddGameDirectory(_path));
|
||||
break;
|
||||
case DragStatus.File:
|
||||
UserSettings.IsEndpointValid(EEndpointType.Mapping, out var oldMappingsEndpoint);
|
||||
try
|
||||
{
|
||||
var newMappingsEndpoint = new EndpointSettings() { Overwrite = true, FilePath = _path };
|
||||
UserSettings.Default.CurrentDir.Endpoints[(int) EEndpointType.Mapping] = newMappingsEndpoint;
|
||||
await _applicationView.CUE4Parse.InitMappings();
|
||||
_applicationView.SettingsView.MappingEndpoint = newMappingsEndpoint;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UserSettings.Default.CurrentDir.Endpoints[(int) EEndpointType.Mapping] = oldMappingsEndpoint;
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to load mapping file: {ex.Message}", Constants.WHITE, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void GetValidTarget(object sender, DragEventArgs e)
|
||||
{
|
||||
if (!_applicationView.Status.IsReady || !e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetData(DataFormats.FileDrop) is not string[] files)
|
||||
return;
|
||||
|
||||
bool directorySelectorIsVisible = _applicationView.Status.Kind is EStatusKind.Configuring;
|
||||
if (!directorySelectorIsVisible && (Helper.IsWindowOpen<DictionaryEditor>() || Helper.IsWindowOpen<EndpointEditor>()))
|
||||
{
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
ResetState();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (sender)
|
||||
{
|
||||
case MainWindow or SettingsView when !directorySelectorIsVisible:
|
||||
foreach (var path in files)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
_path = path;
|
||||
_dragStatus = DragStatus.Folder;
|
||||
return;
|
||||
}
|
||||
else if (File.Exists(path) && path.EndsWith(".usmap", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith(".jmap", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith(".jmap.gz", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_path = path;
|
||||
_dragStatus = DragStatus.File;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DirectorySelector:
|
||||
if (files.FirstOrDefault(f => Directory.Exists(f)) is { } folder)
|
||||
{
|
||||
_path = folder;
|
||||
_dragStatus = DragStatus.Folder;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ResetState();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -23,8 +24,9 @@ public partial class EndpointEditor
|
|||
InitializeComponent();
|
||||
|
||||
Title = title;
|
||||
TargetResponse.SyntaxHighlighting =
|
||||
EndpointResponse.SyntaxHighlighting = AvalonExtensions.HighlighterSelector("json");
|
||||
TargetResponse.SyntaxHighlighting = EndpointResponse.SyntaxHighlighting = AvalonExtensions.HighlighterSelector("json");
|
||||
TargetResponse.TextArea.TextView.LinkTextForegroundBrush = Brushes.DodgerBlue;
|
||||
EndpointResponse.TextArea.TextView.LinkTextForegroundBrush = Brushes.DodgerBlue;
|
||||
|
||||
InstructionBox.Text = type switch
|
||||
{
|
||||
|
|
@ -91,7 +93,7 @@ public partial class EndpointEditor
|
|||
|
||||
private void OnEvaluator(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo { FileName = "https://jsonpath.herokuapp.com/", UseShellExecute = true });
|
||||
Process.Start(new ProcessStartInfo { FileName = "https://jsonpath.com/", UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
|
@ -126,13 +126,40 @@ public class FLogger : ITextFormatter
|
|||
{
|
||||
NavigateUri = new Uri(url),
|
||||
OverridesDefaultStyle = true,
|
||||
Style = new Style(typeof(Hyperlink)) { Setters =
|
||||
Style = new Style(typeof(Hyperlink))
|
||||
{
|
||||
new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand),
|
||||
new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline),
|
||||
new Setter(TextElement.ForegroundProperty, Brushes.Cornsilk)
|
||||
}}
|
||||
}.Click += (sender, _) => Process.Start("explorer.exe", $"/select, \"{((Hyperlink)sender).NavigateUri.AbsoluteUri}\"");
|
||||
Setters =
|
||||
{
|
||||
new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand),
|
||||
new Setter(TextElement.ForegroundProperty, Brushes.Goldenrod),
|
||||
new Setter(TextElement.FontWeightProperty, FontWeights.Bold)
|
||||
},
|
||||
Triggers =
|
||||
{
|
||||
new Trigger
|
||||
{
|
||||
Property = UIElement.IsMouseOverProperty,
|
||||
Value = true,
|
||||
Setters =
|
||||
{
|
||||
new Setter(TextElement.ForegroundProperty, Brushes.Gold),
|
||||
new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.Click += (sender, _) =>
|
||||
{
|
||||
var uri = ((Hyperlink) sender).NavigateUri;
|
||||
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(uri.AbsoluteUri) { UseShellExecute = true });
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select, \"{uri.AbsoluteUri}\"");
|
||||
}
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
32
FModel/Views/Resources/Converters/EnumFlagToBoolConverter.cs
Normal file
32
FModel/Views/Resources/Converters/EnumFlagToBoolConverter.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public sealed class EnumFlagToBoolConverter : IValueConverter
|
||||
{
|
||||
public static readonly EnumFlagToBoolConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is null || parameter is null) return false;
|
||||
|
||||
var enumType = value.GetType();
|
||||
if (!enumType.IsEnum) return false;
|
||||
|
||||
var flag = parameter is string s
|
||||
? Enum.Parse(enumType, s, ignoreCase: true)
|
||||
: parameter;
|
||||
|
||||
var current = System.Convert.ToInt64(value);
|
||||
var wanted = System.Convert.ToInt64(flag);
|
||||
|
||||
return (current & wanted) == wanted;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters
|
||||
{
|
||||
public class FileNameWithoutExtensionConverter : IValueConverter
|
||||
{
|
||||
public static readonly FileNameWithoutExtensionConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> value is string s ? Path.GetFileNameWithoutExtension(s) : value;
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -80,16 +80,24 @@ public class FileToGeometryConverter : IMultiValueConverter
|
|||
"bin" => ("DataTableIcon", "BinaryBrush"),
|
||||
"xml" => ("XmlIcon", "JsonXmlBrush"),
|
||||
"gitignore" => ("GitIcon", "GitBrush"),
|
||||
"gitattributes" => ("GitIcon", "GitBrush"),
|
||||
"html" => ("HtmlIcon", "HtmlBrush"),
|
||||
"js" => ("JavaScriptIcon", "JavaScriptBrush"),
|
||||
"css" => ("CssIcon", "CssBrush"),
|
||||
"csv" => ("CsvIcon", "CsvBrush"),
|
||||
"sql" => ("SQLIcon", "SQLBrush"),
|
||||
"py" => ("PythonIcon", "PythonBrush"),
|
||||
"cs" => ("CSharpIcon", "CSharpBrush"),
|
||||
_ => ("DataTableIcon", "NeutralBrush")
|
||||
},
|
||||
|
||||
EAssetCategory.ByteCode => ("CodeIcon", "CodeBrush"),
|
||||
|
||||
EAssetCategory.Borderlands4 => ("BorderlandsIcon", "BorderlandsBrush"),
|
||||
EAssetCategory.Borderlands => ("BorderlandsIcon", "BorderlandsBrush"),
|
||||
EAssetCategory.Aion2 => ("AionIcon", "AionBrush"),
|
||||
EAssetCategory.RocoKingdomWorld => ("RocoKingdomWorldIcon", "RocoKingdomWorldBrush"),
|
||||
EAssetCategory.DeltaForce => ("DeltaForceIcon", "DeltaForceBrush"),
|
||||
EAssetCategory.LegoBatman => ("BatmanIcon", "BatmanBrush"),
|
||||
|
||||
_ => ("AssetIcon", "NeutralBrush")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public class FolderToGeometryConverter : IValueConverter
|
|||
|
||||
var (geometry, brush) = folderName switch
|
||||
{
|
||||
"ai" => ("AIIcon", "AIBrush"),
|
||||
"textures" or "texture" or "ui" or "icons" or "umgassets" or "hud" or "hdri" or "tex" => ("TextureIconAlt", "TextureBrush"),
|
||||
"config" or "tags" => ("ConfigIcon", "ConfigBrush"),
|
||||
"audio" or "wwiseaudio" or "wwise" or "fmod" or "sound" or "sounds" or "cue" => ("AudioIconAlt", "AudioBrush"),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using FModel.Extensions;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
|
|
|
|||
23
FModel/Views/Resources/Converters/TextToRefreshConverter.cs
Normal file
23
FModel/Views/Resources/Converters/TextToRefreshConverter.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class TextToRefreshConverter : IValueConverter
|
||||
{
|
||||
public static readonly TextToRefreshConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is DateTime dt && dt != DateTime.MaxValue)
|
||||
return $"Next Refresh: {dt:MMM d, yyyy}";
|
||||
|
||||
return "Next Refresh: Never";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -13,8 +13,6 @@
|
|||
xmlns:adonisConverters="clr-namespace:AdonisUI.Converters;assembly=AdonisUI"
|
||||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI">
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
|
||||
<Style x:Key="TabItemFillSpace" TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="Width">
|
||||
<Setter.Value>
|
||||
|
|
@ -773,9 +771,15 @@
|
|||
</Image.ContextMenu>
|
||||
</Image>
|
||||
</Border>
|
||||
<TextBlock Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Center"
|
||||
Visibility="{Binding SelectedItem.HasMultipleImages, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Text="{Binding SelectedItem.Page, ElementName=TabControlName}" />
|
||||
<StackPanel Grid.Column="2" VerticalAlignment="Bottom" HorizontalAlignment="Center">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Text="{Binding SelectedItem.SelectedImage.ExportName, Converter={x:Static converters:FileNameWithoutExtensionConverter.Instance}, ElementName=TabControlName}"
|
||||
FontSize="10" FontWeight="SemiBold"
|
||||
Visibility="{Binding SelectedItem.HasImage, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Visibility="{Binding SelectedItem.HasMultipleImages, ElementName=TabControlName, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||
Text="{Binding SelectedItem.Page, ElementName=TabControlName}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding SelectedItem.HasImage, ElementName=TabControlName}" Value="False">
|
||||
|
|
@ -928,7 +932,7 @@
|
|||
</MenuItem.IsEnabled>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data">
|
||||
<MenuItem Command="{Binding TabCommand}" CommandParameter="Save_Data">
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Export Raw Data', StringFormat='Export Raw Data (.{0})'}" />
|
||||
</MenuItem.Header>
|
||||
|
|
@ -940,7 +944,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Properties">
|
||||
<MenuItem Header="Save Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Save_Properties">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -949,7 +953,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Textures">
|
||||
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Save_Textures">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -958,7 +962,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Models">
|
||||
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Save_Models">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -967,7 +971,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Animations">
|
||||
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Save_Animations">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -976,7 +980,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Audio" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Audio">
|
||||
<MenuItem Header="Save Audio" Command="{Binding TabCommand}" CommandParameter="Save_Audio">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@
|
|||
</MenuItem.Header>
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -231,7 +231,7 @@
|
|||
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -246,7 +246,7 @@
|
|||
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -261,7 +261,7 @@
|
|||
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -291,7 +291,7 @@
|
|||
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -561,7 +561,7 @@
|
|||
</MenuItem.Header>
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -576,7 +576,7 @@
|
|||
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -591,7 +591,7 @@
|
|||
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -606,7 +606,7 @@
|
|||
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -621,7 +621,7 @@
|
|||
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -636,7 +636,7 @@
|
|||
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" SizeToContent="Height"
|
||||
MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.10'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.45'}">
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.45'}"
|
||||
AllowDrop="True">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="Settings" />
|
||||
|
|
@ -156,15 +157,32 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Auto-save packages following their game directory" />
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 10" ToolTip="Auto-save packages following their game directory" />
|
||||
<CheckBox Grid.Row="9" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 0"
|
||||
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<Separator Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Local Mapping File (drag & drop)" VerticalAlignment="Center" Margin="0 5 0 0" />
|
||||
<CheckBox Grid.Row="10" Grid.Column="2" Margin="0 5 0 0"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 10 0 0"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 10 0 0" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<Button Grid.Row="11" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 10 0 0"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<Separator Grid.Row="12" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
|
||||
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
|
|
@ -178,10 +196,10 @@
|
|||
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<ComboBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
<ComboBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
ItemsSource="{Binding SettingsView.AesReloads}" SelectedItem="{Binding SettingsView.SelectedAesReload, Mode=TwoWay}"
|
||||
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
|
|
@ -192,8 +210,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
|
|
@ -205,23 +223,6 @@
|
|||
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="14" Grid.Column="2" Margin="0 5 0 10"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<Button Grid.Row="15" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<TextBlock Grid.Row="16" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
|
||||
|
|
@ -237,23 +238,24 @@
|
|||
IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<TextBlock Grid.Row="19"
|
||||
<TextBlock Grid.Row="19" Grid.Column="0" Text="Decompile Lua" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Decompile/Disassemble compiled lua files with an unluac." />
|
||||
<CheckBox Grid.Row="19" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding DecompileLua, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
|
||||
Checked="OnDecompileLuaChanged" />
|
||||
|
||||
<TextBlock Grid.Row="20"
|
||||
Grid.Column="0"
|
||||
Text="Convert Audio During Export (.wav)"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="19"
|
||||
<CheckBox Grid.Row="20"
|
||||
Grid.Column="2"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding ConvertAudioOnBulkExport, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Margin="0 5 0 10"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<TextBlock Grid.Row="20" Grid.Column="0" Text="Max Wwise Bank (.BNK) Prefetch" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Slider Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="5" TickPlacement="None" Minimum="0" Maximum="2048" Ticks="0,8,32,128,256,512,1024,2048"
|
||||
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
|
||||
Value="{Binding WwiseMaxBnkPrefetch, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="21"
|
||||
Grid.Column="0"
|
||||
Text="CRIWARE Decryption Key"
|
||||
|
|
@ -392,6 +394,8 @@
|
|||
<ContentControl.Style>
|
||||
<Style TargetType="{x:Type ContentControl}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
|
||||
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
|
||||
<Setter Property="ContentTemplate">
|
||||
|
|
@ -607,6 +611,98 @@
|
|||
HotKey="{Binding NextAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="unluacTemplate">
|
||||
<Grid adonisExtensions:LayerExtension.Layer="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Mode" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="10"
|
||||
SelectedValuePath="Tag"
|
||||
SelectedValue="{Binding UnluacMode, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Margin="0 0 0 5">
|
||||
<ComboBoxItem Content="Decompile" Tag="{x:Static local:EUnluacMode.Decompile}" />
|
||||
<ComboBoxItem Content="Disassemble" Tag="{x:Static local:EUnluacMode.Disassemble}" />
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Raw string" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Tag="RawString"
|
||||
Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"
|
||||
IsChecked="{Binding UnluacFlags, Source={x:Static local:Settings.UserSettings.Default},
|
||||
Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=RawString, Mode=OneWay}"
|
||||
Checked="OnUnluacFlagChanged"
|
||||
Unchecked="OnUnluacFlagChanged"
|
||||
ToolTip="Copy string bytes directly to output">
|
||||
</CheckBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="4" Text="No debug" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Tag="NoDebug"
|
||||
Grid.Row="1" Grid.Column="6" Grid.ColumnSpan="3"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"
|
||||
IsChecked="{Binding UnluacFlags, Source={x:Static local:Settings.UserSettings.Default},
|
||||
Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=NoDebug, Mode=OneWay}"
|
||||
Checked="OnUnluacFlagChanged"
|
||||
Unchecked="OnUnluacFlagChanged"
|
||||
ToolTip="Ignore debugging information in input file">
|
||||
</CheckBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="8" Text="Luaj" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Tag="Luaj"
|
||||
Grid.Row="1" Grid.Column="10" Grid.ColumnSpan="3"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"
|
||||
IsChecked="{Binding UnluacFlags, Source={x:Static local:Settings.UserSettings.Default},
|
||||
Converter={x:Static converters:EnumFlagToBoolConverter.Instance}, ConverterParameter=Luaj, Mode=OneWay}"
|
||||
Checked="OnUnluacFlagChanged"
|
||||
Unchecked="OnUnluacFlagChanged"
|
||||
ToolTip="Emulate Luaj's permissive parser">
|
||||
</CheckBox>
|
||||
|
||||
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="11" Style="{StaticResource CustomSeparator}" />
|
||||
|
||||
<TextBlock Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Text="OpcodeMap"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0 0 0 5"/>
|
||||
<TextBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="10"
|
||||
Margin="0 0 0 5" Height="300"
|
||||
FontSize="14" Padding="8"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Top"
|
||||
VerticalContentAlignment="Top" TextAlignment="Left"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Text="{Binding SettingsView.UnluacOpcodeMap, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
AcceptsReturn="True"
|
||||
AcceptsTab="True"
|
||||
TextWrapping="NoWrap"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
ToolTip="Paste a custom opcode map." />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</adonisControls:AdonisWindow.Resources>
|
||||
<Grid>
|
||||
|
|
@ -684,6 +780,28 @@
|
|||
</StackPanel>
|
||||
</TreeViewItem.Header>
|
||||
</TreeViewItem>
|
||||
<TreeViewItem Tag="unluacTemplate">
|
||||
<TreeViewItem.Header>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource LuaIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<TextBlock Text="unluac" HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</TreeViewItem.Header>
|
||||
<TreeViewItem.Style>
|
||||
<Style TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DecompileLua, Source={x:Static local:Settings.UserSettings.Default}}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TreeViewItem.Style>
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="1" Margin="{adonisUi:Space 1, 0.5}" HorizontalAlignment="Stretch">
|
||||
|
|
@ -698,12 +816,15 @@
|
|||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="* Require a restart for changes to take effect"
|
||||
<TextBlock Grid.Column="0" Text="* May Require a restart for changes to take effect"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" FontSize="11" Margin="0 0 10 0"
|
||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.Layer1InteractionForegroundBrush}}" />
|
||||
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<controls:DropOverlay Grid.RowSpan="99"
|
||||
Grid.ColumnSpan="99"
|
||||
Panel.ZIndex="1000" />
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using CUE4Parse.UE4.Lua.unluac;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
|
@ -59,6 +62,8 @@ public partial class SettingsView
|
|||
|
||||
_applicationView.CUE4Parse.Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
|
||||
_applicationView.CUE4Parse.Provider.ReadShaderMaps = UserSettings.Default.ReadShaderMaps;
|
||||
|
||||
UserSettings.Save();
|
||||
}
|
||||
|
||||
private void OnBrowseOutput(object sender, RoutedEventArgs e)
|
||||
|
|
@ -72,6 +77,7 @@ public partial class SettingsView
|
|||
UserSettings.Default.PropertiesDirectory = path;
|
||||
UserSettings.Default.TextureDirectory = path;
|
||||
UserSettings.Default.AudioDirectory = path;
|
||||
UserSettings.Default.CodeDirectory = path;
|
||||
}
|
||||
|
||||
private void OnBrowseDirectories(object sender, RoutedEventArgs e)
|
||||
|
|
@ -153,7 +159,11 @@ public partial class SettingsView
|
|||
private void OpenCustomVersions(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedCustomVersions, "Versioning Configuration (Custom Versions)");
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
var result = editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
|
|
@ -163,7 +173,11 @@ public partial class SettingsView
|
|||
private void OpenOptions(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedOptions, "Versioning Configuration (Options)");
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
var result = editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
|
|
@ -173,7 +187,11 @@ public partial class SettingsView
|
|||
private void OpenMapStructTypes(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedMapStructTypes, "Versioning Configuration (MapStructTypes)");
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
var result = editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
|
|
@ -184,14 +202,22 @@ public partial class SettingsView
|
|||
{
|
||||
var editor = new EndpointEditor(
|
||||
_applicationView.SettingsView.AesEndpoint, "Endpoint Configuration (AES)", EEndpointType.Aes);
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
}
|
||||
|
||||
private void OpenMappingEndpoint(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new EndpointEditor(
|
||||
_applicationView.SettingsView.MappingEndpoint, "Endpoint Configuration (Mapping)", EEndpointType.Mapping);
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
}
|
||||
|
||||
private void CriwareKeyBox_Loaded(object sender, RoutedEventArgs e)
|
||||
|
|
@ -241,4 +267,29 @@ public partial class SettingsView
|
|||
out value
|
||||
);
|
||||
}
|
||||
|
||||
private void OnHyperlinkClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is not Hyperlink hyperlink)
|
||||
return;
|
||||
|
||||
Process.Start(new ProcessStartInfo(hyperlink.NavigateUri.AbsoluteUri) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private async void OnDecompileLuaChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is CheckBox { IsChecked: true } && UnluacHelper.Instance is null)
|
||||
await ApplicationViewModel.InitUnluac();
|
||||
}
|
||||
|
||||
private void OnUnluacFlagChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not CheckBox cb || cb.Tag is not string name) return;
|
||||
if (!Enum.TryParse<EUnluacFlags>(name, true, out var flag)) return;
|
||||
|
||||
var current = UserSettings.Default.UnluacFlags;
|
||||
var isChecked = cb.IsChecked == true;
|
||||
|
||||
UserSettings.Default.UnluacFlags = isChecked ? (current | flag) : (current & ~flag);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class Animation : IDisposable
|
|||
EndTime = Sequences[i].EndTime;
|
||||
}
|
||||
|
||||
TotalElapsedTime = animSet.TotalAnimTime;
|
||||
TotalElapsedTime = EndTime;
|
||||
if (Sequences.Length > 0)
|
||||
StartTime = Sequences[0].StartTime;
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ public class Animation : IDisposable
|
|||
var sequence = Sequences[i];
|
||||
if (elapsedTime <= sequence.EndTime && elapsedTime >= sequence.StartTime)
|
||||
{
|
||||
Framing[i] = (elapsedTime - sequence.StartTime) / sequence.TimePerFrame;
|
||||
Framing[i] = (elapsedTime - sequence.StartTime) * sequence.RateScale / sequence.SecondsPerFrame;
|
||||
}
|
||||
else Framing.Remove(i);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse.Utils;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
|
@ -8,24 +7,26 @@ namespace FModel.Views.Snooper.Animations;
|
|||
public class Sequence
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly float TimePerFrame;
|
||||
public readonly float RateScale;
|
||||
public readonly float StartTime;
|
||||
public readonly float Duration;
|
||||
public readonly float EndTime;
|
||||
public readonly int EndFrame;
|
||||
public readonly int LoopingCount;
|
||||
public readonly bool IsAdditive;
|
||||
public readonly float SecondsPerFrame;
|
||||
|
||||
public Sequence(CAnimSequence sequence)
|
||||
{
|
||||
Name = sequence.Name;
|
||||
TimePerFrame = 1.0f / sequence.FramesPerSecond;
|
||||
RateScale = sequence.OriginalSequence.RateScale;
|
||||
StartTime = sequence.StartPos;
|
||||
Duration = sequence.AnimEndTime;
|
||||
EndTime = StartTime + Duration;
|
||||
EndFrame = (Duration / TimePerFrame).FloorToInt() - 1;
|
||||
EndTime = StartTime + Duration / RateScale;
|
||||
EndFrame = sequence.NumFrames;
|
||||
LoopingCount = sequence.LoopingCount;
|
||||
IsAdditive = sequence.IsAdditive;
|
||||
SecondsPerFrame = Duration / EndFrame;
|
||||
}
|
||||
|
||||
public void DrawSequence(ImDrawListPtr drawList, ImFontPtr fontPtr, float x, Vector2 p2, Vector2 timeStep, Vector2 timeRatio, float t, bool animSelected)
|
||||
|
|
|
|||
|
|
@ -61,13 +61,13 @@ public class Morph : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget)
|
||||
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, int index = 0)
|
||||
{
|
||||
Name = morphTarget.Name;
|
||||
Vertices = new float[vertices.Length];
|
||||
Array.Copy(vertices, Vertices, vertices.Length);
|
||||
|
||||
foreach (var vert in morphTarget.MorphLODModels[0].Vertices)
|
||||
foreach (var vert in morphTarget.MorphLODModels[index].Vertices)
|
||||
{
|
||||
var count = 0;
|
||||
if (dict.TryGetValue(vert.SourceIdx, out var baseIndex))
|
||||
|
|
|
|||
|
|
@ -101,10 +101,10 @@ public class SkeletalModel : UModel
|
|||
foreach (var morph in export.MorphTargets)
|
||||
{
|
||||
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length < 1 ||
|
||||
morphTarget.MorphLODModels[0].Vertices.Length < 1)
|
||||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].LODIndex].Vertices.Length < 1)
|
||||
continue;
|
||||
|
||||
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget));
|
||||
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].LODIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public abstract class UModel : IRenderableModel
|
|||
{
|
||||
_export = export;
|
||||
Path = _export.GetPathName();
|
||||
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
|
||||
Name = export.Name;
|
||||
Type = export.ExportType;
|
||||
UvCount = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -241,13 +241,13 @@ public class Options
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip emmisive for specific games, cause of excessive use in their materials
|
||||
/// Skip emissive for specific games, cause of excessive use in their materials
|
||||
/// </summary>
|
||||
public bool SkipEmmisive()
|
||||
public bool SkipEmissive()
|
||||
{
|
||||
return _game switch
|
||||
{
|
||||
"LIESOFP" or "CODEVEIN2" => true,
|
||||
"LIESOFP" or "CODEVEIN2" or "HIGHONLIFE2" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ public class Material : IDisposable
|
|||
RoughnessMax = roughness + d;
|
||||
}
|
||||
|
||||
if (!options.SkipEmmisive())
|
||||
if (!options.SkipEmissive())
|
||||
{
|
||||
if (Parameters.TryGetScalar(out var emissiveMultScalar, "emissive mult", "Emissive_Mult", "EmissiveIntensity", "EmissionIntensity"))
|
||||
EmissiveMult = emissiveMultScalar;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
</adonisControls:SplitButton.SplitMenu>
|
||||
</adonisControls:SplitButton>
|
||||
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" FontSize="10" Margin="0 2.5 0 0"
|
||||
Text="{Binding NextUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, StringFormat=Next Refresh: {0:MMM d, yyyy}}" />
|
||||
Text="{Binding NextUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:TextToRefreshConverter.Instance}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user